Skip to content

Commit 2f1e4ad

Browse files
committed
monitor: breakpoint debugger on terminal and on IDEs (via DAP)
Signed-off-by: Kohei Tokunaga <[email protected]>
1 parent f18bd40 commit 2f1e4ad

File tree

242 files changed

+26330
-2944
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

242 files changed

+26330
-2944
lines changed

build/build.go

+59-2
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,12 @@ type ContainerConfig struct {
677677
Cwd *string
678678

679679
Initial bool
680+
681+
SignalCh <-chan syscall.Signal
682+
ResizeCh <-chan gateway.WinSize
683+
684+
Image string
685+
ResultMountPath string
680686
}
681687

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

757+
if img := cfg.Image; img != "" {
758+
def, err := llb.Image(img).Marshal(ctx)
759+
if err != nil {
760+
return nil, err
761+
}
762+
r, err := c.Solve(ctx, gateway.SolveRequest{
763+
Definition: def.ToPB(),
764+
})
765+
if err != nil {
766+
return nil, err
767+
}
768+
for i := range containerCfg.Mounts {
769+
containerCfg.Mounts[i].Dest = filepath.Join(cfg.ResultMountPath, containerCfg.Mounts[i].Dest)
770+
}
771+
containerCfg.Mounts = append([]gateway.Mount{
772+
{
773+
Dest: "/",
774+
MountType: pb.MountType_BIND,
775+
Ref: r.Ref,
776+
},
777+
}, containerCfg.Mounts...)
778+
}
779+
751780
ctr, err := c.NewContainer(ctx, containerCfg)
752781
if err != nil {
753782
return nil, err
@@ -758,8 +787,34 @@ func Invoke(ctx context.Context, cfg ContainerConfig) error {
758787
if err != nil {
759788
return nil, errors.Errorf("failed to start container: %v", err)
760789
}
790+
signalCh := cfg.SignalCh
791+
if signalCh == nil {
792+
signalCh = make(chan syscall.Signal)
793+
}
794+
resizeCh := cfg.ResizeCh
795+
if resizeCh == nil {
796+
resizeCh = make(chan gateway.WinSize)
797+
}
761798
errCh := make(chan error)
762799
doneCh := make(chan struct{})
800+
go func() {
801+
for {
802+
select {
803+
case s := <-signalCh:
804+
if err := proc.Signal(ctx, s); err != nil {
805+
logrus.Warnf("failed to send signal %v %v", s, err)
806+
}
807+
case w := <-resizeCh:
808+
if err := proc.Resize(ctx, w); err != nil {
809+
logrus.Warnf("failed to resize %v: %v", w, err)
810+
}
811+
case <-ctx.Done():
812+
return
813+
case <-doneCh:
814+
return
815+
}
816+
}
817+
}()
763818
go func() {
764819
defer close(doneCh)
765820
if err := proc.Wait(); err != nil {
@@ -1016,7 +1071,7 @@ func Build(ctx context.Context, nodes []builder.Node, opt map[string]Options, do
10161071
return BuildWithResultHandler(ctx, nodes, opt, docker, configDir, w, nil)
10171072
}
10181073

1019-
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) {
1074+
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) {
10201075
if len(nodes) == 0 {
10211076
return nil, errors.Errorf("driver required for build")
10221077
}
@@ -1279,7 +1334,9 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
12791334
}
12801335
results.Set(resultKey(dp.driverIndex, k), res)
12811336
if resultHandleFunc != nil {
1282-
resultHandleFunc(dp.driverIndex, &ResultContext{Client: cc, Res: res})
1337+
if err := resultHandleFunc(dp.driverIndex, &ResultContext{Client: cc, Res: res}); err != nil {
1338+
return nil, err
1339+
}
12831340
}
12841341
return res, nil
12851342
}

cmd/buildx/main.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"fmt"
55
"os"
66

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

3030
func init() {
31+
//nolint:staticcheck // Global math/rand seed is deprecated, but still used by external dependencies
3132
seed.WithTimeAndRand()
3233
stack.SetVersionInfo(version.Version, version.Revision)
3334
}

commands/build.go

+15-6
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"github.com/docker/docker/pkg/ioutils"
3333
"github.com/moby/buildkit/client"
3434
"github.com/moby/buildkit/exporter/containerimage/exptypes"
35+
solverpb "github.com/moby/buildkit/solver/pb"
3536
"github.com/moby/buildkit/util/appcontext"
3637
"github.com/moby/buildkit/util/grpcerrors"
3738
"github.com/pkg/errors"
@@ -500,6 +501,7 @@ func launchControllerAndRunBuild(dockerCli command.Cli, options buildOptions) er
500501
}
501502

502503
var ref string
504+
var def *solverpb.Definition
503505
var retErr error
504506
f := ioset.NewSingleForwarder()
505507
f.SetReader(os.Stdin)
@@ -518,12 +520,20 @@ func launchControllerAndRunBuild(dockerCli command.Cli, options buildOptions) er
518520
}
519521
}
520522

