Skip to content

Commit

Permalink
monitor: breakpoint debugger on terminal and on IDEs (via DAP)
Browse files Browse the repository at this point in the history
Signed-off-by: Kohei Tokunaga <ktokunaga.mail@gmail.com>
  • Loading branch information
ktock committed Mar 1, 2023
1 parent f18bd40 commit 35cab95
Show file tree
Hide file tree
Showing 242 changed files with 26,318 additions and 2,945 deletions.
61 changes: 59 additions & 2 deletions build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,12 @@ type ContainerConfig struct {
Cwd *string

Initial bool

SignalCh <-chan syscall.Signal
ResizeCh <-chan gateway.WinSize

Image string
ResultMountPath string
}

// ResultContext is a build result with the client that built it.
Expand Down Expand Up @@ -748,6 +754,29 @@ func Invoke(ctx context.Context, cfg ContainerConfig) error {
containerCfg, processCfg = *ccfg, *pcfg
}

if img := cfg.Image; img != "" {
def, err := llb.Image(img).Marshal(ctx)
if err != nil {
return nil, err
}
r, err := c.Solve(ctx, gateway.SolveRequest{
Definition: def.ToPB(),
})
if err != nil {
return nil, err
}
for i := range containerCfg.Mounts {
containerCfg.Mounts[i].Dest = filepath.Join(cfg.ResultMountPath, containerCfg.Mounts[i].Dest)
}
containerCfg.Mounts = append([]gateway.Mount{
{
Dest: "/",
MountType: pb.MountType_BIND,
Ref: r.Ref,
},
}, containerCfg.Mounts...)
}

ctr, err := c.NewContainer(ctx, containerCfg)
if err != nil {
return nil, err
Expand All @@ -758,8 +787,34 @@ func Invoke(ctx context.Context, cfg ContainerConfig) error {
if err != nil {
return nil, errors.Errorf("failed to start container: %v", err)
}
signalCh := cfg.SignalCh
if signalCh == nil {
signalCh = make(chan syscall.Signal)
}
resizeCh := cfg.ResizeCh
if resizeCh == nil {
resizeCh = make(chan gateway.WinSize)
}
errCh := make(chan error)
doneCh := make(chan struct{})
go func() {
for {
select {
case s := <-signalCh:
if err := proc.Signal(ctx, s); err != nil {
logrus.Warnf("failed to send signal %v %v", s, err)
}
case w := <-resizeCh:
if err := proc.Resize(ctx, w); err != nil {
logrus.Warnf("failed to resize %v: %v", w, err)
}
case <-ctx.Done():
return
case <-doneCh:
return
}
}
}()
go func() {
defer close(doneCh)
if err := proc.Wait(); err != nil {
Expand Down Expand Up @@ -1016,7 +1071,7 @@ func Build(ctx context.Context, nodes []builder.Node, opt map[string]Options, do
return BuildWithResultHandler(ctx, nodes, opt, docker, configDir, w, nil)
}

func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[string]Options, docker *dockerutil.Client, configDir string, w progress.Writer, resultHandleFunc func(driverIndex int, rCtx *ResultContext)) (resp map[string]*client.SolveResponse, err error) {
func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[string]Options, docker *dockerutil.Client, configDir string, w progress.Writer, resultHandleFunc func(driverIndex int, rCtx *ResultContext) error) (resp map[string]*client.SolveResponse, err error) {
if len(nodes) == 0 {
return nil, errors.Errorf("driver required for build")
}
Expand Down Expand Up @@ -1279,7 +1334,9 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
}
results.Set(resultKey(dp.driverIndex, k), res)
if resultHandleFunc != nil {
resultHandleFunc(dp.driverIndex, &ResultContext{Client: cc, Res: res})
if err := resultHandleFunc(dp.driverIndex, &ResultContext{Client: cc, Res: res}); err != nil {
return nil, err
}
}
return res, nil
}
Expand Down
3 changes: 2 additions & 1 deletion cmd/buildx/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"fmt"
"os"

"github.com/containerd/containerd/pkg/seed"
"github.com/containerd/containerd/pkg/seed" //nolint:staticcheck // Global math/rand seed is deprecated, but still used by external dependencies
"github.com/docker/buildx/commands"
"github.com/docker/buildx/version"
"github.com/docker/cli/cli"
Expand All @@ -28,6 +28,7 @@ import (
)

func init() {
//nolint:staticcheck // Global math/rand seed is deprecated, but still used by external dependencies
seed.WithTimeAndRand()
stack.SetVersionInfo(version.Version, version.Revision)
}
Expand Down
21 changes: 15 additions & 6 deletions commands/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/docker/docker/pkg/ioutils"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/exporter/containerimage/exptypes"
solverpb "github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/util/appcontext"
"github.com/moby/buildkit/util/grpcerrors"
"github.com/pkg/errors"
Expand Down Expand Up @@ -500,6 +501,7 @@ func launchControllerAndRunBuild(dockerCli command.Cli, options buildOptions) er
}

var ref string
var def *solverpb.Definition
var retErr error
f := ioset.NewSingleForwarder()
f.SetReader(os.Stdin)
Expand All @@ -518,12 +520,20 @@ func launchControllerAndRunBuild(dockerCli command.Cli, options buildOptions) er
}
}

