Skip to content

Commit 1b2034f

Browse files
committed
monitor: breakpoint debugger on terminal and on IDEs (via DAP)
Signed-off-by: Kohei Tokunaga <ktokunaga.mail@gmail.com>
1 parent c7f16e3 commit 1b2034f

File tree

246 files changed

+26580
-2990
lines changed

Some content is hidden

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

246 files changed

+26580
-2990
lines changed

build/build.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -664,7 +664,7 @@ func Build(ctx context.Context, nodes []builder.Node, opt map[string]Options, do
664664
return BuildWithResultHandler(ctx, nodes, opt, docker, configDir, w, nil)
665665
}
666666

667-
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) {
667+
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) {
668668
if len(nodes) == 0 {
669669
return nil, errors.Errorf("driver required for build")
670670
}
@@ -929,7 +929,9 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
929929
if resultHandleFunc != nil {
930930
resultCtx, err := NewResultContext(cc, so, res)
931931
if err == nil {
932-
resultHandleFunc(dp.driverIndex, resultCtx)
932+
if err := resultHandleFunc(dp.driverIndex, resultCtx); err != nil {
933+
return nil, err
934+
}
933935
} else {
934936
logrus.Warnf("failed to record result: %s", err)
935937
}

build/invoke.go

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -97,22 +97,22 @@ func (c *Container) markUnavailable() {
9797
c.isUnavailable.Store(true)
9898
}
9999

100-
func (c *Container) Exec(ctx context.Context, cfg *controllerapi.InvokeConfig, stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser) error {
100+
func (c *Container) Exec(ctx context.Context, cfg *controllerapi.InvokeConfig, stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, signalCh <-chan syscall.Signal, resizeCh <-chan gateway.WinSize) error {
101101
if isInit := c.initStarted.CompareAndSwap(false, true); isInit {
102102
defer func() {
103103
// container can't be used after init exits
104104
c.markUnavailable()
105105
}()
106106
}
107-
err := exec(ctx, c.resultCtx, cfg, c.container, stdin, stdout, stderr)
107+
err := exec(ctx, c.resultCtx, cfg, c.container, stdin, stdout, stderr, signalCh, resizeCh)
108108
if err != nil {
109109
// Container becomes unavailable if one of the processes fails in it.
110110
c.markUnavailable()
111111
}
112112
return err
113113
}
114114

115-
func exec(ctx context.Context, resultCtx *ResultContext, cfg *controllerapi.InvokeConfig, ctr gateway.Container, stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser) error {
115+
func exec(ctx context.Context, resultCtx *ResultContext, cfg *controllerapi.InvokeConfig, ctr gateway.Container, stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, signalCh <-chan syscall.Signal, resizeCh <-chan gateway.WinSize) error {
116116
processCfg, err := resultCtx.getProcessConfig(cfg, stdin, stdout, stderr)
117117
if err != nil {
118118
return err
@@ -125,12 +125,24 @@ func exec(ctx context.Context, resultCtx *ResultContext, cfg *controllerapi.Invo
125125
doneCh := make(chan struct{})
126126
defer close(doneCh)
127127
go func() {
128-
select {
129-
case <-ctx.Done():
130-
if err := proc.Signal(ctx, syscall.SIGKILL); err != nil {
131-
logrus.Warnf("failed to kill process: %v", err)
128+
for {
129+
select {
130+
case s := <-signalCh:
131+
if err := proc.Signal(ctx, s); err != nil {
132+
logrus.Warnf("failed to send signal %v %v", s, err)
133+
}
134+
case w := <-resizeCh:
135+
if err := proc.Resize(ctx, w); err != nil {
136+
logrus.Warnf("failed to resize %v: %v", w, err)
137+
}
138+
case <-ctx.Done():
139+
if err := proc.Signal(ctx, syscall.SIGKILL); err != nil {
140+
logrus.Warnf("failed to kill process: %v", err)
141+
}
142+
return
143+
case <-doneCh:
144+
return
132145
}
133-
case <-doneCh:
134146
}
135147
}()
136148

build/result.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ import (
55
_ "crypto/sha256" // ensure digests can be computed
66
"encoding/json"
77
"io"
8+
"path/filepath"
89
"sync"
910
"sync/atomic"
1011

1112
controllerapi "github.com/docker/buildx/controller/pb"
1213
"github.com/moby/buildkit/client"
14+
"github.com/moby/buildkit/client/llb"
1315
"github.com/moby/buildkit/exporter/containerimage/exptypes"
1416
gateway "github.com/moby/buildkit/frontend/gateway/client"
1517
"github.com/moby/buildkit/solver/errdefs"
@@ -44,6 +46,17 @@ func getDefinition(ctx context.Context, res *gateway.Result) (*pb.Definition, er
4446
return def.ToPB(), nil
4547
}
4648

49+
func DefinitionFromResultContext(ctx context.Context, res *ResultContext) (*pb.Definition, error) {
50+
if res.def != nil {
51+
return res.def, nil
52+
}
53+
return nil, errors.Errorf("result context doesn't contain build definition")
54+
}
55+
56+
func GetResultAtFromResultContext(ctx context.Context, resultCtx *ResultContext, target *pb.Definition, statusChan chan *client.SolveStatus) (*ResultContext, error) {
57+
return getResultAt(ctx, resultCtx.client, resultCtx.solveOpt, target, statusChan)
58+
}
59+
4760
func getResultAt(ctx context.Context, c *client.Client, solveOpt client.SolveOpt, target *pb.Definition, statusChan chan *client.SolveStatus) (*ResultContext, error) {
4861
ctx, cancel := context.WithCancel(ctx)
4962
defer cancel()
@@ -76,6 +89,7 @@ func getResultAt(ctx context.Context, c *client.Client, solveOpt client.SolveOpt
7689
resultCtx := ResultContext{
7790
client: c,
7891
solveOpt: solveOpt,
92+
def: target,
7993
}
8094
_, err := c.Build(context.Background(), solveOpt, "buildx", func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
8195
ctx, cancel := context.WithCancel(ctx)
@@ -124,6 +138,7 @@ func getResultAt(ctx context.Context, c *client.Client, solveOpt client.SolveOpt
124138
type ResultContext struct {
125139
client *client.Client
126140
res *gateway.Result
141+
def *pb.Definition
127142
solveOpt client.SolveOpt
128143

129144
solveErr *errdefs.SolveError
@@ -150,6 +165,10 @@ func (r *ResultContext) Done() {
150165
})
151166
}
152167

168+
func (r *ResultContext) SolveError() *errdefs.SolveError {
169+
return r.solveErr
170+
}
171+
153172
func (r *ResultContext) registerCleanup(f func()) {
154173
r.cleanupsMu.Lock()
155174
r.cleanups = append(r.cleanups, f)
@@ -177,6 +196,28 @@ func (r *ResultContext) getContainerConfig(ctx context.Context, c gateway.Client
177196
}
178197
containerCfg = *ccfg
179198
}
199+
if img := cfg.Image; img != "" {
200+
def, err := llb.Image(img).Marshal(ctx)
201+
if err != nil {
202+
return containerCfg, err
203+
}
204+
r, err := c.Solve(ctx, gateway.SolveRequest{
205+
Definition: def.ToPB(),
206+
})
207+
if err != nil {
208+
return containerCfg, err
209+
}
210+
for i := range containerCfg.Mounts {
211+
containerCfg.Mounts[i].Dest = filepath.Join(cfg.ResultMountPath, containerCfg.Mounts[i].Dest)
212+
}
213+
containerCfg.Mounts = append([]gateway.Mount{
214+
{
215+
Dest: "/",
216+
MountType: pb.MountType_BIND,
217+
Ref: r.Ref,
218+
},
219+
}, containerCfg.Mounts...)
220+
}
180221
return containerCfg, nil
181222
}
182223

cmd/buildx/main.go

Lines changed: 2 additions & 1 deletion
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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"github.com/docker/docker/pkg/ioutils"
3535
"github.com/moby/buildkit/client"
3636
"github.com/moby/buildkit/exporter/containerimage/exptypes"
37+
solverpb "github.com/moby/buildkit/solver/pb"
3738
"github.com/moby/buildkit/util/appcontext"
3839
"github.com/moby/buildkit/util/grpcerrors"
3940
"github.com/pkg/errors"
@@ -510,6 +511,7 @@ func launchControllerAndRunBuild(dockerCli command.Cli, options buildOptions) er
510511
opts = *optsP
511512

512513
var ref string
514+
var def *solverpb.Definition
513515
var retErr error
514516
f := ioset.NewSingleForwarder()
515517
f.SetReader(os.Stdin)
@@ -528,12 +530,20 @@ func launchControllerAndRunBuild(dockerCli command.Cli, options buildOptions) er
528530
}
529531
}
530532

533+
if options.invoke == "debug-step" {
534+
// Special mode where we don't get the result but get only the build definition.
535+
// In this mode, Build() doesn't perform the build therefore always fails.
536+
// The error returned by Build() contains *pb.Definition wrapped with *controllererror.BuildError.
537+
opts.Debug = true
538+
}
539+
531540
var resp *client.SolveResponse
532-
ref, resp, err = c.Build(ctx, opts, pr, os.Stdout, os.Stderr, progress)
541+
ref, resp, def, err = c.Build(ctx, opts, pr, os.Stdout, os.Stderr, progress)
533542
if err != nil {
534543
var be *controllererrors.BuildError
535544
if errors.As(err, &be) {
536545
ref = be.Ref
546+
def = be.Definition
537547
retErr = err
538548
// We can proceed to monitor
539549
} else {
@@ -574,7 +584,7 @@ func launchControllerAndRunBuild(dockerCli command.Cli, options buildOptions) er
574584
}
575585
return errors.Errorf("failed to configure terminal: %v", err)
576586
}
577-
err = monitor.RunMonitor(ctx, ref, &opts, invokeConfig, c, progress, pr2, os.Stdout, os.Stderr)
587+
err = monitor.RunMonitor(ctx, ref, def, &opts, invokeConfig, c, progress, pr2, os.Stdout, os.Stderr)
578588
con.Reset()
579589
if err := pw2.Close(); err != nil {
580590
logrus.Debug("failed to close monitor stdin pipe reader")
@@ -587,12 +597,12 @@ func launchControllerAndRunBuild(dockerCli command.Cli, options buildOptions) er
587597
logrus.Warnf("disconnect error: %v", err)
588598
}
589599
}
590-
return nil
600+
return retErr
591601
}
592602

593603
func needsMonitor(invokeFlag string, retErr error) bool {
594604
switch invokeFlag {
595-
case "debug-shell":
605+
case "debug-shell", "debug-step":
596606
return true
597607
case "on-error":
598608
return retErr != nil
@@ -606,8 +616,7 @@ func parseInvokeConfig(invoke string) (cfg controllerapi.InvokeConfig, err error
606616
switch invoke {
607617
case "default", "debug-shell":
608618
return cfg, nil
609-
case "on-error":
610-
// NOTE: we overwrite the command to run because the original one should fail on the failed step.
619+
case "on-error", "debug-step":
611620
cfg.Cmd = []string{"/bin/sh"}
612621
return cfg, nil
613622
}

commands/debug-shell.go

Lines changed: 1 addition & 1 deletion
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.InvokeConfig{
41+
err = monitor.RunMonitor(ctx, "", nil, nil, controllerapi.InvokeConfig{
4242
Tty: true,
4343
}, c, progress, os.Stdin, os.Stdout, os.Stderr)
4444
con.Reset()

commands/root.go

Lines changed: 2 additions & 0 deletions
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

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"github.com/moby/buildkit/client"
3232
"github.com/moby/buildkit/session/auth/authprovider"
3333
"github.com/moby/buildkit/solver/errdefs"
34+
solverpb "github.com/moby/buildkit/solver/pb"
3435
"github.com/moby/buildkit/util/grpcerrors"
3536
"github.com/moby/buildkit/util/progress/progressui"
3637
"github.com/morikuni/aec"
@@ -174,15 +175,15 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in controllerapi.Build
174175
return nil, nil, err
175176
}
176177

177-
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)
178179
err = wrapBuildError(err, false)
179180
if err != nil {
180181
return nil, nil, err
181182
}
182183
return resp, res, nil
183184
}
184185

185-
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) {
186187
ctx2, cancel := context.WithCancel(context.TODO())
187188
defer cancel()
188189

@@ -197,12 +198,18 @@ func buildTargets(ctx context.Context, dockerCli command.Cli, ng *store.NodeGrou
197198
var res *build.ResultContext
198199
var mu sync.Mutex
199200
var idx int
200-
resp, err := build.BuildWithResultHandler(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), progress.Tee(printer, statusChan), func(driverIndex int, gotRes *build.ResultContext) {
201+
pw, done := progress.Tee(printer, statusChan)
202+
defer done()
203+
resp, err := build.BuildWithResultHandler(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), pw, func(driverIndex int, gotRes *build.ResultContext) error {
201204
mu.Lock()
202205
defer mu.Unlock()
203206
if res == nil || driverIndex < idx {
204207
idx, res = driverIndex, gotRes
205208
}
209+
if debug {
210+
return errors.Errorf("debug mode")
211+
}
212+
return nil
206213
})
207214
err1 := printer.Wait()
208215
if err == nil {
@@ -385,6 +392,7 @@ func controllerUlimitOpt2DockerUlimit(u *controllerapi.UlimitOpt) *dockeropts.Ul
385392

386393
type ResultContextError struct {
387394
ResultContext *build.ResultContext
395+
Definition *solverpb.Definition
388396
error
389397
}
390398

@@ -396,5 +404,10 @@ func wrapResultContext(wErr error, res *build.ResultContext) error {
396404
if wErr == nil {
397405
return nil
398406
}
399-
return &ResultContextError{ResultContext: res, error: wErr}
407+
def, err := build.DefinitionFromResultContext(context.Background(), res)
408+
if err != nil {
409+
logrus.Warnf("failed to get definition from result: %v", err)
410+
return wErr
411+
}
412+
return &ResultContextError{ResultContext: res, Definition: def, error: wErr}
400413
}

controller/control/controller.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,38 @@ 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)
15+
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)
1416
// Invoke starts an IO session into the specified process.
1517
// If pid doesn't matche to any running processes, it starts a new process with the specified config.
1618
// If there is no container running or InvokeConfig.Rollback is speicfied, the process will start in a newly created container.
1719
// NOTE: If needed, in the future, we can split this API into three APIs (NewContainer, NewProcess and Attach).
18-
Invoke(ctx context.Context, ref, pid string, options controllerapi.InvokeConfig, ioIn io.ReadCloser, ioOut io.WriteCloser, ioErr io.WriteCloser) error
20+
Invoke(ctx context.Context, ref, pid string, options controllerapi.InvokeConfig, ioIn io.ReadCloser, ioOut io.WriteCloser, ioErr io.WriteCloser, signalCh <-chan syscall.Signal, resizeCh <-chan WinSize) error
1921
Kill(ctx context.Context) error
2022
Close() error
2123
List(ctx context.Context) (refs []string, _ error)
2224
Disconnect(ctx context.Context, ref string) error
2325
ListProcesses(ctx context.Context, ref string) (infos []*controllerapi.ProcessInfo, retErr error)
2426
DisconnectProcess(ctx context.Context, ref, pid string) error
2527
Inspect(ctx context.Context, ref string) (*controllerapi.InspectResponse, error)
28+
Continue(ctx context.Context, ref string, def *solverpb.Definition, w io.Writer, out console.File, progressMode string) error
2629
}
2730

2831
type ControlOptions struct {
2932
ServerConfig string
3033
Root string
3134
Detach bool
3235
}
36+
37+
type WinSize struct {
38+
Rows uint32
39+
Cols uint32
40+
}

0 commit comments

Comments
 (0)