From 0ce15c38fb49966af21d5bbc6c6844c8939fd58b Mon Sep 17 00:00:00 2001 From: CrazyMax Date: Thu, 1 Jun 2023 10:36:15 +0200 Subject: [PATCH] bake: use controller to build With this change we are now passing a list of controller options to run a build and a map of responses and result context is returned as bake can handle a list of targets. Signed-off-by: CrazyMax --- bake/bake.go | 11 ++----- bake/bake_test.go | 44 ++++++++++++++++--------- build/build.go | 4 +-- commands/bake.go | 34 +++++++++---------- commands/build.go | 7 ++-- controller/build/build.go | 58 +++++++++++++++++++++++---------- controller/local/controller.go | 8 +++-- controller/remote/controller.go | 3 +- 8 files changed, 103 insertions(+), 66 deletions(-) diff --git a/bake/bake.go b/bake/bake.go index 4458ccc2b24d..e2a9cbef45ff 100644 --- a/bake/bake.go +++ b/bake/bake.go @@ -15,7 +15,6 @@ import ( composecli "github.com/compose-spec/compose-go/cli" "github.com/docker/buildx/bake/hclparser" "github.com/docker/buildx/build" - cbuild "github.com/docker/buildx/controller/build" controllerapi "github.com/docker/buildx/controller/pb" "github.com/docker/buildx/util/buildflags" hcl "github.com/hashicorp/hcl/v2" @@ -908,18 +907,14 @@ func (t *Target) GetName(ectx *hcl.EvalContext, block *hcl.Block, loadDeps func( return value.AsString(), nil } -func TargetsToBuildOpt(m map[string]*Target, inp *Input) (map[string]build.Options, error) { - m2 := make(map[string]build.Options, len(m)) +func TargetsToControllerOptions(m map[string]*Target, inp *Input) (map[string]*controllerapi.BuildOptions, error) { + m2 := make(map[string]*controllerapi.BuildOptions, len(m)) for k, v := range m { opts, err := toControllerOpt(v, inp) if err != nil { return nil, err } - bo, err := cbuild.ToBuildOpts(*opts, nil) - if err != nil { - return nil, err - } - m2[k] = *bo + m2[k] = opts } return m2, nil } diff --git a/bake/bake_test.go b/bake/bake_test.go index f3341c06d7f9..f3874240e6ac 100644 --- a/bake/bake_test.go +++ b/bake/bake_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/docker/buildx/controller/pb" "github.com/stretchr/testify/require" ) @@ -390,7 +391,7 @@ func TestHCLCwdPrefix(t *testing.T) { _, ok := m["app"] require.True(t, ok) - _, err = TargetsToBuildOpt(m, &Input{}) + _, err = TargetsToControllerOptions(m, &Input{}) require.NoError(t, err) require.Equal(t, "test", *m["app"].Dockerfile) @@ -421,7 +422,7 @@ func TestOverrideMerge(t *testing.T) { _, ok := m["app"] require.True(t, ok) - _, err = TargetsToBuildOpt(m, &Input{}) + _, err = TargetsToControllerOptions(m, &Input{}) require.NoError(t, err) require.Equal(t, []string{"linux/arm", "linux/ppc64le"}, m["app"].Platforms) @@ -456,7 +457,7 @@ func TestReadContexts(t *testing.T) { _, ok := m["app"] require.True(t, ok) - bo, err := TargetsToBuildOpt(m, &Input{}) + bo, err := TargetsToControllerOptions(m, &Input{}) require.NoError(t, err) ctxs := bo["app"].Inputs.NamedContexts @@ -472,7 +473,7 @@ func TestReadContexts(t *testing.T) { _, ok = m["app"] require.True(t, ok) - bo, err = TargetsToBuildOpt(m, &Input{}) + bo, err = TargetsToControllerOptions(m, &Input{}) require.NoError(t, err) ctxs = bo["app"].Inputs.NamedContexts @@ -490,7 +491,7 @@ func TestReadContexts(t *testing.T) { _, ok = m["app"] require.True(t, ok) - bo, err = TargetsToBuildOpt(m, &Input{}) + bo, err = TargetsToControllerOptions(m, &Input{}) require.NoError(t, err) ctxs = bo["app"].Inputs.NamedContexts @@ -1325,7 +1326,7 @@ func TestHCLNullVars(t *testing.T) { _, ok := m["default"] require.True(t, ok) - _, err = TargetsToBuildOpt(m, &Input{}) + _, err = TargetsToControllerOptions(m, &Input{}) require.NoError(t, err) require.Equal(t, map[string]*string{"bar": ptrstr("baz")}, m["default"].Args) require.Equal(t, map[string]*string{"com.docker.app.baz": ptrstr("foo")}, m["default"].Labels) @@ -1360,7 +1361,7 @@ func TestJSONNullVars(t *testing.T) { _, ok := m["default"] require.True(t, ok) - _, err = TargetsToBuildOpt(m, &Input{}) + _, err = TargetsToControllerOptions(m, &Input{}) require.NoError(t, err) require.Equal(t, map[string]*string{"bar": ptrstr("baz")}, m["default"].Args) } @@ -1432,21 +1433,34 @@ func TestAttestDuplicates(t *testing.T) { require.Equal(t, []string{"type=sbom,foo=bar", "type=provenance,mode=max"}, m["default"].Attest) require.NoError(t, err) - opts, err := TargetsToBuildOpt(m, &Input{}) + opts, err := TargetsToControllerOptions(m, &Input{}) require.NoError(t, err) - require.Equal(t, map[string]*string{ - "sbom": ptrstr("type=sbom,foo=bar"), - "provenance": ptrstr("type=provenance,mode=max"), + require.Equal(t, []*pb.Attest{ + { + Type: "sbom", + Attrs: "type=sbom,foo=bar", + }, + { + Type: "provenance", + Attrs: "type=provenance,mode=max", + }, }, opts["default"].Attests) m, _, err = ReadTargets(ctx, []File{fp}, []string{"default"}, []string{"*.attest=type=sbom,disabled=true"}, nil) require.Equal(t, []string{"type=sbom,disabled=true", "type=provenance,mode=max"}, m["default"].Attest) require.NoError(t, err) - opts, err = TargetsToBuildOpt(m, &Input{}) + opts, err = TargetsToControllerOptions(m, &Input{}) require.NoError(t, err) - require.Equal(t, map[string]*string{ - "sbom": nil, - "provenance": ptrstr("type=provenance,mode=max"), + require.Equal(t, []*pb.Attest{ + { + Type: "sbom", + Disabled: true, + Attrs: "type=sbom,disabled=true", + }, + { + Type: "provenance", + Attrs: "type=provenance,mode=max", + }, }, opts["default"].Attests) } diff --git a/build/build.go b/build/build.go index ae027b4bd0a9..37e39587ea4c 100644 --- a/build/build.go +++ b/build/build.go @@ -667,7 +667,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(target string, rCtx *ResultContext)) (resp map[string]*client.SolveResponse, err error) { if len(nodes) == 0 { return nil, errors.Errorf("driver required for build") } @@ -932,7 +932,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s if resultHandleFunc != nil { resultCtx, err := NewResultContext(ctx, cc, so, res) if err == nil { - resultHandleFunc(dp.driverIndex, resultCtx) + resultHandleFunc(k, resultCtx) } else { logrus.Warnf("failed to record result: %s", err) } diff --git a/commands/bake.go b/commands/bake.go index 398c0fd915a4..8af93e5eef82 100644 --- a/commands/bake.go +++ b/commands/bake.go @@ -10,10 +10,10 @@ import ( "github.com/docker/buildx/bake" "github.com/docker/buildx/build" "github.com/docker/buildx/builder" + cbuild "github.com/docker/buildx/controller/build" + controllerapi "github.com/docker/buildx/controller/pb" "github.com/docker/buildx/util/buildflags" "github.com/docker/buildx/util/cobrautil/completion" - "github.com/docker/buildx/util/confutil" - "github.com/docker/buildx/util/dockerutil" "github.com/docker/buildx/util/progress" "github.com/docker/buildx/util/tracing" "github.com/docker/cli/cli/command" @@ -29,10 +29,8 @@ type bakeOptions struct { sbom string provenance string - builder string - metadataFile string - exportPush bool - exportLoad bool + controllerapi.CommonOptions + //control.ControlOptions } func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags commonFlags) (err error) { @@ -67,12 +65,12 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags com } overrides := in.overrides - if in.exportPush { - if in.exportLoad { + if in.ExportPush { + if in.ExportLoad { return errors.Errorf("push and load may not be set together at the moment") } overrides = append(overrides, "*.push=true") - } else if in.exportLoad { + } else if in.ExportLoad { overrides = append(overrides, "*.output=type=docker") } if cFlags.noCache != nil { @@ -100,7 +98,7 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags com // instance only needed for reading remote bake files or building if url != "" || !in.printOnly { b, err := builder.New(dockerCli, - builder.WithName(in.builder), + builder.WithName(in.Builder), builder.WithContextPathHash(contextPathHash), ) if err != nil { @@ -166,7 +164,7 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags com } // this function can update target context string from the input so call before printOnly check - bo, err := bake.TargetsToBuildOpt(tgts, inp) + opts, err := bake.TargetsToControllerOptions(tgts, inp) if err != nil { return err } @@ -191,17 +189,17 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags com return nil } - resp, err := build.Build(ctx, nodes, bo, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), printer) + resp, _, err := cbuild.RunBuild(ctx, dockerCli, in.Builder, opts, os.Stdin, printer, false) if err != nil { return wrapBuildError(err, true) } - if len(in.metadataFile) > 0 { + if len(in.MetadataFile) > 0 { dt := make(map[string]interface{}) for t, r := range resp { dt[t] = decodeExporterResponse(r.ExporterResponse) } - if err := writeMetadataFile(in.metadataFile, dt); err != nil { + if err := writeMetadataFile(in.MetadataFile, dt); err != nil { return err } } @@ -225,8 +223,8 @@ func bakeCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command { if !cmd.Flags().Lookup("pull").Changed { cFlags.pull = nil } - options.builder = rootOpts.builder - options.metadataFile = cFlags.metadataFile + options.Builder = rootOpts.builder + options.MetadataFile = cFlags.metadataFile // Other common flags (noCache, pull and progress) are processed in runBake function. return runBake(dockerCli, args, options, cFlags) }, @@ -236,9 +234,9 @@ func bakeCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command { flags := cmd.Flags() flags.StringArrayVarP(&options.files, "file", "f", []string{}, "Build definition file") - flags.BoolVar(&options.exportLoad, "load", false, `Shorthand for "--set=*.output=type=docker"`) + flags.BoolVar(&options.ExportLoad, "load", false, `Shorthand for "--set=*.output=type=docker"`) flags.BoolVar(&options.printOnly, "print", false, "Print the options without building") - flags.BoolVar(&options.exportPush, "push", false, `Shorthand for "--set=*.output=type=registry"`) + flags.BoolVar(&options.ExportPush, "push", false, `Shorthand for "--set=*.output=type=registry"`) flags.StringVar(&options.sbom, "sbom", "", `Shorthand for "--set=*.attest=type=sbom"`) flags.StringVar(&options.provenance, "provenance", "", `Shorthand for "--set=*.attest=type=provenance"`) flags.StringArrayVar(&options.overrides, "set", nil, `Override target value (e.g., "targetpattern.key=value")`) diff --git a/commands/build.go b/commands/build.go index 31946aaccc76..3ead17830773 100644 --- a/commands/build.go +++ b/commands/build.go @@ -299,8 +299,11 @@ func getImageID(resp map[string]string) string { } func runBasicBuild(ctx context.Context, dockerCli command.Cli, opts *controllerapi.BuildOptions, options buildOptions, printer *progress.Printer) (*client.SolveResponse, error) { - resp, _, err := cbuild.RunBuild(ctx, dockerCli, *opts, os.Stdin, printer, false) - return resp, err + resp, _, err := cbuild.RunBuild(ctx, dockerCli, options.Builder, map[string]*controllerapi.BuildOptions{cbuild.DefaultTargetName: opts}, os.Stdin, printer, false) + if err != nil { + return nil, err + } + return resp[cbuild.DefaultTargetName], err } func runControllerBuild(ctx context.Context, dockerCli command.Cli, opts *controllerapi.BuildOptions, options buildOptions, printer *progress.Printer) (*client.SolveResponse, error) { diff --git a/controller/build/build.go b/controller/build/build.go index 182d273ab450..bd51ad63e41a 100644 --- a/controller/build/build.go +++ b/controller/build/build.go @@ -27,30 +27,54 @@ import ( "github.com/moby/buildkit/session/auth/authprovider" "github.com/moby/buildkit/util/grpcerrors" "github.com/pkg/errors" + "golang.org/x/sync/errgroup" "google.golang.org/grpc/codes" ) -const defaultTargetName = "default" +const DefaultTargetName = "default" // RunBuild runs the specified build and returns the result. // // NOTE: When an error happens during the build and this function acquires the debuggable *build.ResultContext, // this function returns it in addition to the error (i.e. it does "return nil, res, err"). The caller can // inspect the result and debug the cause of that error. -func RunBuild(ctx context.Context, dockerCli command.Cli, in controllerapi.BuildOptions, inStream io.Reader, progress progress.Writer, generateResult bool) (*client.SolveResponse, *build.ResultContext, error) { - opts, err := ToBuildOpts(in, inStream) - if err != nil { +func RunBuild(ctx context.Context, dockerCli command.Cli, builderName string, in map[string]*controllerapi.BuildOptions, inStream io.Reader, progress progress.Writer, generateResult bool) (map[string]*client.SolveResponse, map[string]*build.ResultContext, error) { + var err error + + opts := make(map[string]build.Options, len(in)) + optsMu := sync.Mutex{} + eg, _ := errgroup.WithContext(ctx) + for t, o := range in { + func(t string, o *controllerapi.BuildOptions) { + eg.Go(func() error { + opt, err := ToBuildOpts(o, inStream) + if err != nil { + return err + } + optsMu.Lock() + opts[t] = *opt + optsMu.Unlock() + return nil + }) + }(t, o) + } + if err := eg.Wait(); err != nil { return nil, nil, err } // key string used for kubernetes "sticky" mode - contextPathHash, err := filepath.Abs(in.Inputs.ContextPath) - if err != nil { - contextPathHash = in.Inputs.ContextPath + contextPathHash, _ := os.Getwd() + if len(in) == 1 { + for _, o := range in { + contextPathHash, err = filepath.Abs(o.Inputs.ContextPath) + if err != nil { + contextPathHash = o.Inputs.ContextPath + } + } } b, err := builder.New(dockerCli, - builder.WithName(in.Opts.Builder), + builder.WithName(builderName), builder.WithContextPathHash(contextPathHash), ) if err != nil { @@ -64,7 +88,7 @@ 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}, progress, generateResult) + resp, res, err := buildTargets(ctx, dockerCli, b.NodeGroup, nodes, opts, progress, generateResult) err = wrapBuildError(err, false) if err != nil { return nil, nil, err @@ -72,7 +96,7 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in controllerapi.Build return resp, res, nil } -func ToBuildOpts(in controllerapi.BuildOptions, inStream io.Reader) (*build.Options, error) { +func ToBuildOpts(in *controllerapi.BuildOptions, inStream io.Reader) (*build.Options, error) { if in.Opts.NoCache && len(in.NoCacheFilter) > 0 { return nil, errors.Errorf("--no-cache and --no-cache-filter cannot currently be used together") } @@ -216,19 +240,19 @@ func ToBuildOpts(in controllerapi.BuildOptions, inStream io.Reader) (*build.Opti // NOTE: When an error happens during the build and this function acquires the debuggable *build.ResultContext, // this function returns it in addition to the error (i.e. it does "return nil, res, err"). The caller can // inspect the result and debug the cause of that error. -func buildTargets(ctx context.Context, dockerCli command.Cli, ng *store.NodeGroup, nodes []builder.Node, opts map[string]build.Options, progress progress.Writer, generateResult bool) (*client.SolveResponse, *build.ResultContext, error) { - var res *build.ResultContext +func buildTargets(ctx context.Context, dockerCli command.Cli, ng *store.NodeGroup, nodes []builder.Node, opts map[string]build.Options, progress progress.Writer, generateResult bool) (map[string]*client.SolveResponse, map[string]*build.ResultContext, error) { + var res map[string]*build.ResultContext var resp map[string]*client.SolveResponse var err error if generateResult { var mu sync.Mutex - var idx int - resp, err = build.BuildWithResultHandler(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), progress, func(driverIndex int, gotRes *build.ResultContext) { + resp, err = build.BuildWithResultHandler(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), progress, func(target string, gotRes *build.ResultContext) { mu.Lock() defer mu.Unlock() - if res == nil || driverIndex < idx { - idx, res = driverIndex, gotRes + if res == nil { + res = make(map[string]*build.ResultContext) } + res[target] = gotRes }) } else { resp, err = build.Build(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), progress) @@ -236,7 +260,7 @@ func buildTargets(ctx context.Context, dockerCli command.Cli, ng *store.NodeGrou if err != nil { return nil, res, err } - return resp[defaultTargetName], res, err + return resp, res, err } func wrapBuildError(err error, bake bool) error { diff --git a/controller/local/controller.go b/controller/local/controller.go index 9d69412ef583..fd74086f7e0e 100644 --- a/controller/local/controller.go +++ b/controller/local/controller.go @@ -48,11 +48,13 @@ func (b *localController) Build(ctx context.Context, options controllerapi.Build } defer b.buildOnGoing.Store(false) - resp, res, buildErr := cbuild.RunBuild(ctx, b.dockerCli, options, in, progress, true) + resp, res, buildErr := cbuild.RunBuild(ctx, b.dockerCli, map[string]*controllerapi.BuildOptions{ + cbuild.DefaultTargetName: &options, + }, in, progress, true) // NOTE: RunBuild can return *build.ResultContext even on error. if res != nil { b.buildConfig = buildConfig{ - resultCtx: res, + resultCtx: res[cbuild.DefaultTargetName], buildOptions: &options, } if buildErr != nil { @@ -62,7 +64,7 @@ func (b *localController) Build(ctx context.Context, options controllerapi.Build if buildErr != nil { return "", nil, buildErr } - return b.ref, resp, nil + return b.ref, resp[cbuild.DefaultTargetName], nil } func (b *localController) ListProcesses(ctx context.Context, ref string) (infos []*controllerapi.ProcessInfo, retErr error) { diff --git a/controller/remote/controller.go b/controller/remote/controller.go index 7d7c8496990c..ecc6afe1e83c 100644 --- a/controller/remote/controller.go +++ b/controller/remote/controller.go @@ -149,7 +149,8 @@ func serveCmd(dockerCli command.Cli) *cobra.Command { // prepare server b := NewServer(func(ctx context.Context, options *controllerapi.BuildOptions, stdin io.Reader, progress progress.Writer) (*client.SolveResponse, *build.ResultContext, error) { - return cbuild.RunBuild(ctx, dockerCli, *options, stdin, progress, true) + resp, res, buildErr := cbuild.RunBuild(ctx, dockerCli, map[string]*controllerapi.BuildOptions{cbuild.DefaultTargetName: options}, stdin, progress, true) + return resp[cbuild.DefaultTargetName], res[cbuild.DefaultTargetName], buildErr }) defer b.Close()