Skip to content
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/boumenot/gocover-cobertura v1.2.0
github.com/cbroglie/mustache v1.4.0
github.com/cespare/xxhash/v2 v2.3.0
github.com/creack/pty v1.1.21
github.com/dustin/go-humanize v1.0.1
github.com/elastic/elastic-integration-corpus-generator-tool v0.10.0
github.com/elastic/go-elasticsearch/v7 v7.17.10
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBS
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creasty/defaults v1.7.0 h1:eNdqZvc5B509z18lD8yc212CAqJNvfT1Jq6L8WowdBA=
github.com/creasty/defaults v1.7.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
Expand Down
58 changes: 56 additions & 2 deletions internal/compose/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,23 @@
package compose

import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"io"
"os"
"os/exec"
"regexp"
"runtime"
"strconv"
"strings"
"sync"
"time"

"github.com/Masterminds/semver/v3"
"github.com/creack/pty"

"gopkg.in/yaml.v3"

Expand Down Expand Up @@ -486,16 +490,66 @@ func (p *Project) runDockerComposeCmd(ctx context.Context, opts dockerComposeOpt
}
cmd.Env = append(os.Environ(), opts.env...)

ptty, tty, err := pty.Open()
if err != nil {
return fmt.Errorf("failed to open pseudo-tty to capture stderr: %w", err)
}

var errBuffer bytes.Buffer
cmd.Stderr = tty
var stderr io.Writer = &errBuffer
if logger.IsDebugMode() {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
stderr = io.MultiWriter(&errBuffer, os.Stderr)
}
if opts.stdout != nil {
cmd.Stdout = opts.stdout
}

var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
io.Copy(stderr, ptty)
}()

logger.Debugf("running command: %s", cmd)
return cmd.Run()
err = cmd.Run()
ptty.Close()
tty.Close()
wg.Wait()
if err != nil {
if msg := cleanComposeError(errBuffer.String()); len(msg) > 0 {
return fmt.Errorf("%w: %s", err, msg)
}
}
return err
}

const daemonResponse = `Error response from daemon:`

// This regexp must match prefixes like WARN[0000], which may include escape sequences for colored letters
// or structured logs, starting with key=value pairs.
var composeLoggerPrefix = regexp.MustCompile(`^[^\s]+\[[0-9]+\]`)

func cleanComposeError(msg string) string {
// If there is a daemon response, just return it.
if i := strings.Index(msg, daemonResponse); i >= 0 {
return strings.TrimSpace(msg[i+len(daemonResponse):])
}

// Filter out lines coming from the docker compose structured logger.
var cleanError strings.Builder
scanner := bufio.NewScanner(strings.NewReader(msg))
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if composeLoggerPrefix.MatchString(line) {
continue
}
fmt.Fprintln(&cleanError, line)
}

return strings.TrimSpace(cleanError.String())
}

func (p *Project) dockerComposeBaseCommand() (name string, args []string) {
Expand Down
2 changes: 1 addition & 1 deletion internal/stack/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func Update(ctx context.Context, options Options) error {

err = dockerComposePull(ctx, options)
if err != nil {
return fmt.Errorf("updating docker images failed: %w", err)
return fmt.Errorf("pulling docker images failed: %w", err)
}
return nil
}