From 6b834e0c26c443d3b47a9544f2b76d324116d457 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Tue, 27 May 2025 21:21:01 -0700 Subject: [PATCH 1/2] llbsolver: fix nil result error on multi-platform Signed-off-by: Tonis Tiigi --- .../dockerfile/dockerfile_provenance_test.go | 63 +++++++++++++++---- solver/llbsolver/solver.go | 1 + 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/frontend/dockerfile/dockerfile_provenance_test.go b/frontend/dockerfile/dockerfile_provenance_test.go index c9630db24ecf..a1b5b80abb78 100644 --- a/frontend/dockerfile/dockerfile_provenance_test.go +++ b/frontend/dockerfile/dockerfile_provenance_test.go @@ -1055,22 +1055,59 @@ ENV FOO=bar t, fstest.CreateFile("Dockerfile", dockerfile, 0600), ) - - _, err = f.Solve(sb.Context(), c, client.SolveOpt{ - LocalMounts: map[string]fsutil.FS{ - dockerui.DefaultLocalNameDockerfile: dir, - dockerui.DefaultLocalNameContext: dir, - }, - FrontendAttrs: map[string]string{ - "attest:provenance": "mode=max", - }, - Exports: []client.ExportEntry{ - { + buf := &bytes.Buffer{} + + exporters := []struct { + name string + export client.ExportEntry + }{ + { + name: "image", + export: client.ExportEntry{ Type: client.ExporterImage, }, }, - }, nil) - require.NoError(t, err) + { + name: "local", + export: client.ExportEntry{ + Type: client.ExporterLocal, + OutputDir: t.TempDir(), + }, + }, + { + name: "tar", + export: func() client.ExportEntry { + return client.ExportEntry{ + Type: client.ExporterTar, + Output: fixedWriteCloser(&nopWriteCloser{buf}), + } + }(), + }, + } + + for _, exp := range exporters { + for _, platformMode := range []string{"single", "multi"} { + t.Run(exp.name+"/"+platformMode, func(t *testing.T) { + attrs := map[string]string{ + "attest:provenance": "mode=max", + } + if platformMode == "multi" { + attrs["platform"] = "linux/amd64,linux/arm64" + } + _, err = f.Solve(sb.Context(), c, client.SolveOpt{ + LocalMounts: map[string]fsutil.FS{ + dockerui.DefaultLocalNameDockerfile: dir, + dockerui.DefaultLocalNameContext: dir, + }, + FrontendAttrs: attrs, + Exports: []client.ExportEntry{ + exp.export, + }, + }, nil) + require.NoError(t, err) + }) + } + } } // https://github.com/moby/buildkit/issues/3562 diff --git a/solver/llbsolver/solver.go b/solver/llbsolver/solver.go index 36fc7fd08502..f544785f3139 100644 --- a/solver/llbsolver/solver.go +++ b/solver/llbsolver/solver.go @@ -933,6 +933,7 @@ func addProvenanceToResult(res *frontend.Result, br *provenanceBridge) (*Result, } for k, ref := range res.Refs { if ref == nil { + out.Provenance.Refs[k] = nil continue } cp, err := getProvenance(ref, reqs.refs[k].bridge, k, reqs) From d5704f85f4d55eb75931676cdd44e4b668932ed4 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Tue, 27 May 2025 22:08:19 -0700 Subject: [PATCH 2/2] exporter: fix oci export of multi-platform nil result Signed-off-by: Tonis Tiigi --- exporter/oci/export.go | 3 ++ frontend/dockerfile/dockerfile_test.go | 61 ++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/exporter/oci/export.go b/exporter/oci/export.go index c6650b0a258b..0ba04d12314c 100644 --- a/exporter/oci/export.go +++ b/exporter/oci/export.go @@ -236,6 +236,9 @@ func (e *imageExporterInstance) Export(ctx context.Context, src *exporter.Source mprovider := contentutil.NewMultiProvider(e.opt.ImageWriter.ContentStore()) for _, ref := range refs { eg.Go(func() error { + if ref == nil { + return nil + } remotes, err := ref.GetRemotes(egCtx, false, e.opts.RefCfg, false, session.NewGroup(sessionID)) if err != nil { return err diff --git a/frontend/dockerfile/dockerfile_test.go b/frontend/dockerfile/dockerfile_test.go index 0eb3e59ca607..ec3dbe5657a7 100644 --- a/frontend/dockerfile/dockerfile_test.go +++ b/frontend/dockerfile/dockerfile_test.go @@ -201,6 +201,7 @@ var allTests = integration.TestFuncs( testDockerIgnoreMissingProvenance, testCommandSourceMapping, testSBOMScannerArgs, + testMultiNilRefsOCIExporter, testNilContextInSolveGateway, testMultiNilRefsInSolveGateway, testCopyUnicodePath, @@ -9237,6 +9238,66 @@ COPY --link foo foo } } +func testMultiNilRefsOCIExporter(t *testing.T, sb integration.Sandbox) { + integration.SkipOnPlatform(t, "windows") + workers.CheckFeatureCompat(t, sb, workers.FeatureMultiPlatform, workers.FeatureOCIExporter) + + f := getFrontend(t, sb) + + dockerfile := []byte(`FROM scratch`) + + dir := integration.Tmpdir( + t, + fstest.CreateFile("Dockerfile", dockerfile, 0600), + ) + + c, err := client.New(sb.Context(), sb.Address()) + require.NoError(t, err) + defer c.Close() + + destDir := t.TempDir() + + out := filepath.Join(destDir, "out.tar") + outW, err := os.Create(out) + require.NoError(t, err) + + _, err = f.Solve(sb.Context(), c, client.SolveOpt{ + LocalMounts: map[string]fsutil.FS{ + dockerui.DefaultLocalNameDockerfile: dir, + dockerui.DefaultLocalNameContext: dir, + }, + FrontendAttrs: map[string]string{ + "platform": "linux/arm64,linux/amd64", + }, + Exports: []client.ExportEntry{ + { + Type: client.ExporterOCI, + Output: fixedWriteCloser(outW), + }, + }, + }, nil) + require.NoError(t, err) + + dt, err := os.ReadFile(filepath.Join(destDir, "out.tar")) + require.NoError(t, err) + + m, err := testutil.ReadTarToMap(dt, false) + require.NoError(t, err) + + var idx ocispecs.Index + err = json.Unmarshal(m[ocispecs.ImageIndexFile].Data, &idx) + require.NoError(t, err) + + require.Equal(t, 1, len(idx.Manifests)) + mlistHex := idx.Manifests[0].Digest.Hex() + + idx = ocispecs.Index{} + err = json.Unmarshal(m[ocispecs.ImageBlobsDir+"/sha256/"+mlistHex].Data, &idx) + require.NoError(t, err) + + require.Equal(t, 2, len(idx.Manifests)) +} + func timeMustParse(t *testing.T, layout, value string) time.Time { tm, err := time.Parse(layout, value) require.NoError(t, err)