diff --git a/envbuilder.go b/envbuilder.go index bc94d89c..b082ca80 100644 --- a/envbuilder.go +++ b/envbuilder.go @@ -456,17 +456,18 @@ func Run(ctx context.Context, opts options.Options) error { } kOpts := &config.KanikoOptions{ // Boilerplate! - CustomPlatform: platforms.Format(platforms.Normalize(platforms.DefaultSpec())), - SnapshotMode: "redo", - RunV2: true, - RunStdout: stdoutWriter, - RunStderr: stderrWriter, - Destinations: destinations, - NoPush: !opts.PushImage || len(destinations) == 0, - CacheRunLayers: true, - CacheCopyLayers: true, - CompressedCaching: true, - Compression: config.ZStd, + CustomPlatform: platforms.Format(platforms.Normalize(platforms.DefaultSpec())), + SnapshotMode: "redo", + RunV2: true, + RunStdout: stdoutWriter, + RunStderr: stderrWriter, + Destinations: destinations, + NoPush: !opts.PushImage || len(destinations) == 0, + CacheRunLayers: true, + CacheCopyLayers: true, + ForceBuildMetadata: opts.PushImage, // Force layers with no changes to be cached, required for cache probing. + CompressedCaching: true, + Compression: config.ZStd, // Maps to "default" level, ~100-300 MB/sec according to // benchmarks in klauspost/compress README // https://github.com/klauspost/compress/blob/67a538e2b4df11f8ec7139388838a13bce84b5d5/zstd/encoder_options.go#L188 @@ -1180,17 +1181,18 @@ func RunCacheProbe(ctx context.Context, opts options.Options) (v1.Image, error) } kOpts := &config.KanikoOptions{ // Boilerplate! - CustomPlatform: platforms.Format(platforms.Normalize(platforms.DefaultSpec())), - SnapshotMode: "redo", - RunV2: true, - RunStdout: stdoutWriter, - RunStderr: stderrWriter, - Destinations: destinations, - NoPush: !opts.PushImage || len(destinations) == 0, - CacheRunLayers: true, - CacheCopyLayers: true, - CompressedCaching: true, - Compression: config.ZStd, + CustomPlatform: platforms.Format(platforms.Normalize(platforms.DefaultSpec())), + SnapshotMode: "redo", + RunV2: true, + RunStdout: stdoutWriter, + RunStderr: stderrWriter, + Destinations: destinations, + NoPush: true, + CacheRunLayers: true, + CacheCopyLayers: true, + ForceBuildMetadata: true, // Force layers with no changes to be cached, required for cache probing. + CompressedCaching: true, + Compression: config.ZStd, // Maps to "default" level, ~100-300 MB/sec according to // benchmarks in klauspost/compress README // https://github.com/klauspost/compress/blob/67a538e2b4df11f8ec7139388838a13bce84b5d5/zstd/encoder_options.go#L188 diff --git a/integration/integration_test.go b/integration/integration_test.go index f88829aa..42246f95 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -1312,6 +1312,56 @@ RUN date --utc > /root/date.txt`, testImageAlpine), require.NotEmpty(t, strings.TrimSpace(out)) }) + t.Run("CacheAndPushWithNoChangeLayers", func(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + srv := gittest.CreateGitServer(t, gittest.Options{ + Files: map[string]string{ + "Dockerfile": fmt.Sprintf(` +FROM %[1]s +RUN touch /foo +RUN echo "Hi, please don't put me in a layer (I guess you won't listen to me...)" +RUN touch /bar +`, testImageAlpine), + }, + }) + + // Given: an empty registry + testReg := setupInMemoryRegistry(t, setupInMemoryRegistryOpts{}) + testRepo := testReg + "/test" + ref, err := name.ParseReference(testRepo + ":latest") + require.NoError(t, err) + _, err = remote.Image(ref) + require.ErrorContains(t, err, "NAME_UNKNOWN", "expected image to not be present before build + push") + + opts := []string{ + envbuilderEnv("GIT_URL", srv.URL), + envbuilderEnv("CACHE_REPO", testRepo), + envbuilderEnv("DOCKERFILE_PATH", "Dockerfile"), + } + + // When: we run envbuilder with PUSH_IMAGE set + _ = pushImage(t, ref, nil, opts...) + + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + require.NoError(t, err) + defer cli.Close() + + // Then: re-running envbuilder with GET_CACHED_IMAGE should succeed + cachedRef := getCachedImage(ctx, t, cli, opts...) + + // When: we run the image we just built + ctr := startContainerFromRef(ctx, t, cli, cachedRef) + + // Then: the envbuilder binary exists in the image! + out := execContainer(t, ctr.ID, "/.envbuilder/bin/envbuilder --help") + require.Regexp(t, `(?s)^USAGE:\s+envbuilder`, strings.TrimSpace(out)) + require.NotEmpty(t, strings.TrimSpace(out)) + }) + t.Run("CacheAndPushAuth", func(t *testing.T) { t.Parallel()