if options.invoke == "debug-step" {
// Special mode where we don't get the result but get only the build definition.
// In this mode, Build() doesn't perform the build therefore always fails.
// The error returned by Build() contains *pb.Definition wrapped with *controllererror.BuildError.
opts.Debug = true
}

var resp *client.SolveResponse
ref, resp, err = c.Build(ctx, opts, pr, os.Stdout, os.Stderr, progress)
ref, resp, def, 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
def = be.Definition
retErr = err
// We can proceed to monitor
} else {
Expand Down Expand Up @@ -564,7 +574,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, progress, pr2, os.Stdout, os.Stderr)
err = monitor.RunMonitor(ctx, ref, def, &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 @@ -577,12 +587,12 @@ func launchControllerAndRunBuild(dockerCli command.Cli, options buildOptions) er
logrus.Warnf("disconnect error: %v", err)
}
}
return nil
return retErr
}

func needsMonitor(invokeFlag string, retErr error) bool {
switch invokeFlag {
case "debug-shell":
case "debug-shell", "debug-step":
return true
case "on-error":
return retErr != nil
Expand All @@ -596,8 +606,7 @@ func parseInvokeConfig(invoke string) (cfg controllerapi.ContainerConfig, err er
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.
case "on-error", "debug-step":
// TODO: make this configurable.
cfg.Cmd = []string{"/bin/sh"}
return cfg, nil
Expand Down
2 changes: 1 addition & 1 deletion commands/debug-shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func debugShellCmd(dockerCli command.Cli) *cobra.Command {
if err := con.SetRaw(); err != nil {
return errors.Errorf("failed to configure terminal: %v", err)
}
err = monitor.RunMonitor(ctx, "", nil, controllerapi.ContainerConfig{
err = monitor.RunMonitor(ctx, "", nil, nil, controllerapi.ContainerConfig{
Tty: true,
}, c, progress, os.Stdin, os.Stdout, os.Stderr)
con.Reset()
Expand Down
2 changes: 2 additions & 0 deletions commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

imagetoolscmd "github.com/docker/buildx/commands/imagetools"
"github.com/docker/buildx/controller/remote"
"github.com/docker/buildx/monitor/dap"
"github.com/docker/buildx/util/logutil"
"github.com/docker/cli-docs-tool/annotation"
"github.com/docker/cli/cli"
Expand Down Expand Up @@ -91,6 +92,7 @@ func addCommands(cmd *cobra.Command, dockerCli command.Cli) {
remote.AddControllerCommands(cmd, dockerCli)
addDebugShellCommand(cmd, dockerCli)
}
dap.AddDAPCommands(cmd, dockerCli) // hidden command; we need it for emacs DAP support
}

func rootFlags(options *rootOptions, flags *pflag.FlagSet) {
Expand Down
13 changes: 9 additions & 4 deletions controller/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,15 +175,15 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in controllerapi.Build
return nil, nil, err
}

resp, res, err := buildTargets(ctx, dockerCli, b.NodeGroup, nodes, map[string]build.Options{defaultTargetName: opts}, progressMode, in.Opts.MetadataFile, statusChan)
resp, res, err := buildTargets(ctx, dockerCli, b.NodeGroup, nodes, map[string]build.Options{defaultTargetName: opts}, progressMode, in.Opts.MetadataFile, statusChan, in.Debug)
err = wrapBuildError(err, false)
if err != nil {
return nil, nil, err
}
return resp, res, nil
}

func buildTargets(ctx context.Context, dockerCli command.Cli, ng *store.NodeGroup, nodes []builder.Node, opts map[string]build.Options, progressMode string, metadataFile string, statusChan chan *client.SolveStatus) (*client.SolveResponse, *build.ResultContext, error) {
func buildTargets(ctx context.Context, dockerCli command.Cli, ng *store.NodeGroup, nodes []builder.Node, opts map[string]build.Options, progressMode string, metadataFile string, statusChan chan *client.SolveStatus, debug bool) (*client.SolveResponse, *build.ResultContext, error) {
ctx2, cancel := context.WithCancel(context.TODO())
defer cancel()

Expand All @@ -198,12 +198,16 @@ func buildTargets(ctx context.Context, dockerCli command.Cli, ng *store.NodeGrou
var res *build.ResultContext
var mu sync.Mutex
var idx int
resp, err := build.BuildWithResultHandler(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), progress.Tee(printer, statusChan), func(driverIndex int, gotRes *build.ResultContext) {
resp, err := build.BuildWithResultHandler(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), progress.Tee(printer, statusChan), func(driverIndex int, gotRes *build.ResultContext) error {
mu.Lock()
defer mu.Unlock()
if res == nil || driverIndex < idx {
idx, res = driverIndex, gotRes
}
if debug {
return errors.Errorf("debug mode")
}
return nil
})
err1 := printer.Wait()
if err == nil {
Expand Down Expand Up @@ -386,6 +390,7 @@ func controllerUlimitOpt2DockerUlimit(u *controllerapi.UlimitOpt) *dockeropts.Ul

type ResultContextError struct {
ResultContext *build.ResultContext
Definition *solverpb.Definition
error
}

Expand All @@ -408,7 +413,7 @@ func wrapResultContext(wErr error, res *build.ResultContext) error {
return wErr
}
res.Done()
return &ResultContextError{ResultContext: res2, error: wErr}
return &ResultContextError{ResultContext: res2, Definition: def, error: wErr}
}

func DefinitionFromResultContext(ctx context.Context, res *build.ResultContext) (*solverpb.Definition, error) {
Expand Down
12 changes: 10 additions & 2 deletions controller/control/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,32 @@ package control
import (
"context"
"io"
"syscall"

"github.com/containerd/console"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/moby/buildkit/client"
solverpb "github.com/moby/buildkit/solver/pb"
)

type BuildxController interface {
Build(ctx context.Context, options controllerapi.BuildOptions, in io.ReadCloser, w io.Writer, out console.File, progressMode string) (ref string, resp *client.SolveResponse, err error)
Invoke(ctx context.Context, ref string, options controllerapi.ContainerConfig, ioIn io.ReadCloser, ioOut io.WriteCloser, ioErr io.WriteCloser) (err error)
Invoke(ctx context.Context, ref string, options controllerapi.ContainerConfig, ioIn io.ReadCloser, ioOut io.WriteCloser, ioErr io.WriteCloser, signalCh <-chan syscall.Signal, resizeCh <-chan WinSize) error
Build(ctx context.Context, options controllerapi.BuildOptions, in io.ReadCloser, w io.Writer, out console.File, progressMode string) (ref string, resp *client.SolveResponse, def *solverpb.Definition, err error)
Kill(ctx context.Context) error
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)
Continue(ctx context.Context, ref string, def *solverpb.Definition, w io.Writer, out console.File, progressMode string) error
}

type ControlOptions struct {
ServerConfig string
Root string
Detach bool
}

type WinSize struct {
Rows uint32
Cols uint32
}
7 changes: 4 additions & 3 deletions controller/errdefs/build.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package errdefs

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

Expand All @@ -22,11 +23,11 @@ func (e *BuildError) ToProto() grpcerrors.TypedErrorProto {
return &e.Build
}

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

func (b *Build) WrapError(err error) error {
Expand Down
28 changes: 19 additions & 9 deletions controller/errdefs/errdefs.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions controller/errdefs/errdefs.proto
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ import "github.com/moby/buildkit/solver/pb/ops.proto";

message Build {
string Ref = 1;
pb.Definition Definition = 2;
}

0 comments on commit 35cab95

Please sign in to comment.