523+
if options.invoke == "debug-step" {
524+
// Special mode where we don't get the result but get only the build definition.
525+
// In this mode, Build() doesn't perform the build therefore always fails.
526+
// The error returned by Build() contains *pb.Definition wrapped with *controllererror.BuildError.
527+
opts.Debug = true
528+
}
529+
521530
var resp *client.SolveResponse
522-
ref, resp, err = c.Build(ctx, opts, pr, os.Stdout, os.Stderr, progress)
531+
ref, resp, def, err = c.Build(ctx, opts, pr, os.Stdout, os.Stderr, progress)
523532
if err != nil {
524533
var be *controllererrors.BuildError
525534
if errors.As(err, &be) {
526535
ref = be.Ref
536+
def = be.Definition
527537
retErr = err
528538
// We can proceed to monitor
529539
} else {
@@ -564,7 +574,7 @@ func launchControllerAndRunBuild(dockerCli command.Cli, options buildOptions) er
564574
}
565575
return errors.Errorf("failed to configure terminal: %v", err)
566576
}
567-
err = monitor.RunMonitor(ctx, ref, &opts, invokeConfig, c, progress, pr2, os.Stdout, os.Stderr)
577+
err = monitor.RunMonitor(ctx, ref, def, &opts, invokeConfig, c, progress, pr2, os.Stdout, os.Stderr)
568578
con.Reset()
569579
if err := pw2.Close(); err != nil {
570580
logrus.Debug("failed to close monitor stdin pipe reader")
@@ -577,12 +587,12 @@ func launchControllerAndRunBuild(dockerCli command.Cli, options buildOptions) er
577587
logrus.Warnf("disconnect error: %v", err)
578588
}
579589
}
580-
return nil
590+
return retErr
581591
}
582592

