11package docker
22
33import (
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
7476func (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
8082func (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
210212func (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
368408func (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 }
0 commit comments