Skip to content

Commit

Permalink
monitor: add debug-shell and on-error
Browse files Browse the repository at this point in the history
Signed-off-by: Kohei Tokunaga <[email protected]>
  • Loading branch information
ktock committed Feb 24, 2023
1 parent f3a4cd5 commit f2f69fb
Show file tree
Hide file tree
Showing 20 changed files with 1,016 additions and 274 deletions.
373 changes: 290 additions & 83 deletions build/build.go

Large diffs are not rendered by default.

103 changes: 67 additions & 36 deletions commands/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/docker/buildx/controller"
cbuild "github.com/docker/buildx/controller/build"
"github.com/docker/buildx/controller/control"
controllererrors "github.com/docker/buildx/controller/errdefs"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/docker/buildx/monitor"
"github.com/docker/buildx/store"
Expand Down Expand Up @@ -488,15 +489,7 @@ func launchControllerAndRunBuild(dockerCli command.Cli, options buildOptions) er
}
}()

f := ioset.NewSingleForwarder()
pr, pw := io.Pipe()
f.SetWriter(pw, func() io.WriteCloser {
pw.Close() // propagate EOF
logrus.Debug("propagating stdin close")
return nil
})
f.SetReader(os.Stdin)

// Start build
opts, err := options.toControllerOptions()
if err != nil {
return err
Expand All @@ -506,38 +499,59 @@ func launchControllerAndRunBuild(dockerCli command.Cli, options buildOptions) er
return err
}

