diff --git a/build/build.go b/build/build.go index 9e1c18c32d3b..b7480652e820 100644 --- a/build/build.go +++ b/build/build.go @@ -284,10 +284,14 @@ func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt Op case 1: // valid case 0: - if nodeDriver.IsMobyDriver() && !noDefaultLoad() { - // backwards compat for docker driver only: - // this ensures the build results in a docker image. - opt.Exports = []client.ExportEntry{{Type: "image", Attrs: map[string]string{}}} + if !noDefaultLoad() { + if nodeDriver.IsMobyDriver() { + // backwards compat for docker driver only: + // this ensures the build results in a docker image. + opt.Exports = []client.ExportEntry{{Type: "image", Attrs: map[string]string{}}} + } else if nodeDriver.Features(ctx)[driver.DefaultLoad] { + opt.Exports = []client.ExportEntry{{Type: "docker", Attrs: map[string]string{}}} + } } default: if err := bopts.LLBCaps.Supports(pb.CapMultipleExporters); err != nil { @@ -500,6 +504,10 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s if noMobyDriver != nil && !noDefaultLoad() && noPrintFunc(opt) { var noOutputTargets []string for name, opt := range opt { + if noMobyDriver.Features(ctx)[driver.DefaultLoad] { + continue + } + if !opt.Linked && len(opt.Exports) == 0 { noOutputTargets = append(noOutputTargets, name) } diff --git a/driver/docker-container/driver.go b/driver/docker-container/driver.go index 187f150c5779..93090da684ee 100644 --- a/driver/docker-container/driver.go +++ b/driver/docker-container/driver.go @@ -57,6 +57,7 @@ type Driver struct { cgroupParent string restartPolicy container.RestartPolicy env []string + defaultLoad bool } func (d *Driver) IsMobyDriver() bool { @@ -431,6 +432,7 @@ func (d *Driver) Features(ctx context.Context) map[driver.Feature]bool { driver.DockerExporter: true, driver.CacheExport: true, driver.MultiPlatform: true, + driver.DefaultLoad: d.defaultLoad, } } diff --git a/driver/docker-container/factory.go b/driver/docker-container/factory.go index 74ba34a86019..18bb2a4a553e 100644 --- a/driver/docker-container/factory.go +++ b/driver/docker-container/factory.go @@ -94,6 +94,11 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver if err != nil { return nil, err } + case k == "default-load": + d.defaultLoad, err = strconv.ParseBool(v) + if err != nil { + return nil, err + } case strings.HasPrefix(k, "env."): envName := strings.TrimPrefix(k, "env.") if envName == "" { diff --git a/driver/docker/driver.go b/driver/docker/driver.go index 85f4248cd4df..3e8411b704ec 100644 --- a/driver/docker/driver.go +++ b/driver/docker/driver.go @@ -102,6 +102,7 @@ func (d *Driver) Features(ctx context.Context) map[driver.Feature]bool { driver.DockerExporter: useContainerdSnapshotter, driver.CacheExport: useContainerdSnapshotter, driver.MultiPlatform: useContainerdSnapshotter, + driver.DefaultLoad: true, } }) return d.features.list diff --git a/driver/features.go b/driver/features.go index 30ee2a482347..09f9dee5c7b1 100644 --- a/driver/features.go +++ b/driver/features.go @@ -7,3 +7,5 @@ const DockerExporter Feature = "Docker exporter" const CacheExport Feature = "Cache export" const MultiPlatform Feature = "Multi-platform build" + +const DefaultLoad Feature = "Automatically load images to the Docker Engine image store" diff --git a/driver/kubernetes/driver.go b/driver/kubernetes/driver.go index 29ffb21e08bd..8894054d6e14 100644 --- a/driver/kubernetes/driver.go +++ b/driver/kubernetes/driver.go @@ -50,6 +50,7 @@ type Driver struct { podClient clientcorev1.PodInterface configMapClient clientcorev1.ConfigMapInterface podChooser podchooser.PodChooser + defaultLoad bool } func (d *Driver) IsMobyDriver() bool { @@ -237,6 +238,7 @@ func (d *Driver) Features(_ context.Context) map[driver.Feature]bool { driver.DockerExporter: d.DockerAPI != nil, driver.CacheExport: true, driver.MultiPlatform: true, // Untested (needs multiple Driver instances) + driver.DefaultLoad: d.defaultLoad, } } diff --git a/driver/kubernetes/factory.go b/driver/kubernetes/factory.go index 56384dbfbf47..6d9a9fbc816d 100644 --- a/driver/kubernetes/factory.go +++ b/driver/kubernetes/factory.go @@ -68,11 +68,13 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver clientset: clientset, } - deploymentOpt, loadbalance, namespace, err := f.processDriverOpts(deploymentName, namespace, cfg) + deploymentOpt, loadbalance, namespace, defaultLoad, err := f.processDriverOpts(deploymentName, namespace, cfg) if nil != err { return nil, err } + d.defaultLoad = defaultLoad + d.deployment, d.configMaps, err = manifest.NewDeployment(deploymentOpt) if err != nil { return nil, err @@ -100,7 +102,7 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver return d, nil } -func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg driver.InitConfig) (*manifest.DeploymentOpt, string, string, error) { +func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg driver.InitConfig) (*manifest.DeploymentOpt, string, string, bool, error) { deploymentOpt := &manifest.DeploymentOpt{ Name: deploymentName, Image: bkimage.DefaultImage, @@ -111,6 +113,8 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg ConfigFiles: cfg.Files, } + defaultLoad := false + deploymentOpt.Qemu.Image = bkimage.QemuImage loadbalance := LoadbalanceSticky @@ -127,7 +131,7 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg case "replicas": deploymentOpt.Replicas, err = strconv.Atoi(v) if err != nil { - return nil, "", "", err + return nil, "", "", false, err } case "requests.cpu": deploymentOpt.RequestsCPU = v @@ -140,7 +144,7 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg case "rootless": deploymentOpt.Rootless, err = strconv.ParseBool(v) if err != nil { - return nil, "", "", err + return nil, "", "", false, err } if _, isImage := cfg.DriverOpts["image"]; !isImage { deploymentOpt.Image = bkimage.DefaultRootlessImage @@ -150,17 +154,17 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg case "nodeselector": deploymentOpt.NodeSelector, err = splitMultiValues(v, ",", "=") if err != nil { - return nil, "", "", errors.Wrap(err, "cannot parse node selector") + return nil, "", "", false, errors.Wrap(err, "cannot parse node selector") } case "annotations": deploymentOpt.CustomAnnotations, err = splitMultiValues(v, ",", "=") if err != nil { - return nil, "", "", errors.Wrap(err, "cannot parse annotations") + return nil, "", "", false, errors.Wrap(err, "cannot parse annotations") } case "labels": deploymentOpt.CustomLabels, err = splitMultiValues(v, ",", "=") if err != nil { - return nil, "", "", errors.Wrap(err, "cannot parse labels") + return nil, "", "", false, errors.Wrap(err, "cannot parse labels") } case "tolerations": ts := strings.Split(v, ";") @@ -185,12 +189,12 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg case "tolerationSeconds": c, err := strconv.Atoi(kv[1]) if nil != err { - return nil, "", "", err + return nil, "", "", false, err } c64 := int64(c) t.TolerationSeconds = &c64 default: - return nil, "", "", errors.Errorf("invalid tolaration %q", v) + return nil, "", "", false, errors.Errorf("invalid tolaration %q", v) } } } @@ -202,24 +206,29 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg case LoadbalanceSticky: case LoadbalanceRandom: default: - return nil, "", "", errors.Errorf("invalid loadbalance %q", v) + return nil, "", "", false, errors.Errorf("invalid loadbalance %q", v) } loadbalance = v case "qemu.install": deploymentOpt.Qemu.Install, err = strconv.ParseBool(v) if err != nil { - return nil, "", "", err + return nil, "", "", false, err } case "qemu.image": if v != "" { deploymentOpt.Qemu.Image = v } + case "default-load": + defaultLoad, err = strconv.ParseBool(v) + if err != nil { + return nil, "", "", false, err + } default: - return nil, "", "", errors.Errorf("invalid driver option %s for driver %s", k, DriverName) + return nil, "", "", false, errors.Errorf("invalid driver option %s for driver %s", k, DriverName) } } - return deploymentOpt, loadbalance, namespace, nil + return deploymentOpt, loadbalance, namespace, defaultLoad, nil } func splitMultiValues(in string, itemsep string, kvsep string) (map[string]string, error) { diff --git a/driver/kubernetes/factory_test.go b/driver/kubernetes/factory_test.go index 605be96e1aae..80b507cffbc3 100644 --- a/driver/kubernetes/factory_test.go +++ b/driver/kubernetes/factory_test.go @@ -52,8 +52,9 @@ func TestFactory_processDriverOpts(t *testing.T) { "loadbalance": "random", "qemu.install": "true", "qemu.image": "qemu:latest", + "default-load": "true", } - r, loadbalance, ns, err := f.processDriverOpts(cfg.Name, "test", cfg) + r, loadbalance, ns, defaultLoad, err := f.processDriverOpts(cfg.Name, "test", cfg) nodeSelectors := map[string]string{ "selector1": "value1", @@ -102,6 +103,7 @@ func TestFactory_processDriverOpts(t *testing.T) { require.Equal(t, LoadbalanceRandom, loadbalance) require.True(t, r.Qemu.Install) require.Equal(t, "qemu:latest", r.Qemu.Image) + require.True(t, defaultLoad) }, ) @@ -109,7 +111,7 @@ func TestFactory_processDriverOpts(t *testing.T) { "NoOptions", func(t *testing.T) { cfg.DriverOpts = map[string]string{} - r, loadbalance, ns, err := f.processDriverOpts(cfg.Name, "test", cfg) + r, loadbalance, ns, defaultLoad, err := f.processDriverOpts(cfg.Name, "test", cfg) require.NoError(t, err) @@ -128,6 +130,7 @@ func TestFactory_processDriverOpts(t *testing.T) { require.Equal(t, LoadbalanceSticky, loadbalance) require.False(t, r.Qemu.Install) require.Equal(t, bkimage.QemuImage, r.Qemu.Image) + require.False(t, defaultLoad) }, ) @@ -138,7 +141,7 @@ func TestFactory_processDriverOpts(t *testing.T) { "loadbalance": "sticky", } - r, loadbalance, ns, err := f.processDriverOpts(cfg.Name, "test", cfg) + r, loadbalance, ns, defaultLoad, err := f.processDriverOpts(cfg.Name, "test", cfg) require.NoError(t, err) @@ -157,6 +160,7 @@ func TestFactory_processDriverOpts(t *testing.T) { require.Equal(t, LoadbalanceSticky, loadbalance) require.False(t, r.Qemu.Install) require.Equal(t, bkimage.QemuImage, r.Qemu.Image) + require.False(t, defaultLoad) }, ) @@ -165,7 +169,7 @@ func TestFactory_processDriverOpts(t *testing.T) { cfg.DriverOpts = map[string]string{ "replicas": "invalid", } - _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) + _, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) require.Error(t, err) }, ) @@ -175,7 +179,7 @@ func TestFactory_processDriverOpts(t *testing.T) { cfg.DriverOpts = map[string]string{ "rootless": "invalid", } - _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) + _, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) require.Error(t, err) }, ) @@ -185,7 +189,7 @@ func TestFactory_processDriverOpts(t *testing.T) { cfg.DriverOpts = map[string]string{ "tolerations": "key=foo,value=bar,invalid=foo2", } - _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) + _, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) require.Error(t, err) }, ) @@ -195,7 +199,7 @@ func TestFactory_processDriverOpts(t *testing.T) { cfg.DriverOpts = map[string]string{ "tolerations": "key=foo,value=bar,tolerationSeconds=invalid", } - _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) + _, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) require.Error(t, err) }, ) @@ -205,7 +209,7 @@ func TestFactory_processDriverOpts(t *testing.T) { cfg.DriverOpts = map[string]string{ "annotations": "key,value", } - _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) + _, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) require.Error(t, err) }, ) @@ -215,7 +219,7 @@ func TestFactory_processDriverOpts(t *testing.T) { cfg.DriverOpts = map[string]string{ "labels": "key=value=foo", } - _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) + _, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) require.Error(t, err) }, ) @@ -225,7 +229,7 @@ func TestFactory_processDriverOpts(t *testing.T) { cfg.DriverOpts = map[string]string{ "loadbalance": "invalid", } - _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) + _, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) require.Error(t, err) }, ) @@ -235,7 +239,7 @@ func TestFactory_processDriverOpts(t *testing.T) { cfg.DriverOpts = map[string]string{ "qemu.install": "invalid", } - _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) + _, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) require.Error(t, err) }, ) @@ -245,7 +249,7 @@ func TestFactory_processDriverOpts(t *testing.T) { cfg.DriverOpts = map[string]string{ "invalid": "foo", } - _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) + _, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg) require.Error(t, err) }, ) diff --git a/driver/remote/driver.go b/driver/remote/driver.go index 5f51e8f071b1..e16c126cf83d 100644 --- a/driver/remote/driver.go +++ b/driver/remote/driver.go @@ -24,6 +24,7 @@ type Driver struct { // if you add fields, remember to update docs: // https://github.com/docker/docs/blob/main/content/build/drivers/remote.md *tlsOpts + defaultLoad bool } type tlsOpts struct { @@ -149,6 +150,7 @@ func (d *Driver) Features(ctx context.Context) map[driver.Feature]bool { driver.DockerExporter: true, driver.CacheExport: true, driver.MultiPlatform: true, + driver.DefaultLoad: d.defaultLoad, } } diff --git a/driver/remote/factory.go b/driver/remote/factory.go index 4d196dd84812..1c30baad6163 100644 --- a/driver/remote/factory.go +++ b/driver/remote/factory.go @@ -4,6 +4,7 @@ import ( "context" "net/url" "path/filepath" + "strconv" "strings" // import connhelpers for special url schemes @@ -80,6 +81,12 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver } tls.key = v tlsEnabled = true + case "default-load": + parsed, err := strconv.ParseBool(v) + if err != nil { + return nil, err + } + d.defaultLoad = parsed default: return nil, errors.Errorf("invalid driver option %s for remote driver", k) } diff --git a/tests/build.go b/tests/build.go index 5bf14fd11526..82ec29fa9021 100644 --- a/tests/build.go +++ b/tests/build.go @@ -57,6 +57,7 @@ var buildTests = []func(t *testing.T, sb integration.Sandbox){ testBuildRef, testBuildMultiExporters, testBuildLoadPush, + testBuildDefaultLoad, } func testBuild(t *testing.T, sb integration.Sandbox) { @@ -675,6 +676,69 @@ func testBuildLoadPush(t *testing.T, sb integration.Sandbox) { // TODO: test metadata file when supported by multi exporters https://github.com/docker/buildx/issues/2181 } +func testBuildDefaultLoad(t *testing.T, sb integration.Sandbox) { + if sb.Name() != "docker" { + t.Skip("skipping test for non-docker workers") + } + + registry, err := sb.NewRegistry() + if errors.Is(err, integration.ErrRequirements) { + t.Skip(err.Error()) + } + require.NoError(t, err) + + target := registry + "/buildx/registry:" + identity.NewID() + + var builderName string + t.Cleanup(func() { + if builderName == "" { + return + } + + cmd := dockerCmd(sb, withArgs("image", "rm", target)) + cmd.Stderr = os.Stderr + require.NoError(t, cmd.Run()) + + out, err := rmCmd(sb, withArgs(builderName)) + require.NoError(t, err, out) + }) + + // TODO: use stable buildkit image when v0.13.0 released + out, err := createCmd(sb, withArgs( + "--driver", "docker-container", + "--buildkitd-flags=--allow-insecure-entitlement=network.host", + "--driver-opt", "network=host", + "--driver-opt", "default-load=true", + "--driver-opt", "image=moby/buildkit:v0.13.0-rc3", + )) + require.NoError(t, err, out) + builderName = strings.TrimSpace(out) + + dir := createTestProject(t) + + cmd := buildxCmd(sb, withArgs( + "build", "--push", + fmt.Sprintf("-t=%s", target), + dir, + )) + cmd.Env = append(cmd.Env, "BUILDX_BUILDER="+builderName) + outb, err := cmd.CombinedOutput() + require.NoError(t, err, string(outb)) + + // test registry + desc, provider, err := contentutil.ProviderFromRef(target) + require.NoError(t, err) + _, err = testutil.ReadImages(sb.Context(), provider, desc) + require.NoError(t, err) + + // test docker store + cmd = dockerCmd(sb, withArgs("image", "inspect", target)) + cmd.Stderr = os.Stderr + require.NoError(t, cmd.Run()) + + // TODO: test metadata file when supported by multi exporters https://github.com/docker/buildx/issues/2181 +} + func createTestProject(t *testing.T) string { dockerfile := []byte(` FROM busybox:latest AS base