Skip to content

Commit da5c486

Browse files
authored
Fix build output (#2312)
* Fix build output Multiplexing stderr to a buffer accidentally broke TTY detection in docker. This PR restores it by using a pipe instead of a wrapper. Also fixed `--progress` not being passed to the second build step. * fix potential stderr/stdout deadlock * only use pty if error writer is a tty * fix deprecation warning * container stop prints the id, discard it * out goes to stderr unless a writer is provided
1 parent 74cd239 commit da5c486

File tree

4 files changed

+70
-28
lines changed

4 files changed

+70
-28
lines changed

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ require (
77
github.com/aws/aws-sdk-go-v2 v1.36.3
88
github.com/aws/aws-sdk-go-v2/credentials v1.17.67
99
github.com/aws/aws-sdk-go-v2/service/s3 v1.79.3
10+
github.com/creack/pty v1.1.24
1011
github.com/docker/cli v28.1.1+incompatible
1112
github.com/docker/docker v28.1.1+incompatible
1213
github.com/docker/go-connections v0.5.0
@@ -33,6 +34,7 @@ require (
3334
golang.org/x/sys v0.32.0
3435
golang.org/x/term v0.31.0
3536
gopkg.in/yaml.v2 v2.4.0
37+
gopkg.in/yaml.v3 v3.0.1
3638
sigs.k8s.io/yaml v1.4.0
3739
)
3840

@@ -279,7 +281,6 @@ require (
279281
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect
280282
google.golang.org/grpc v1.71.0 // indirect
281283
google.golang.org/protobuf v1.36.6 // indirect
282-
gopkg.in/yaml.v3 v3.0.1 // indirect
283284
gotest.tools/gotestsum v1.12.2 // indirect
284285
honnef.co/go/tools v0.6.1 // indirect
285286
mvdan.cc/gofumpt v0.7.0 // indirect

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,8 @@ github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9
121121
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
122122
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
123123
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
124-
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
125-
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
124+
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
125+
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
126126
github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs=
127127
github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88=
128128
github.com/daixiang0/gci v0.13.5 h1:kThgmH1yBmZSBCh1EJVxQ7JsHpm5Oms0AMed/0LaH4c=

pkg/docker/docker_command.go

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package docker
22

33
import (
4+
"bytes"
45
"context"
56
"encoding/json"
67
"errors"
@@ -12,6 +13,7 @@ import (
1213
"runtime"
1314
"strings"
1415

16+
"github.com/creack/pty"
1517
"github.com/docker/cli/cli/config"
1618
"github.com/docker/cli/cli/config/configfile"
1719
"github.com/docker/cli/cli/config/types"
@@ -53,7 +55,7 @@ func (c *DockerCommand) Pull(ctx context.Context, image string, force bool) (*im
5355
"--platform", "linux/amd64",
5456
}
5557

56-
err := c.exec(ctx, nil, nil, "", args)
58+
err := c.exec(ctx, nil, nil, nil, "", args)
5759
if err != nil {
5860
// A "not found" error message will be different depending on what flavor of engine and
5961
// registry version we're hitting. This checks for both docker and OCI lingo.
@@ -74,7 +76,7 @@ func (c *DockerCommand) Pull(ctx context.Context, image string, force bool) (*im
7476
func (c *DockerCommand) Push(ctx context.Context, image string) error {
7577
console.Debugf("=== DockerCommand.Push %s", image)
7678

77-
return c.exec(ctx, nil, nil, "", []string{"push", image})
79+
return c.exec(ctx, nil, nil, nil, "", []string{"push", image})
7880
}
7981

8082
func (c *DockerCommand) LoadUserInformation(ctx context.Context, registryHost string) (*command.UserInfo, error) {
@@ -118,7 +120,7 @@ func (c *DockerCommand) CreateTarFile(ctx context.Context, image string, tmpDir
118120
"/",
119121
folder,
120122
}
121-
if err := c.exec(ctx, nil, nil, "", args); err != nil {
123+
if err := c.exec(ctx, nil, nil, nil, "", args); err != nil {
122124
return "", err
123125
}
124126
return filepath.Join(tmpDir, tarFile), nil
@@ -143,7 +145,7 @@ func (c *DockerCommand) CreateAptTarFile(ctx context.Context, tmpDir string, apt
143145
"/buildtmp/" + aptTarFile,
144146
}
145147
args = append(args, packages...)
146-
if err := c.exec(ctx, nil, nil, "", args); err != nil {
148+
if err := c.exec(ctx, nil, nil, nil, "", args); err != nil {
147149
return "", err
148150
}
149151

@@ -204,7 +206,7 @@ func (c *DockerCommand) ContainerLogs(ctx context.Context, containerID string, w
204206
"--follow",
205207
}
206208

207-
return c.exec(ctx, nil, w, "", args)
209+
return c.exec(ctx, nil, w, nil, "", args)
208210
}
209211

210212
func (c *DockerCommand) ContainerInspect(ctx context.Context, id string) (*container.InspectResponse, error) {
@@ -241,11 +243,11 @@ func (c *DockerCommand) ContainerStop(ctx context.Context, containerID string) e
241243
args := []string{
242244
"container",
243245
"stop",
244-
"--time", "3",
246+
"--timeout", "3",
245247
containerID,
246248
}
247249

248-
if err := c.exec(ctx, nil, nil, "", args); err != nil {
250+
if err := c.exec(ctx, nil, nil, nil, "", args); err != nil {
249251
if strings.Contains(err.Error(), "No such container") {
250252
err = &command.NotFoundError{Object: "container", Ref: containerID}
251253
}
@@ -330,44 +332,82 @@ func (c *DockerCommand) ImageBuild(ctx context.Context, options command.ImageBui
330332

331333
in := strings.NewReader(options.DockerfileContents)
332334

333-
return c.exec(ctx, in, nil, options.WorkingDir, args)
335+
return c.exec(ctx, in, nil, nil, options.WorkingDir, args)
334336
}
335337

336-
func (c *DockerCommand) exec(ctx context.Context, in io.Reader, out io.Writer, dir string, args []string) error {
338+
func (c *DockerCommand) exec(ctx context.Context, in io.Reader, outw, errw io.Writer, dir string, args []string) error {
339+
if outw == nil {
340+
outw = os.Stderr
341+
}
342+
if errw == nil {
343+
errw = os.Stderr
344+
}
345+
337346
dockerCmd := DockerCommandFromEnvironment()
338347
cmd := exec.CommandContext(ctx, dockerCmd, args...)
348+
if dir != "" {
349+
cmd.Dir = dir
350+
}
351+
352+
// setup stderr buffer & writer to errw and buffer
353+
var stderrBuf bytes.Buffer
354+
355+
// if errw is a TTY, use a pty for stderr output so that the child process will properly detect an interactive console
356+
if f, ok := errw.(*os.File); ok && console.IsTTY(f) {
357+
stderrpty, stderrtty, err := pty.Open()
358+
if err != nil {
359+
return fmt.Errorf("failed to open stderr pty: %w", err)
360+
}
361+
cmd.Stderr = stderrtty
362+
363+
go func() {
364+
defer stderrpty.Close()
365+
defer stderrtty.Close()
366+
367+
_, err = io.Copy(io.MultiWriter(
368+
errw,
369+
util.NewRingBufferWriter(&stderrBuf, 1024),
370+
), stderrpty)
371+
if err != nil {
372+
console.Errorf("failed to copy stderr pty to errw: %s", err)
373+
}
374+
}()
375+
} else {
376+
cmd.Stderr = io.MultiWriter(errw, util.NewRingBufferWriter(&stderrBuf, 1024))
377+
}
339378

340-
if out == nil {
341-
out = os.Stderr
379+
// setup stdout pipe
380+
outpipe, err := cmd.StdoutPipe()
381+
if err != nil {
382+
return fmt.Errorf("failed to create stdout pipe: %w", err)
342383
}
384+
// copy stdout to outw
385+
go func() {
386+
defer outpipe.Close()
343387

344-
// the ring buffer captures the last N bytes written to `w` so we have some context to return in an error
345-
errbuf := util.NewRingBufferWriter(out, 1024)
346-
cmd.Stdout = errbuf
347-
cmd.Stderr = errbuf
388+
_, err = io.Copy(outw, outpipe)
389+
if err != nil {
390+
console.Errorf("failed to copy stdout to outw: %s", err)
391+
}
392+
}()
348393

349394
if in != nil {
350395
cmd.Stdin = in
351396
}
352397

353-
if dir != "" {
354-
cmd.Dir = dir
355-
}
356-
357398
console.Debug("$ " + strings.Join(cmd.Args, " "))
358-
err := cmd.Run()
359-
if err != nil {
399+
if err := cmd.Run(); err != nil {
360400
if errors.Is(err, context.Canceled) {
361401
return err
362402
}
363-
return fmt.Errorf("command failed: %s: %w", errbuf.String(), err)
403+
return fmt.Errorf("command failed: %s: %w", stderrBuf.String(), err)
364404
}
365405
return nil
366406
}
367407

368408
func (c *DockerCommand) execCaptured(ctx context.Context, in io.Reader, dir string, args []string) (string, error) {
369409
var out strings.Builder
370-
err := c.exec(ctx, in, &out, dir, args)
410+
err := c.exec(ctx, in, &out, nil, dir, args)
371411
if err != nil {
372412
return "", err
373413
}

pkg/image/build.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ func Build(ctx context.Context, cfg *config.Config, dir, imageName string, secre
271271
labels[key] = val
272272
}
273273

274-
if err := BuildAddLabelsAndSchemaToImage(ctx, dockerCommand, imageName, labels, bundledSchemaFile); err != nil {
274+
if err := BuildAddLabelsAndSchemaToImage(ctx, dockerCommand, imageName, labels, bundledSchemaFile, progressOutput); err != nil {
275275
return fmt.Errorf("Failed to add labels to image: %w", err)
276276
}
277277
return nil
@@ -280,14 +280,15 @@ func Build(ctx context.Context, cfg *config.Config, dir, imageName string, secre
280280
// BuildAddLabelsAndSchemaToImage builds a cog model with labels and schema.
281281
//
282282
// The new image is based on the provided image with the labels and schema file appended to it.
283-
func BuildAddLabelsAndSchemaToImage(ctx context.Context, dockerClient command.Command, image string, labels map[string]string, bundledSchemaFile string) error {
283+
func BuildAddLabelsAndSchemaToImage(ctx context.Context, dockerClient command.Command, image string, labels map[string]string, bundledSchemaFile string, progressOutput string) error {
284284
dockerfile := "FROM " + image + "\n"
285285
dockerfile += "COPY " + bundledSchemaFile + " .cog\n"
286286

287287
buildOpts := command.ImageBuildOptions{
288288
DockerfileContents: dockerfile,
289289
ImageName: image,
290290
Labels: labels,
291+
ProgressOutput: progressOutput,
291292
}
292293

293294
if err := dockerClient.ImageBuild(ctx, buildOpts); err != nil {

0 commit comments

Comments
 (0)