// Avoid leaving a stale file if we eventually fail
if options.imageIDFile != "" {
if err := os.Remove(options.imageIDFile); err != nil && !os.IsNotExist(err) {
return errors.Wrap(err, "removing image ID file")
var ref string
var retErr error
f := ioset.NewSingleForwarder()
f.SetReader(os.Stdin)
if options.invoke != "debug-shell" {
pr, pw := io.Pipe()
f.SetWriter(pw, func() io.WriteCloser {
pw.Close() // propagate EOF
logrus.Debug("propagating stdin close")
return nil
})

// Avoid leaving a stale file if we eventually fail
if options.imageIDFile != "" {
if err := os.Remove(options.imageIDFile); err != nil && !os.IsNotExist(err) {
return errors.Wrap(err, "removing image ID file")
}
}
}

// Start build
ref, resp, err := c.Build(ctx, opts, pr, os.Stdout, os.Stderr, progress)
if err != nil {
return errors.Wrapf(err, "failed to build") // TODO: allow invoke even on error
}
if err := pw.Close(); err != nil {
logrus.Debug("failed to close stdin pipe writer")
}
if err := pr.Close(); err != nil {
logrus.Debug("failed to close stdin pipe reader")
}
var resp *client.SolveResponse
ref, resp, err = c.Build(ctx, opts, pr, os.Stdout, os.Stderr, progress)
if err != nil {
var be *controllererrors.BuildError
if errors.As(err, &be) {
ref = be.Ref
retErr = err
// We can proceed to monitor
} else {
return errors.Wrapf(err, "failed to build")
}
}
if err := pw.Close(); err != nil {
logrus.Debug("failed to close stdin pipe writer")
}
if err := pr.Close(); err != nil {
logrus.Debug("failed to close stdin pipe reader")
}

if options.quiet {
fmt.Println(resp.ExporterResponse[exptypes.ExporterImageDigestKey])
}
if options.imageIDFile != "" {
dgst := resp.ExporterResponse[exptypes.ExporterImageDigestKey]
if v, ok := resp.ExporterResponse[exptypes.ExporterImageConfigDigestKey]; ok {
dgst = v
if options.quiet {
fmt.Println(resp.ExporterResponse[exptypes.ExporterImageDigestKey])
}
if options.imageIDFile != "" {
dgst := resp.ExporterResponse[exptypes.ExporterImageDigestKey]
if v, ok := resp.ExporterResponse[exptypes.ExporterImageConfigDigestKey]; ok {
dgst = v
}
return os.WriteFile(options.imageIDFile, []byte(dgst), 0644)
}
return os.WriteFile(options.imageIDFile, []byte(dgst), 0644)

}

// post-build operations
if options.invoke != "" {
if needsMonitor(options.invoke, retErr) {
pr2, pw2 := io.Pipe()
f.SetWriter(pw2, func() io.WriteCloser {
pw2.Close() // propagate EOF
Expand All @@ -550,7 +564,7 @@ func launchControllerAndRunBuild(dockerCli command.Cli, options buildOptions) er
}
return errors.Errorf("failed to configure terminal: %v", err)
}
err = monitor.RunMonitor(ctx, ref, opts, invokeConfig, c, options.progress, pr2, os.Stdout, os.Stderr)
err = monitor.RunMonitor(ctx, ref, &opts, invokeConfig, c, progress, pr2, os.Stdout, os.Stderr)
con.Reset()
if err := pw2.Close(); err != nil {
logrus.Debug("failed to close monitor stdin pipe reader")
Expand All @@ -566,9 +580,26 @@ func launchControllerAndRunBuild(dockerCli command.Cli, options buildOptions) er
return nil
}

func needsMonitor(invokeFlag string, retErr error) bool {
switch invokeFlag {
case "debug-shell":
return true
case "on-error":
return retErr != nil
default:
return invokeFlag != ""
}
}

func parseInvokeConfig(invoke string) (cfg controllerapi.ContainerConfig, err error) {
cfg.Tty = true
if invoke == "default" {
switch invoke {
case "default", "debug-shell":
return cfg, nil
case "on-error":
// NOTE: we overwrite the command to run because the original one should fail on the failed step.
// TODO: make this configurable.
cfg.Cmd = []string{"/bin/sh"}
return cfg, nil
}

Expand Down
63 changes: 63 additions & 0 deletions commands/debug-shell.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package commands

import (
"context"
"os"
"runtime"

"github.com/containerd/console"
"github.com/docker/buildx/controller"
"github.com/docker/buildx/controller/control"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/docker/buildx/monitor"
"github.com/docker/cli/cli/command"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

func debugShellCmd(dockerCli command.Cli) *cobra.Command {
var options control.ControlOptions
var progress string

cmd := &cobra.Command{
Use: "debug-shell",
Short: "Start a monitor",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.TODO()
c, err := controller.NewController(ctx, options, dockerCli)
if err != nil {
return err
}
defer func() {
if err := c.Close(); err != nil {
logrus.Warnf("failed to close server connection %v", err)
}
}()
con := console.Current()
if err := con.SetRaw(); err != nil {
return errors.Errorf("failed to configure terminal: %v", err)
}
err = monitor.RunMonitor(ctx, "", nil, controllerapi.ContainerConfig{
Tty: true,
}, c, progress, os.Stdin, os.Stdout, os.Stderr)
con.Reset()
return err
},
}

flags := cmd.Flags()

flags.StringVar(&options.Root, "root", "", "Specify root directory of server to connect [experimental]")
flags.BoolVar(&options.Detach, "detach", runtime.GOOS == "linux", "Detach buildx server (supported only on linux) [experimental]")
flags.StringVar(&options.ServerConfig, "server-config", "", "Specify buildx server config file (used only when launching new server) [experimental]")
flags.StringVar(&progress, "progress", "auto", `Set type of progress output ("auto", "plain", "tty"). Use plain to show container output`)

return cmd
}

func addDebugShellCommand(cmd *cobra.Command, dockerCli command.Cli) {
cmd.AddCommand(
debugShellCmd(dockerCli),
)
}
1 change: 1 addition & 0 deletions commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ func addCommands(cmd *cobra.Command, dockerCli command.Cli) {
)
if isExperimental() {
remote.AddControllerCommands(cmd, dockerCli)
addDebugShellCommand(cmd, dockerCli)
}
}

Expand Down
50 changes: 50 additions & 0 deletions controller/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/session/auth/authprovider"
"github.com/moby/buildkit/solver/errdefs"
solverpb "github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/util/grpcerrors"
"github.com/moby/buildkit/util/progress/progressui"
"github.com/morikuni/aec"
Expand Down Expand Up @@ -209,6 +210,9 @@ func buildTargets(ctx context.Context, dockerCli command.Cli, ng *store.NodeGrou
err = err1
}
if err != nil {
if res != nil {
err = wrapResultContext(err, res)
}
return nil, nil, err
}

Expand Down Expand Up @@ -379,3 +383,49 @@ func controllerUlimitOpt2DockerUlimit(u *controllerapi.UlimitOpt) *dockeropts.Ul
}
return dockeropts.NewUlimitOpt(&values)
}

type ResultContextError struct {
ResultContext *build.ResultContext
error
}

func (e *ResultContextError) Unwrap() error {
return e.error
}

func wrapResultContext(wErr error, res *build.ResultContext) error {
if wErr == nil {
return nil
}
def, err := DefinitionFromResultContext(context.TODO(), res)
if err != nil {
logrus.Errorf("failed to get definition from result: %v", err)
return wErr
}
res2, err := build.GetResultAt(context.TODO(), res, def, nil)
if err != nil {
logrus.Errorf("failed to get result: %v", err)
return wErr
}
res.Done()
return &ResultContextError{ResultContext: res2, error: wErr}
}

func DefinitionFromResultContext(ctx context.Context, res *build.ResultContext) (*solverpb.Definition, error) {
if res.Res == nil {
return nil, errors.Errorf("result context doesn't contain build result")
}
ref, err := res.Res.SingleRef()
if err != nil {
return nil, err
}
st, err := ref.ToState()
if err != nil {
return nil, err
}
def, err := st.Marshal(ctx)
if err != nil {
return nil, err
}
return def.ToPB(), nil
}
1 change: 1 addition & 0 deletions controller/control/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type BuildxController interface {
Close() error
List(ctx context.Context) (refs []string, _ error)
Disconnect(ctx context.Context, ref string) error
Inspect(ctx context.Context, ref string) (*controllerapi.InspectResponse, error)
}

type ControlOptions struct {
Expand Down
34 changes: 34 additions & 0 deletions controller/errdefs/build.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package errdefs

import (
"github.com/containerd/typeurl"
"github.com/moby/buildkit/util/grpcerrors"
)

func init() {
typeurl.Register((*Build)(nil), "github.com/docker/buildx", "errdefs.Build+json")
}

type BuildError struct {
Build
error
}

func (e *BuildError) Unwrap() error {
return e.error
}

func (e *BuildError) ToProto() grpcerrors.TypedErrorProto {
return &e.Build
}

func WrapBuild(err error, ref string) error {
if err == nil {
return nil
}
return &BuildError{Build: Build{Ref: ref}, error: err}
}

func (b *Build) WrapError(err error) error {
return &BuildError{error: err, Build: *b}
}
Loading

0 comments on commit f2f69fb

Please sign in to comment.