From 1715f796056f8ace5f36d8a19f2d3aa243a55940 Mon Sep 17 00:00:00 2001 From: Tomas Perez Molina Date: Fri, 4 Sep 2026 13:46:19 +0000 Subject: [PATCH 1/2] build: extract multi-driver export preparation Move the existing exporter setup out of the node build loop without changing its behavior. Environment: Datadog workspace Co-Authored-By: OpenAI GPT-5.6 Signed-off-by: Tomas Perez Molina --- build/build.go | 55 ++++++++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/build/build.go b/build/build.go index f0badc95982d..daca97227403 100644 --- a/build/build.go +++ b/build/build.go @@ -432,6 +432,35 @@ func toRepoOnly(in string) (string, error) { return strings.Join(out, ","), nil } +func prepareMultiDriverExports(so *client.SolveOpt, pushNames *string, insecurePush *bool) error { + for i, e := range so.Exports { + switch e.Type { + case "oci", "tar": + return errors.Errorf("%s for multi-node builds currently not supported", e.Type) + case "image": + if *pushNames == "" && e.Attrs["push"] != "" { + if ok, _ := strconv.ParseBool(e.Attrs["push"]); ok { + *pushNames = e.Attrs["name"] + if *pushNames == "" { + return errors.Errorf("tag is needed when pushing to registry") + } + names, err := toRepoOnly(e.Attrs["name"]) + if err != nil { + return err + } + if ok, _ := strconv.ParseBool(e.Attrs["registry.insecure"]); ok { + *insecurePush = true + } + e.Attrs["name"] = names + e.Attrs["push-by-digest"] = "true" + so.Exports[i].Attrs = e.Attrs + } + } + } + } + return nil +} + type ( EvaluateFunc func(ctx context.Context, name string, c gateway.Client, res *gateway.Result, opt Options) error Handler struct { @@ -566,30 +595,8 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[ node := dp.Node() so := reqForNodes[k][i].so if multiDriver { - for i, e := range so.Exports { - switch e.Type { - case "oci", "tar": - return errors.Errorf("%s for multi-node builds currently not supported", e.Type) - case "image": - if pushNames == "" && e.Attrs["push"] != "" { - if ok, _ := strconv.ParseBool(e.Attrs["push"]); ok { - pushNames = e.Attrs["name"] - if pushNames == "" { - return errors.Errorf("tag is needed when pushing to registry") - } - names, err := toRepoOnly(e.Attrs["name"]) - if err != nil { - return err - } - if ok, _ := strconv.ParseBool(e.Attrs["registry.insecure"]); ok { - insecurePush = true - } - e.Attrs["name"] = names - e.Attrs["push-by-digest"] = "true" - so.Exports[i].Attrs = e.Attrs - } - } - } + if err := prepareMultiDriverExports(so, &pushNames, &insecurePush); err != nil { + return err } } From aaf1e92d83f5d39dd28cbfd6f8c93f5446261735 Mon Sep 17 00:00:00 2001 From: Tomas Perez Molina Date: Fri, 4 Sep 2026 13:46:44 +0000 Subject: [PATCH 2/2] build: fix partial multi-node image pushes Prepare each node's cloned exporter options so no node publishes the requested tag before all platforms complete. Environment: Datadog workspace Co-Authored-By: OpenAI GPT-5.6 Signed-off-by: Tomas Perez Molina --- build/build.go | 40 +++++++++++++++++++++++----------------- build/build_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 17 deletions(-) diff --git a/build/build.go b/build/build.go index daca97227403..a2444e5452f1 100644 --- a/build/build.go +++ b/build/build.go @@ -433,29 +433,35 @@ func toRepoOnly(in string) (string, error) { } func prepareMultiDriverExports(so *client.SolveOpt, pushNames *string, insecurePush *bool) error { - for i, e := range so.Exports { + var pushPrepared bool + for i := range so.Exports { + e := &so.Exports[i] switch e.Type { case "oci", "tar": return errors.Errorf("%s for multi-node builds currently not supported", e.Type) case "image": - if *pushNames == "" && e.Attrs["push"] != "" { - if ok, _ := strconv.ParseBool(e.Attrs["push"]); ok { - *pushNames = e.Attrs["name"] - if *pushNames == "" { - return errors.Errorf("tag is needed when pushing to registry") - } - names, err := toRepoOnly(e.Attrs["name"]) - if err != nil { - return err - } - if ok, _ := strconv.ParseBool(e.Attrs["registry.insecure"]); ok { - *insecurePush = true - } - e.Attrs["name"] = names - e.Attrs["push-by-digest"] = "true" - so.Exports[i].Attrs = e.Attrs + if pushPrepared { + continue + } + if ok, _ := strconv.ParseBool(e.Attrs["push"]); !ok { + continue + } + if *pushNames == "" { + *pushNames = e.Attrs["name"] + if *pushNames == "" { + return errors.Errorf("tag is needed when pushing to registry") } + if ok, _ := strconv.ParseBool(e.Attrs["registry.insecure"]); ok { + *insecurePush = true + } + } + names, err := toRepoOnly(e.Attrs["name"]) + if err != nil { + return err } + e.Attrs["name"] = names + e.Attrs["push-by-digest"] = "true" + pushPrepared = true } } return nil diff --git a/build/build_test.go b/build/build_test.go index d7f65fb4e092..30450584782f 100644 --- a/build/build_test.go +++ b/build/build_test.go @@ -35,6 +35,48 @@ func (d warnOutputDriver) IsMobyDriver() bool { return d.moby } +func TestPrepareMultiDriverExportsForEveryNode(t *testing.T) { + const taggedName = "registry.example.com/user/app:latest" + const secondaryName = "registry.example.com/user/secondary:latest" + newSolveOpt := func() *client.SolveOpt { + return &client.SolveOpt{ + Exports: []client.ExportEntry{ + { + Type: "image", + Attrs: map[string]string{ + "name": taggedName, + "push": "true", + "registry.insecure": "true", + }, + }, + { + Type: "image", + Attrs: map[string]string{ + "name": secondaryName, + "push": "true", + }, + }, + }, + } + } + solveOpts := []*client.SolveOpt{newSolveOpt(), newSolveOpt()} + + var pushNames string + var insecurePush bool + for _, so := range solveOpts { + require.NoError(t, prepareMultiDriverExports(so, &pushNames, &insecurePush)) + } + + require.Equal(t, taggedName, pushNames) + require.True(t, insecurePush) + for _, so := range solveOpts { + require.Equal(t, "registry.example.com/user/app", so.Exports[0].Attrs["name"]) + require.Equal(t, "true", so.Exports[0].Attrs["push-by-digest"]) + require.Equal(t, secondaryName, so.Exports[1].Attrs["name"]) + require.NotContains(t, so.Exports[1].Attrs, "push-by-digest") + } +} + func TestWarnOnNoOutput(t *testing.T) { cloudNodes := []builder.Node{{Driver: newWarnOutputDriver("cloud", false)}} mobyNodes := []builder.Node{{Driver: newWarnOutputDriver("docker", true)}}