From 08f3f4f2f683394b71f92b2829441fbded54af23 Mon Sep 17 00:00:00 2001 From: "Paul \"TBBle\" Hampson" Date: Sat, 14 Oct 2023 18:44:17 +0900 Subject: [PATCH] Skip export of caches with no layers to OCI structures These happen when a container image is built using only metadata actions, and hence does not generate any filesystem changes. Such a degenerate cache is not intersting, and some OCI registries reject OCI images with no layers. Specifically, this only affects the `registry` and `local` exporters. The `inline` exporter already early-outs in this case with the same warning, and the `gha`, `s3`, and `azblob` exporters are unchanged. Signed-off-by: Paul "TBBle" Hampson --- cache/remotecache/export.go | 6 ++++ client/client_test.go | 65 +++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/cache/remotecache/export.go b/cache/remotecache/export.go index fbb475132d6cb..64347eb0865fe 100644 --- a/cache/remotecache/export.go +++ b/cache/remotecache/export.go @@ -11,6 +11,7 @@ import ( v1 "github.com/moby/buildkit/cache/remotecache/v1" "github.com/moby/buildkit/session" "github.com/moby/buildkit/solver" + "github.com/moby/buildkit/util/bklog" "github.com/moby/buildkit/util/compression" "github.com/moby/buildkit/util/contentutil" "github.com/moby/buildkit/util/progress" @@ -185,6 +186,11 @@ func (ce *contentCacheExporter) Finalize(ctx context.Context) (map[string]string return nil, err } + if len(config.Layers) == 0 { + bklog.G(ctx).Warn("failed to match any cache with layers") + return nil, progress.OneOff(ctx, "skipping cache export for empty result")(nil) + } + cache, err := NewExportableCache(ce.oci, ce.imageManifest) if err != nil { return nil, err diff --git a/client/client_test.go b/client/client_test.go index 45f68c5dc367e..fe585071e4d77 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -206,6 +206,7 @@ func TestIntegration(t *testing.T) { testLLBMountPerformance, testClientCustomGRPCOpts, testMultipleRecordsWithSameLayersCacheImportExport, + testRegistryEmptyCacheExport, testSnapshotWithMultipleBlobs, testExportLocalNoPlatformSplit, testExportLocalNoPlatformSplitOverwrite, @@ -5437,6 +5438,70 @@ func testBasicGhaCacheImportExport(t *testing.T, sb integration.Sandbox) { testBasicCacheImportExport(t, sb, []CacheOptionsEntry{im}, []CacheOptionsEntry{ex}) } +func testRegistryEmptyCacheExport(t *testing.T, sb integration.Sandbox) { + requiresLinux(t) + + workers.CheckFeatureCompat(t, sb, + workers.FeatureCacheExport, + workers.FeatureCacheBackendRegistry, + ) + + for _, ociMediaTypes := range []bool{true, false} { + ociMediaTypes := ociMediaTypes + for _, imageManifest := range []bool{true, false} { + imageManifest := imageManifest + if imageManifest && !ociMediaTypes { + // invalid configuration for remote cache + continue + } + + t.Run(t.Name()+fmt.Sprintf("/ociMediaTypes=%t/imageManifest=%t", ociMediaTypes, imageManifest), func(t *testing.T) { + c, err := New(sb.Context(), sb.Address()) + require.NoError(t, err) + defer c.Close() + + st := llb.Scratch() + def, err := st.Marshal(sb.Context()) + require.NoError(t, err) + + registry, err := sb.NewRegistry() + if errors.Is(err, integration.ErrRequirements) { + t.Skip(err.Error()) + } + require.NoError(t, err) + + cacheTarget := registry + "/buildkit/testregistryemptycache:latest" + + cacheOptionsEntry := CacheOptionsEntry{ + Type: "registry", + Attrs: map[string]string{ + "ref": cacheTarget, + "image-manifest": strconv.FormatBool(imageManifest), + "oci-mediatypes": strconv.FormatBool(ociMediaTypes), + }, + } + + _, err = c.Solve(sb.Context(), def, SolveOpt{ + CacheExports: []CacheOptionsEntry{cacheOptionsEntry}, + }, nil) + require.NoError(t, err) + + ctx := namespaces.WithNamespace(sb.Context(), "buildkit") + cdAddress := sb.ContainerdAddress() + var client *containerd.Client + if cdAddress != "" { + client, err = newContainerd(cdAddress) + require.NoError(t, err) + defer client.Close() + + _, err := client.Fetch(ctx, cacheTarget) + require.ErrorIs(t, err, ctderrdefs.ErrNotFound, "unexpected error %v", err) + } + }) + } + } +} + func testMultipleRecordsWithSameLayersCacheImportExport(t *testing.T, sb integration.Sandbox) { workers.CheckFeatureCompat(t, sb, workers.FeatureCacheExport,