Skip to content

Commit 8f2604b

Browse files
committed
monitor: move remaining controller functionality into monitor
This creates a `Monitor` type that keeps the global state between monitor invocations and allows the monitor to exist during the build so it can be utilized for callbacks. The result handler is now registered with the monitor during the build and `Run` will use the result if it is present and the configuration intends the monitor to be invoked with the given result. Signed-off-by: Jonathan A. Sternberg <[email protected]>
1 parent bb5b5e3 commit 8f2604b

File tree

7 files changed

+189
-241
lines changed

7 files changed

+189
-241
lines changed

build/build.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -311,11 +311,15 @@ func toRepoOnly(in string) (string, error) {
311311
return strings.Join(out, ","), nil
312312
}
313313

314+
type Handler struct {
315+
OnResult func(driverIdx int, rCtx *ResultHandle)
316+
}
317+
314318
func Build(ctx context.Context, nodes []builder.Node, opts map[string]Options, docker *dockerutil.Client, cfg *confutil.Config, w progress.Writer) (resp map[string]*client.SolveResponse, err error) {
315319
return BuildWithResultHandler(ctx, nodes, opts, docker, cfg, w, nil)
316320
}
317321

318-
func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[string]Options, docker *dockerutil.Client, cfg *confutil.Config, w progress.Writer, resultHandleFunc func(driverIdx int, rCtx *ResultHandle)) (resp map[string]*client.SolveResponse, err error) {
322+
func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[string]Options, docker *dockerutil.Client, cfg *confutil.Config, w progress.Writer, bh *Handler) (resp map[string]*client.SolveResponse, err error) {
319323
if len(nodes) == 0 {
320324
return nil, errors.Errorf("driver required for build")
321325
}
@@ -509,10 +513,10 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[
509513
buildRef := fmt.Sprintf("%s/%s/%s", node.Builder, node.Name, so.Ref)
510514

511515
var rr *client.SolveResponse
512-
if resultHandleFunc != nil {
516+
if bh != nil && bh.OnResult != nil {
513517
var resultHandle *ResultHandle
514518
resultHandle, rr, err = NewResultHandle(ctx, cc, *so, "buildx", buildFunc, ch)
515-
resultHandleFunc(dp.driverIndex, resultHandle)
519+
bh.OnResult(dp.driverIndex, resultHandle)
516520
} else {
517521
span, ctx := tracing.StartSpan(ctx, "build")
518522
rr, err = c.Build(ctx, *so, "buildx", buildFunc, ch)

commands/build.go

Lines changed: 17 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323
"github.com/docker/buildx/commands/debug"
2424
cbuild "github.com/docker/buildx/controller/build"
2525
controllererrors "github.com/docker/buildx/controller/errdefs"
26-
"github.com/docker/buildx/controller/local"
2726
controllerapi "github.com/docker/buildx/controller/pb"
2827
"github.com/docker/buildx/monitor"
2928
"github.com/docker/buildx/store"
@@ -32,7 +31,6 @@ import (
3231
"github.com/docker/buildx/util/cobrautil"
3332
"github.com/docker/buildx/util/confutil"
3433
"github.com/docker/buildx/util/desktop"
35-
"github.com/docker/buildx/util/ioset"
3634
"github.com/docker/buildx/util/metricutil"
3735
"github.com/docker/buildx/util/osutil"
3836
"github.com/docker/buildx/util/progress"
@@ -418,11 +416,7 @@ func getImageID(resp map[string]string) string {
418416
}
419417

420418
func runBasicBuild(ctx context.Context, dockerCli command.Cli, opts *cbuild.Options, printer *progress.Printer) (*client.SolveResponse, *build.Inputs, error) {
421-
resp, res, dfmap, err := cbuild.RunBuild(ctx, dockerCli, opts, dockerCli.In(), printer, false)
422-
if res != nil {
423-
res.Done()
424-
}
425-
return resp, dfmap, err
419+
return cbuild.RunBuild(ctx, dockerCli, opts, dockerCli.In(), printer, nil)
426420
}
427421

428422
func runControllerBuild(ctx context.Context, dockerCli command.Cli, opts *cbuild.Options, options buildOptions, printer *progress.Printer) (_ *client.SolveResponse, _ *build.Inputs, retErr error) {
@@ -433,33 +427,32 @@ func runControllerBuild(ctx context.Context, dockerCli command.Cli, opts *cbuild
433427

434428
var (
435429
in io.ReadCloser
436-
f *ioset.SingleForwarder
430+
m *monitor.Monitor
431+
bh build.Handler
437432
)
438433
if options.invokeConfig == nil {
439434
in = dockerCli.In()
440435
} else {
441-
f = ioset.NewSingleForwarder()
442-
f.SetReader(dockerCli.In())
436+
m = monitor.New(&options.invokeConfig.InvokeConfig, dockerCli.In(), os.Stdout, os.Stderr, printer)
437+
defer m.Close()
438+
439+
bh = m.Handler()
443440
}
444441

445442
for {
446-
c := local.NewController(ctx, dockerCli)
447-
448-
resp, inputs, err := c.Build(ctx, opts, in, printer)
443+
resp, inputs, err := cbuild.RunBuild(ctx, dockerCli, opts, in, printer, &bh)
449444
if err != nil {
450445
var be *controllererrors.BuildError
451446
if errors.As(err, &be) {
452447
retErr = err
453448
// We can proceed to monitor
454449
} else {
455-
c.Close()
456450
return nil, nil, errors.Wrapf(err, "failed to build")
457451
}
458452
}
459453

460-
if options.invokeConfig != nil {
461-
if err := runMonitorIfNeeded(ctx, options.invokeConfig, retErr, c, f, os.Stdout, os.Stderr, printer); err != nil {
462-
c.Close()
454+
if m != nil {
455+
if err := m.Run(ctx, err); err != nil {
463456
if errors.Is(err, monitor.ErrReload) {
464457
retErr = nil
465458
continue
@@ -468,55 +461,10 @@ func runControllerBuild(ctx context.Context, dockerCli command.Cli, opts *cbuild
468461
}
469462
}
470463

471-
c.Close()
472464
return resp, inputs, err
473465
}
474466
}
475467

476-
func runMonitorIfNeeded(ctx context.Context, cfg *invokeConfig, retErr error, c *local.Controller, stdin *ioset.SingleForwarder, stdout io.WriteCloser, stderr console.File, printer *progress.Printer) error {
477-
if !cfg.needsDebug(retErr) {
478-
return nil
479-
}
480-
481-
// Print errors before launching monitor
482-
if err := printError(retErr, printer); err != nil {
483-
logrus.Warnf("failed to print error information: %v", err)
484-
}
485-
486-
pr, pw := io.Pipe()
487-
stdin.SetWriter(pw, func() io.WriteCloser {
488-
pw.Close() // propagate EOF
489-
return nil
490-
})
491-
492-
con := console.Current()
493-
if err := con.SetRaw(); err != nil {
494-
return errors.Errorf("failed to configure terminal: %v", err)
495-
}
496-
defer con.Reset()
497-
498-
monitorErr := monitor.RunMonitor(ctx, &cfg.InvokeConfig, c, pr, stdout, stderr, printer)
499-
if err := pw.Close(); err != nil {
500-
logrus.Debug("failed to close monitor stdin pipe reader")
501-
}
502-
return monitorErr
503-
}
504-
505-
func printError(err error, printer *progress.Printer) error {
506-
if err == nil {
507-
return nil
508-
}
509-
if err := printer.Pause(); err != nil {
510-
return err
511-
}
512-
defer printer.Unpause()
513-
for _, s := range errdefs.Sources(err) {
514-
s.Print(os.Stderr)
515-
}
516-
fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
517-
return nil
518-
}
519-
520468
func newDebuggableBuild(dockerCli command.Cli, rootOpts *rootOptions) debug.DebuggableCmd {
521469
return &debuggableBuild{dockerCli: dockerCli, rootOpts: rootOpts}
522470
}
@@ -973,23 +921,21 @@ func printValue(w io.Writer, printer callFunc, version string, format string, re
973921

974922
type invokeConfig struct {
975923
controllerapi.InvokeConfig
976-
onFlag string
977924
invokeFlag string
978925
}
979926

980-
func (cfg *invokeConfig) needsDebug(retErr error) bool {
981-
switch cfg.onFlag {
927+
func (cfg *invokeConfig) parseInvokeConfig(invoke, on string) error {
928+
switch on {
982929
case "always":
983-
return true
930+
cfg.SuspendOn = controllerapi.SuspendAlways
984931
case "error":
985-
return retErr != nil
932+
cfg.SuspendOn = controllerapi.SuspendError
986933
default:
987-
return cfg.invokeFlag != ""
934+
if invoke != "" {
935+
cfg.SuspendOn = controllerapi.SuspendAlways
936+
}
988937
}
989-
}
990938

991-
func (cfg *invokeConfig) parseInvokeConfig(invoke, on string) error {
992-
cfg.onFlag = on
993939
cfg.invokeFlag = invoke
994940
cfg.Tty = true
995941
cfg.NoCmd = true

commands/debug/root.go

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,8 @@
11
package debug
22

33
import (
4-
"context"
5-
"os"
6-
7-
"github.com/containerd/console"
8-
"github.com/docker/buildx/controller/local"
9-
controllerapi "github.com/docker/buildx/controller/pb"
10-
"github.com/docker/buildx/monitor"
114
"github.com/docker/buildx/util/cobrautil"
12-
"github.com/docker/buildx/util/progress"
135
"github.com/docker/cli/cli/command"
14-
"github.com/moby/buildkit/util/progress/progressui"
15-
"github.com/pkg/errors"
166
"github.com/spf13/cobra"
177
)
188

@@ -38,27 +28,6 @@ func RootCmd(dockerCli command.Cli, children ...DebuggableCmd) *cobra.Command {
3828
cmd := &cobra.Command{
3929
Use: "debug",
4030
Short: "Start debugger",
41-
Args: cobra.NoArgs,
42-
RunE: func(cmd *cobra.Command, args []string) error {
43-
printer, err := progress.NewPrinter(context.TODO(), os.Stderr, progressui.DisplayMode(progressMode))
44-
if err != nil {
45-
return err
46-
}
47-
48-
ctx := context.TODO()
49-
c := local.NewController(ctx, dockerCli)
50-
51-
con := console.Current()
52-
if err := con.SetRaw(); err != nil {
53-
return errors.Errorf("failed to configure terminal: %v", err)
54-
}
55-
56-
err = monitor.RunMonitor(ctx, &controllerapi.InvokeConfig{
57-
Tty: true,
58-
}, c, dockerCli.In(), os.Stdout, os.Stderr, printer)
59-
con.Reset()
60-
return err
61-
},
6231
}
6332
cobrautil.MarkCommandExperimental(cmd)
6433

controller/build/build.go

Lines changed: 19 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import (
55
"io"
66
"path/filepath"
77
"strings"
8-
"sync"
98

109
"github.com/docker/buildx/build"
1110
"github.com/docker/buildx/builder"
11+
"github.com/docker/buildx/controller/errdefs"
1212
controllerapi "github.com/docker/buildx/controller/pb"
1313
"github.com/docker/buildx/store"
1414
"github.com/docker/buildx/store/storeutil"
@@ -34,9 +34,9 @@ const defaultTargetName = "default"
3434
// NOTE: When an error happens during the build and this function acquires the debuggable *build.ResultHandle,
3535
// this function returns it in addition to the error (i.e. it does "return nil, res, err"). The caller can
3636
// inspect the result and debug the cause of that error.
37-
func RunBuild(ctx context.Context, dockerCli command.Cli, in *Options, inStream io.Reader, progress progress.Writer, generateResult bool) (*client.SolveResponse, *build.ResultHandle, *build.Inputs, error) {
37+
func RunBuild(ctx context.Context, dockerCli command.Cli, in *Options, inStream io.Reader, progress progress.Writer, bh *build.Handler) (*client.SolveResponse, *build.Inputs, error) {
3838
if in.NoCache && len(in.NoCacheFilter) > 0 {
39-
return nil, nil, nil, errors.Errorf("--no-cache and --no-cache-filter cannot currently be used together")
39+
return nil, nil, errors.Errorf("--no-cache and --no-cache-filter cannot currently be used together")
4040
}
4141

4242
contexts := map[string]build.NamedContext{}
@@ -70,7 +70,7 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in *Options, inStream
7070

7171
platforms, err := platformutil.Parse(in.Platforms)
7272
if err != nil {
73-
return nil, nil, nil, err
73+
return nil, nil, err
7474
}
7575
opts.Platforms = platforms
7676

@@ -81,7 +81,7 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in *Options, inStream
8181

8282
secrets, err := controllerapi.CreateSecrets(in.Secrets)
8383
if err != nil {
84-
return nil, nil, nil, err
84+
return nil, nil, err
8585
}
8686
opts.Session = append(opts.Session, secrets)
8787

@@ -91,13 +91,13 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in *Options, inStream
9191
}
9292
ssh, err := controllerapi.CreateSSH(sshSpecs)
9393
if err != nil {
94-
return nil, nil, nil, err
94+
return nil, nil, err
9595
}
9696
opts.Session = append(opts.Session, ssh)
9797

9898
outputs, _, err := controllerapi.CreateExports(in.Exports)
9999
if err != nil {
100-
return nil, nil, nil, err
100+
return nil, nil, err
101101
}
102102
if in.ExportPush {
103103
var pushUsed bool
@@ -136,7 +136,7 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in *Options, inStream
136136

137137
annotations, err := buildflags.ParseAnnotations(in.Annotations)
138138
if err != nil {
139-
return nil, nil, nil, errors.Wrap(err, "parse annotations")
139+
return nil, nil, errors.Wrap(err, "parse annotations")
140140
}
141141

142142
for _, o := range outputs {
@@ -156,7 +156,7 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in *Options, inStream
156156

157157
allow, err := buildflags.ParseEntitlements(in.Allow)
158158
if err != nil {
159-
return nil, nil, nil, err
159+
return nil, nil, err
160160
}
161161
opts.Allow = allow
162162

@@ -180,56 +180,40 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in *Options, inStream
180180
builder.WithContextPathHash(contextPathHash),
181181
)
182182
if err != nil {
183-
return nil, nil, nil, err
183+
return nil, nil, err
184184
}
185185
if err = updateLastActivity(dockerCli, b.NodeGroup); err != nil {
186-
return nil, nil, nil, errors.Wrapf(err, "failed to update builder last activity time")
186+
return nil, nil, errors.Wrapf(err, "failed to update builder last activity time")
187187
}
188188
nodes, err := b.LoadNodes(ctx)
189189
if err != nil {
190-
return nil, nil, nil, err
190+
return nil, nil, err
191191
}
192192

193193
var inputs *build.Inputs
194194
buildOptions := map[string]build.Options{defaultTargetName: opts}
195-
resp, res, err := buildTargets(ctx, dockerCli, nodes, buildOptions, progress, generateResult)
195+
resp, err := buildTargets(ctx, dockerCli, nodes, buildOptions, progress, bh)
196196
err = wrapBuildError(err, false)
197197
if err != nil {
198-
// NOTE: buildTargets can return *build.ResultHandle even on error.
199-
return nil, res, nil, err
198+
return nil, nil, errdefs.WrapBuild(err)
200199
}
201200
if i, ok := buildOptions[defaultTargetName]; ok {
202201
inputs = &i.Inputs
203202
}
204-
return resp, res, inputs, nil
203+
return resp, inputs, nil
205204
}
206205

207206
// buildTargets runs the specified build and returns the result.
208207
//
209208
// NOTE: When an error happens during the build and this function acquires the debuggable *build.ResultHandle,
210209
// this function returns it in addition to the error (i.e. it does "return nil, res, err"). The caller can
211210
// inspect the result and debug the cause of that error.
212-
func buildTargets(ctx context.Context, dockerCli command.Cli, nodes []builder.Node, opts map[string]build.Options, progress progress.Writer, generateResult bool) (*client.SolveResponse, *build.ResultHandle, error) {
213-
var res *build.ResultHandle
214-
var resp map[string]*client.SolveResponse
215-
var err error
216-
if generateResult {
217-
var mu sync.Mutex
218-
var idx int
219-
resp, err = build.BuildWithResultHandler(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.NewConfig(dockerCli), progress, func(driverIndex int, gotRes *build.ResultHandle) {
220-
mu.Lock()
221-
defer mu.Unlock()
222-
if res == nil || driverIndex < idx {
223-
idx, res = driverIndex, gotRes
224-
}
225-
})
226-
} else {
227-
resp, err = build.Build(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.NewConfig(dockerCli), progress)
228-
}
211+
func buildTargets(ctx context.Context, dockerCli command.Cli, nodes []builder.Node, opts map[string]build.Options, progress progress.Writer, bh *build.Handler) (*client.SolveResponse, error) {
212+
resp, err := build.BuildWithResultHandler(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.NewConfig(dockerCli), progress, bh)
229213
if err != nil {
230-
return nil, res, err
214+
return nil, err
231215
}
232-
return resp[defaultTargetName], res, err
216+
return resp[defaultTargetName], err
233217
}
234218

235219
func wrapBuildError(err error, bake bool) error {

0 commit comments

Comments
 (0)