@@ -15,6 +15,7 @@ import (
1515 "github.com/docker/buildx/controller"
1616 cbuild "github.com/docker/buildx/controller/build"
1717 "github.com/docker/buildx/controller/control"
18+ controllererrors "github.com/docker/buildx/controller/errdefs"
1819 controllerapi "github.com/docker/buildx/controller/pb"
1920 "github.com/docker/buildx/monitor"
2021 "github.com/docker/buildx/store"
@@ -345,29 +346,39 @@ func launchControllerAndRunBuild(dockerCli command.Cli, options buildOptions) er
345346 }
346347 }()
347348
349+ // Start build
350+ var ref string
351+ var retErr error
348352 f := ioset .NewSingleForwarder ()
349- pr , pw := io .Pipe ()
350- f .SetWriter (pw , func () io.WriteCloser {
351- pw .Close () // propagate EOF
352- logrus .Debug ("propagating stdin close" )
353- return nil
354- })
355353 f .SetReader (os .Stdin )
356-
357- // Start build
358- ref , err := c .Build (ctx , options .BuildOptions , pr , os .Stdout , os .Stderr , options .progress )
359- if err != nil {
360- return errors .Wrapf (err , "failed to build" ) // TODO: allow invoke even on error
361- }
362- if err := pw .Close (); err != nil {
363- logrus .Debug ("failed to close stdin pipe writer" )
364- }
365- if err := pr .Close (); err != nil {
366- logrus .Debug ("failed to close stdin pipe reader" )
354+ if options .invoke != "debug-shell" {
355+ pr , pw := io .Pipe ()
356+ f .SetWriter (pw , func () io.WriteCloser {
357+ pw .Close () // propagate EOF
358+ logrus .Debug ("propagating stdin close" )
359+ return nil
360+ })
361+ ref , err = c .Build (ctx , options .BuildOptions , pr , os .Stdout , os .Stderr , options .progress )
362+ if err != nil {
363+ var be * controllererrors.BuildError
364+ if errors .As (err , & be ) {
365+ ref = be .Ref
366+ retErr = err
367+ // We can proceed to monitor
368+ } else {
369+ return errors .Wrapf (err , "failed to build" )
370+ }
371+ }
372+ if err := pw .Close (); err != nil {
373+ logrus .Debug ("failed to close stdin pipe writer" )
374+ }
375+ if err := pr .Close (); err != nil {
376+ logrus .Debug ("failed to close stdin pipe reader" )
377+ }
367378 }
368379
369380 // post-build operations
370- if options .invoke != "" {
381+ if needsMonitor ( options .invoke , retErr ) {
371382 pr2 , pw2 := io .Pipe ()
372383 f .SetWriter (pw2 , func () io.WriteCloser {
373384 pw2 .Close () // propagate EOF
@@ -380,7 +391,7 @@ func launchControllerAndRunBuild(dockerCli command.Cli, options buildOptions) er
380391 }
381392 return errors .Errorf ("failed to configure terminal: %v" , err )
382393 }
383- err = monitor .RunMonitor (ctx , ref , options .BuildOptions , invokeConfig , c , options .progress , pr2 , os .Stdout , os .Stderr )
394+ err = monitor .RunMonitor (ctx , ref , & options .BuildOptions , invokeConfig , c , options .progress , pr2 , os .Stdout , os .Stderr )
384395 con .Reset ()
385396 if err := pw2 .Close (); err != nil {
386397 logrus .Debug ("failed to close monitor stdin pipe reader" )
@@ -400,9 +411,26 @@ func launchControllerAndRunBuild(dockerCli command.Cli, options buildOptions) er
400411 return nil
401412}
402413
414+ func needsMonitor (invokeFlag string , retErr error ) bool {
415+ switch invokeFlag {
416+ case "debug-shell" :
417+ return true
418+ case "on-error" :
419+ return retErr != nil
420+ default :
421+ return invokeFlag != ""
422+ }
423+ }
424+
403425func parseInvokeConfig (invoke string ) (cfg controllerapi.ContainerConfig , err error ) {
404426 cfg .Tty = true
405- if invoke == "default" {
427+ switch invoke {
428+ case "default" , "debug-shell" :
429+ return cfg , nil
430+ case "on-error" :
431+ // NOTE: we overwrite the command to run because the original one should fail on the failed step.
432+ // TODO: make this configurable.
433+ cfg .Cmd = []string {"/bin/sh" }
406434 return cfg , nil
407435 }
408436
@@ -521,3 +549,49 @@ func (s *shmSize) Set(v string) error {
521549func (s * shmSize ) Type () string {
522550 return s .org .Type ()
523551}
552+
553+ func addDebugShellCommand (cmd * cobra.Command , dockerCli command.Cli ) {
554+ cmd .AddCommand (
555+ debugShellCmd (dockerCli ),
556+ )
557+ }
558+
559+ func debugShellCmd (dockerCli command.Cli ) * cobra.Command {
560+ var options control.ControlOptions
561+ var progress string
562+
563+ cmd := & cobra.Command {
564+ Use : "debug-shell" ,
565+ Short : "Start a monitor" ,
566+ RunE : func (cmd * cobra.Command , args []string ) error {
567+ ctx := context .TODO ()
568+ c , err := controller .NewController (ctx , options , dockerCli )
569+ if err != nil {
570+ return err
571+ }
572+ defer func () {
573+ if err := c .Close (); err != nil {
574+ logrus .Warnf ("failed to close server connection %v" , err )
575+ }
576+ }()
577+ con := console .Current ()
578+ if err := con .SetRaw (); err != nil {
579+ return errors .Errorf ("failed to configure terminal: %v" , err )
580+ }
581+ err = monitor .RunMonitor (ctx , "" , nil , controllerapi.ContainerConfig {
582+ Tty : true ,
583+ }, c , progress , os .Stdin , os .Stdout , os .Stderr )
584+ con .Reset ()
585+ return err
586+ },
587+ }
588+
589+ flags := cmd .Flags ()
590+
591+ flags .StringVar (& options .Root , "root" , "" , "Specify root directory of server to connect [experimental]" )
592+ flags .BoolVar (& options .Detach , "detach" , runtime .GOOS == "linux" , "Detach buildx server (supported only on linux) [experimental]" )
593+ flags .StringVar (& options .ServerConfig , "server-config" , "" , "Specify buildx server config file (used only when launching new server) [experimental]" )
594+ flags .StringVar (& progress , "progress" , "auto" , `Set type of progress output ("auto", "plain", "tty"). Use plain to show container output` )
595+
596+ return cmd
597+ }
0 commit comments