583593
func needsMonitor(invokeFlag string, retErr error) bool {
584594
switch invokeFlag {
585-
case "debug-shell":
595+
case "debug-shell", "debug-step":
586596
return true
587597
case "on-error":
588598
return retErr != nil
@@ -596,8 +606,7 @@ func parseInvokeConfig(invoke string) (cfg controllerapi.ContainerConfig, err er
596606
switch invoke {
597607
case "default", "debug-shell":
598608
return cfg, nil
599-
case "on-error":
600-
// NOTE: we overwrite the command to run because the original one should fail on the failed step.
609+
case "on-error", "debug-step":
601610
// TODO: make this configurable.
602611
cfg.Cmd = []string{"/bin/sh"}
603612
return cfg, nil

commands/debug-shell.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func debugShellCmd(dockerCli command.Cli) *cobra.Command {
3838
if err := con.SetRaw(); err != nil {
3939
return errors.Errorf("failed to configure terminal: %v", err)
4040
}
41-
err = monitor.RunMonitor(ctx, "", nil, controllerapi.ContainerConfig{
41+
err = monitor.RunMonitor(ctx, "", nil, nil, controllerapi.ContainerConfig{
4242
Tty: true,
4343
}, c, progress, os.Stdin, os.Stdout, os.Stderr)
4444
con.Reset()

commands/root.go

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
imagetoolscmd "github.com/docker/buildx/commands/imagetools"
77
"github.com/docker/buildx/controller/remote"
8+
"github.com/docker/buildx/monitor/dap"
89
"github.com/docker/buildx/util/logutil"
910
"github.com/docker/cli-docs-tool/annotation"
1011
"github.com/docker/cli/cli"
@@ -91,6 +92,7 @@ func addCommands(cmd *cobra.Command, dockerCli command.Cli) {
9192
remote.AddControllerCommands(cmd, dockerCli)
9293
addDebugShellCommand(cmd, dockerCli)
9394
}
95+
dap.AddDAPCommands(cmd, dockerCli) // hidden command; we need it for emacs DAP support
9496
}
9597

9698
func rootFlags(options *rootOptions, flags *pflag.FlagSet) {

controller/build/build.go

+9-4
Original file line numberDiff line numberDiff line change
@@ -175,15 +175,15 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in controllerapi.Build
175175
return nil, nil, err
176176
}
177177

178-
resp, res, err := buildTargets(ctx, dockerCli, b.NodeGroup, nodes, map[string]build.Options{defaultTargetName: opts}, progressMode, in.Opts.MetadataFile, statusChan)
178+
resp, res, err := buildTargets(ctx, dockerCli, b.NodeGroup, nodes, map[string]build.Options{defaultTargetName: opts}, progressMode, in.Opts.MetadataFile, statusChan, in.Debug)
179179
err = wrapBuildError(err, false)
180180
if err != nil {
181181
return nil, nil, err
182182
}
183183
return resp, res, nil
184184
}
185185

186-
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) {
186+
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) {
187187
ctx2, cancel := context.WithCancel(context.TODO())
188188
defer cancel()
189189

@@ -198,12 +198,16 @@ func buildTargets(ctx context.Context, dockerCli command.Cli, ng *store.NodeGrou
198198
var res *build.ResultContext
199199
var mu sync.Mutex
200200
var idx int
201-
resp, err := build.BuildWithResultHandler(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), progress.Tee(printer, statusChan), func(driverIndex int, gotRes *build.ResultContext) {
201+
resp, err := build.BuildWithResultHandler(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), progress.Tee(printer, statusChan), func(driverIndex int, gotRes *build.ResultContext) error {
202202
mu.Lock()
203203
defer mu.Unlock()
204204
if res == nil || driverIndex < idx {
205205
idx, res = driverIndex, gotRes
206206
}
207+
if debug {
208+
return errors.Errorf("debug mode")
209+
}
210+
return nil
207211
})
208212
err1 := printer.Wait()
209213
if err == nil {
@@ -386,6 +390,7 @@ func controllerUlimitOpt2DockerUlimit(u *controllerapi.UlimitOpt) *dockeropts.Ul
386390

387391
type ResultContextError struct {
388392
ResultContext *build.ResultContext
393+
Definition *solverpb.Definition
389394
error
390395
}
391396

@@ -408,7 +413,7 @@ func wrapResultContext(wErr error, res *build.ResultContext) error {
408413
return wErr
409414
}
410415
res.Done()
411-
return &ResultContextError{ResultContext: res2, error: wErr}
416+
return &ResultContextError{ResultContext: res2, Definition: def, error: wErr}
412417
}
413418

414419
func DefinitionFromResultContext(ctx context.Context, res *build.ResultContext) (*solverpb.Definition, error) {

controller/control/controller.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,32 @@ package control
33
import (
44
"context"
55
"io"
6+
"syscall"
67

78
"github.com/containerd/console"
89
controllerapi "github.com/docker/buildx/controller/pb"
910
"github.com/moby/buildkit/client"
11+
solverpb "github.com/moby/buildkit/solver/pb"
1012
)
1113

1214
type BuildxController interface {
13-
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)
14-
Invoke(ctx context.Context, ref string, options controllerapi.ContainerConfig, ioIn io.ReadCloser, ioOut io.WriteCloser, ioErr io.WriteCloser) (err error)
15+
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
16+
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)
1517
Kill(ctx context.Context) error
1618
Close() error
1719
List(ctx context.Context) (refs []string, _ error)
1820
Disconnect(ctx context.Context, ref string) error
1921
Inspect(ctx context.Context, ref string) (*controllerapi.InspectResponse, error)
22+
Continue(ctx context.Context, ref string, def *solverpb.Definition, w io.Writer, out console.File, progressMode string) error
2023
}
2124

2225
type ControlOptions struct {
2326
ServerConfig string
2427
Root string
2528
Detach bool
2629
}
30+
31+
type WinSize struct {
32+
Rows uint32
33+
Cols uint32
34+
}

controller/errdefs/build.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package errdefs
22

33
import (
4-
"github.com/containerd/typeurl"
4+
"github.com/containerd/typeurl/v2"
5+
"github.com/moby/buildkit/solver/pb"
56
"github.com/moby/buildkit/util/grpcerrors"
67
)
78

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

25-
func WrapBuild(err error, ref string) error {
26+
func WrapBuild(err error, ref string, def *pb.Definition) error {
2627
if err == nil {
2728
return nil
2829
}
29-
return &BuildError{Build: Build{Ref: ref}, error: err}
30+
return &BuildError{Build: Build{Ref: ref, Definition: def}, error: err}
3031
}
3132

3233
func (b *Build) WrapError(err error) error {

controller/errdefs/errdefs.pb.go

+19-9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

controller/errdefs/errdefs.proto

+1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ import "github.com/moby/buildkit/solver/pb/ops.proto";
66

77
message Build {
88
string Ref = 1;
9+
pb.Definition Definition = 2;
910
}

0 commit comments

Comments
 (0)