Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions build/opt.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ var sendGitQueryAsInput = sync.OnceValue(func() bool {
return false
})

const (
noDefaultAttestationsEnv = "BUILDX_NO_DEFAULT_ATTESTATIONS"
noDefaultOCIArtifactEnv = "BUILDX_NO_DEFAULT_OCI_ARTIFACT"

@thaJeztah thaJeztah Aug 4, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking out loud; what are the formats we (currently) support for including attestations? Would it make sense to have a "attestations-format" selection instead of a boolean? (e.g. BUILDX_ATTESTATIONS_FORMAT=(oci|foo|bar|compat|legacy)) or was the old format not complying to any standards? (ISTR there were options for legacy registries, so those would still be valid and potentially work around the issue with non-OCI-compliant registries?)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather keep this as a narrow opt-out for now (specially since this change aims for a patch release). BuildKit currently exposes this as a boolean exporter option, oci-artifact=true|false, so mirroring that as BUILDX_NO_DEFAULT_OCI_ARTIFACT keeps Buildx from inventing a new format-selection layer on top.

The explicit format selection already exists through --output type=image,oci-artifact=false when users want to choose it directly. This env var only changes the default when the option was not set, similar to BUILDX_NO_DEFAULT_ATTESTATIONS.

A generic BUILDX_ATTESTATIONS_FORMAT=legacy|compat|oci feels a bit too broad to me because Buildx would then need to define what those names mean and keep mapping them to BuildKit exporter behavior. If BuildKit grows more attestation storage formats later, I think that should first be exposed as a BuildKit exporter option, and Buildx can follow that model.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's ok with me; mostly wanted to avoid having to introduce yet-another env-var if there's more options than enable/disable (not even sure what format the "disable" means and if that's a legacy fallback defined by OCI?)

)

// policyExplicitlyDisabled reports whether the user passed `--policy
// disabled=true`, which suppresses both user-defined and builtin default
// policies.
Expand Down Expand Up @@ -355,12 +360,11 @@ func toSolveOpt(ctx context.Context, np *noderesolver.ResolvedNode, multiDriver
}

if _, ok := opt.Attests["provenance"]; !ok && supportAttestations {
const noAttestEnv = "BUILDX_NO_DEFAULT_ATTESTATIONS"
var noProv bool
if v, ok := os.LookupEnv(noAttestEnv); ok {
if v, ok := os.LookupEnv(noDefaultAttestationsEnv); ok {
noProv, err = strconv.ParseBool(v)
if err != nil {
return nil, nil, errors.Wrap(err, "invalid "+noAttestEnv)
return nil, nil, errors.Wrap(err, "invalid "+noDefaultAttestationsEnv)
}
}
if !noProv {
Expand Down Expand Up @@ -436,6 +440,14 @@ func toSolveOpt(ctx context.Context, np *noderesolver.ResolvedNode, multiDriver
}
opt.Exports = exports

var noDefaultOCIArtifact bool
if v, ok := os.LookupEnv(noDefaultOCIArtifactEnv); ok {
noDefaultOCIArtifact, err = strconv.ParseBool(v)
if err != nil {
return nil, nil, errors.Wrap(err, "invalid "+noDefaultOCIArtifactEnv)
}
}

// set up exporters
for i, e := range opt.Exports {
if e.Type == "oci" && !nodeDriver.Features(ctx)[driver.OCIExporter] {
Expand Down Expand Up @@ -497,6 +509,14 @@ func toSolveOpt(ctx context.Context, np *noderesolver.ResolvedNode, multiDriver
opt.Exports[i].Attrs["buildinfo-attrs"] = v
}
}
if noDefaultOCIArtifact && supportAttestations {
switch opt.Exports[i].Type {
case client.ExporterImage, client.ExporterOCI, "moby":
if _, ok := opt.Exports[i].Attrs[string(exptypes.OptKeyOCIArtifact)]; !ok {
opt.Exports[i].Attrs[string(exptypes.OptKeyOCIArtifact)] = "false"
}
}
}
}

so.Exports = opt.Exports
Expand Down
36 changes: 36 additions & 0 deletions tests/bake.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ var bakeTests = []func(t *testing.T, sb integration.Sandbox){
testBakeMetadataWarningsDedup,
testBakeMultiExporters,
testBakeLoadPush,
testBakeNoDefaultOCIArtifact,
testBakeListTargets,
testBakeListVariables,
testBakeListTypedVariables,
Expand Down Expand Up @@ -2232,6 +2233,41 @@ target "default" {
// TODO: test metadata file when supported by multi exporters https://github.com/docker/buildx/issues/2181
}

func testBakeNoDefaultOCIArtifact(t *testing.T, sb integration.Sandbox) {
if isMobyWorker(sb) {
t.Skip("attestations are not supported by the docker worker")
}
Comment on lines +2237 to +2239

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting; are these two the same? (I went looking if we had a more specific "supports attestations" instead of driver-name 😂)

buildx/tests/integration.go

Lines 126 to 129 in 1988826

func isMobyWorker(sb integration.Sandbox) bool {
name, _, hasFeature := driverName(sb.Name())
return name == "docker" && !hasFeature
}

buildx/tests/integration.go

Lines 136 to 139 in 1988826

func isDockerWorker(sb integration.Sandbox) bool {
name, _, _ := driverName(sb.Name())
return name == "docker"
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hum indeed, should rework this 😅


registry, err := sb.NewRegistry()
if errors.Is(err, integration.ErrRequirements) {
t.Skip(err.Error())
}
require.NoError(t, err)
target := registry + "/buildx/bake-no-default-oci-artifact:latest"

dockerfile := []byte(`
FROM scratch
COPY foo /foo
`)
bakefile := fmt.Appendf(nil, `
target "default" {
output = ["type=image,name=%s,push=true"]
attest = ["type=provenance"]
}
`, target)
dir := tmpdir(
t,
fstest.CreateFile("docker-bake.hcl", bakefile, 0600),
fstest.CreateFile("Dockerfile", dockerfile, 0600),
fstest.CreateFile("foo", []byte("foo"), 0600),
)

out, err := bakeCmd(sb, withDir(dir), withEnv("BUILDX_NO_DEFAULT_OCI_ARTIFACT=true"))
require.NoError(t, err, string(out))

requireLegacyAttestationStorage(t, sb, target)
}

func testBakeLoadPush(t *testing.T, sb integration.Sandbox) {
if !isDockerContainerWorker(sb) {
t.Skip("only testing with docker-container worker")
Expand Down
55 changes: 55 additions & 0 deletions tests/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ var buildTests = []func(t *testing.T, sb integration.Sandbox){
testBuildLocalExportDeleteMode,
testBuildRegistryExport,
testBuildRegistryExportAttestations,
testBuildRegistryExportNoDefaultOCIArtifact,
testBuildTarExport,
testBuildMobyFromLocalImage,
testBuildDetailsLink,
Expand Down Expand Up @@ -633,6 +634,60 @@ func testBuildRegistryExportAttestations(t *testing.T, sb integration.Sandbox) {
require.Len(t, att.Layers, 1)
}

func testBuildRegistryExportNoDefaultOCIArtifact(t *testing.T, sb integration.Sandbox) {
if isMobyWorker(sb) {
t.Skip("attestations are not supported by the docker worker")
}

dir := createTestProject(t)

registry, err := sb.NewRegistry()
if errors.Is(err, integration.ErrRequirements) {
t.Skip(err.Error())
}
require.NoError(t, err)
target := registry + "/buildx/registry-no-default-oci-artifact:latest"

out, err := buildCmd(sb,
withEnv("BUILDX_NO_DEFAULT_OCI_ARTIFACT=true"),
withArgs(fmt.Sprintf("--output=type=image,name=%s,push=true", target), "--provenance=true", dir),
)
require.NoError(t, err, string(out))

requireLegacyAttestationStorage(t, sb, target)
}

func requireLegacyAttestationStorage(t *testing.T, sb integration.Sandbox, ref string) {
t.Helper()

cmd := buildxCmd(sb, withArgs("imagetools", "inspect", ref, "--raw"))
dt, err := cmd.CombinedOutput()
require.NoError(t, err, string(dt))

var idx ocispecs.Index
err = json.Unmarshal(dt, &idx)
require.NoError(t, err)

var attestation ocispecs.Descriptor
for _, desc := range idx.Manifests {
if desc.Annotations["vnd.docker.reference.type"] == "attestation-manifest" {
attestation = desc
break
}
}
require.NotEmpty(t, attestation.Digest)

cmd = buildxCmd(sb, withArgs("imagetools", "inspect", ref+"@"+attestation.Digest.String(), "--raw"))
dt, err = cmd.CombinedOutput()
require.NoError(t, err, string(dt))

var mfst ocispecs.Manifest
err = json.Unmarshal(dt, &mfst)
require.NoError(t, err)
require.Nil(t, mfst.Subject)
require.NotEmpty(t, mfst.Layers)
}

func testImageIDOutput(t *testing.T, sb integration.Sandbox) {
dockerfile := []byte(`FROM busybox:latest`)

Expand Down
Loading