Skip to content

Commit 3328ff8

Browse files
committed
monitor: add debug-shell and on-error
Signed-off-by: Kohei Tokunaga <ktokunaga.mail@gmail.com>
1 parent aa05f4c commit 3328ff8

File tree

19 files changed

+962
-244
lines changed

19 files changed

+962
-244
lines changed

build/build.go

Lines changed: 290 additions & 83 deletions
Large diffs are not rendered by default.

commands/build.go

Lines changed: 94 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
403425
func 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 {
521549
func (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+
}

commands/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ func addCommands(cmd *cobra.Command, dockerCli command.Cli) {
8989
)
9090
if isExperimental() {
9191
remote.AddControllerCommands(cmd, dockerCli)
92+
addDebugShellCommand(cmd, dockerCli)
9293
}
9394
}
9495

controller/build/build.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"github.com/moby/buildkit/client"
3333
"github.com/moby/buildkit/session/auth/authprovider"
3434
"github.com/moby/buildkit/solver/errdefs"
35+
solverpb "github.com/moby/buildkit/solver/pb"
3536
"github.com/moby/buildkit/util/grpcerrors"
3637
"github.com/moby/buildkit/util/progress/progressui"
3738
"github.com/morikuni/aec"
@@ -239,6 +240,9 @@ func buildTargets(ctx context.Context, dockerCli command.Cli, ng *store.NodeGrou
239240
err = err1
240241
}
241242
if err != nil {
243+
if res != nil {
244+
err = wrapResultContext(err, res)
245+
}
242246
return "", nil, err
243247
}
244248

@@ -449,3 +453,49 @@ func controllerUlimitOpt2DockerUlimit(u *controllerapi.UlimitOpt) *dockeropts.Ul
449453
}
450454
return dockeropts.NewUlimitOpt(&values)
451455
}
456+
457+
type ResultContextError struct {
458+
ResultContext *build.ResultContext
459+
error
460+
}
461+
462+
func (e *ResultContextError) Unwrap() error {
463+
return e.error
464+
}
465+
466+
func wrapResultContext(wErr error, res *build.ResultContext) error {
467+
if wErr == nil {
468+
return nil
469+
}
470+
def, err := DefinitionFromResultContext(context.TODO(), res)
471+
if err != nil {
472+
logrus.Errorf("failed to get definition from result: %v", err)
473+
return wErr
474+
}
475+
res2, err := build.GetResultAt(context.TODO(), res, def, nil)
476+
if err != nil {
477+
logrus.Errorf("failed to get result: %v", err)
478+
return wErr
479+
}
480+
res.Done()
481+
return &ResultContextError{ResultContext: res2, error: wErr}
482+
}
483+
484+
func DefinitionFromResultContext(ctx context.Context, res *build.ResultContext) (*solverpb.Definition, error) {
485+
if res.Res == nil {
486+
return nil, errors.Errorf("result context doesn't contain build result")
487+
}
488+
ref, err := res.Res.SingleRef()
489+
if err != nil {
490+
return nil, err
491+
}
492+
st, err := ref.ToState()
493+
if err != nil {
494+
return nil, err
495+
}
496+
def, err := st.Marshal(ctx)
497+
if err != nil {
498+
return nil, err
499+
}
500+
return def.ToPB(), nil
501+
}

controller/control/controller.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type BuildxController interface {
1515
Close() error
1616
List(ctx context.Context) (res []string, _ error)
1717
Disconnect(ctx context.Context, ref string) error
18+
Inspect(ctx context.Context, ref string) (*controllerapi.InspectResponse, error)
1819
}
1920

2021
type ControlOptions struct {

controller/errdefs/build.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package errdefs
2+
3+
import (
4+
"github.com/containerd/typeurl"
5+
"github.com/moby/buildkit/util/grpcerrors"
6+
)
7+
8+
func init() {
9+
typeurl.Register((*Build)(nil), "github.com/docker/buildx", "errdefs.Build+json")
10+
}
11+
12+
type BuildError struct {
13+
Build
14+
error
15+
}
16+
17+
func (e *BuildError) Unwrap() error {
18+
return e.error
19+
}
20+
21+
func (e *BuildError) ToProto() grpcerrors.TypedErrorProto {
22+
return &e.Build
23+
}
24+
25+
func WrapBuild(err error, ref string) error {
26+
if err == nil {
27+
return nil
28+
}
29+
return &BuildError{Build: Build{Ref: ref}, error: err}
30+
}
31+
32+
func (b *Build) WrapError(err error) error {
33+
return &BuildError{error: err, Build: *b}
34+
}

controller/errdefs/errdefs.pb.go

Lines changed: 77 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

controller/errdefs/errdefs.proto

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
syntax = "proto3";
2+
3+
package errdefs;
4+
5+
import "github.com/moby/buildkit/solver/pb/ops.proto";
6+
7+
message Build {
8+
string Ref = 1;
9+
}

controller/errdefs/generate.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package errdefs
2+
3+
//go:generate protoc -I=. -I=../../vendor/ --gogo_out=plugins=grpc:. errdefs.proto

0 commit comments

Comments
 (0)