From d53e46af9b45b3eda49665b04b0aa5ceaa8fdb0c Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Fri, 17 May 2024 16:29:02 +0000 Subject: [PATCH 1/4] feat: push final image to cache repo This commit enables pushing the final image to the given cache repo.a As part of #185, the goal is to allow for faster startup when the image has already been built. --- envbuilder.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/envbuilder.go b/envbuilder.go index 93d9e731..d2c43052 100644 --- a/envbuilder.go +++ b/envbuilder.go @@ -480,14 +480,18 @@ func Run(ctx context.Context, options Options) error { if val, ok := os.LookupEnv("KANIKO_REGISTRY_MIRROR"); ok { registryMirror = strings.Split(val, ";") } - image, err := executor.DoBuild(&config.KanikoOptions{ + var destinations []string + if options.CacheRepo != "" { + destinations = append(destinations, options.CacheRepo) + } + opts := &config.KanikoOptions{ // Boilerplate! CustomPlatform: platforms.Format(platforms.Normalize(platforms.DefaultSpec())), SnapshotMode: "redo", RunV2: true, RunStdout: stdoutWriter, RunStderr: stderrWriter, - Destinations: []string{"local"}, + Destinations: destinations, CacheRunLayers: true, CacheCopyLayers: true, CompressedCaching: true, @@ -518,11 +522,16 @@ func Run(ctx context.Context, options Options) error { RegistryMirrors: registryMirror, }, SrcContext: buildParams.BuildContext, - }) + } + image, err := executor.DoBuild(opts) if err != nil { return nil, err } + if err := executor.DoPush(image, opts); err != nil { + return nil, err + } endStage("🏗️ Built image!") + return image, err } From 0765f696dc4e2a250617c2fa9a6061ac5bc4ed38 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 20 May 2024 11:03:49 +0000 Subject: [PATCH 2/4] explicitly set nopush if no destinations --- envbuilder.go | 1 + 1 file changed, 1 insertion(+) diff --git a/envbuilder.go b/envbuilder.go index d2c43052..a76b7467 100644 --- a/envbuilder.go +++ b/envbuilder.go @@ -492,6 +492,7 @@ func Run(ctx context.Context, options Options) error { RunStdout: stdoutWriter, RunStderr: stderrWriter, Destinations: destinations, + NoPush: len(destinations) == 0, CacheRunLayers: true, CacheCopyLayers: true, CompressedCaching: true, From f416b7b292079036d7112cf3e804444330da0ce5 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 20 May 2024 11:09:31 +0000 Subject: [PATCH 3/4] wrap --- envbuilder.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/envbuilder.go b/envbuilder.go index a76b7467..aac63bc6 100644 --- a/envbuilder.go +++ b/envbuilder.go @@ -526,10 +526,10 @@ func Run(ctx context.Context, options Options) error { } image, err := executor.DoBuild(opts) if err != nil { - return nil, err + return nil, xerrors.Errorf("do build: %w", err) } if err := executor.DoPush(image, opts); err != nil { - return nil, err + return nil, xerrors.Errorf("do push: %w", err) } endStage("🏗️ Built image!") From d008cc0ef8c149d4d70e14c4bc1200a6b9aac239 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Tue, 11 Jun 2024 14:03:59 +0100 Subject: [PATCH 4/4] chore: add integration tests for pushing image to cache repo --- integration/integration_test.go | 80 +++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/integration/integration_test.go b/integration/integration_test.go index 8733788d..8456139c 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -34,6 +34,8 @@ import ( "github.com/go-git/go-billy/v5/memfs" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/registry" + "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/remote/transport" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -987,6 +989,84 @@ COPY %s .`, testImageAlpine, inclFile) } } +func TestPushImage(t *testing.T) { + t.Parallel() + + t.Run("Push", func(t *testing.T) { + t.Parallel() + + srv := createGitServer(t, gitServerOptions{ + files: map[string]string{ + ".devcontainer/Dockerfile": fmt.Sprintf("FROM %s\nRUN date --utc > /root/date.txt", testImageAlpine), + ".devcontainer/devcontainer.json": `{ + "name": "Test", + "build": { + "dockerfile": "Dockerfile" + }, + }`, + }, + }) + + // Given: an empty registry + testReg := setupInMemoryRegistry(t) + 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") + + // When: we run envbuilder with PUSH_IMAGE set + _, err = runEnvbuilder(t, options{env: []string{ + envbuilderEnv("GIT_URL", srv.URL), + envbuilderEnv("CACHE_REPO", testRepo), + }}) + require.NoError(t, err) + + // Then: the image should be pushed + _, err = remote.Image(ref) + require.NoError(t, err, "expected image to be present after build + push") + }) + + t.Run("PushErr", func(t *testing.T) { + t.Parallel() + + srv := createGitServer(t, gitServerOptions{ + files: map[string]string{ + ".devcontainer/Dockerfile": fmt.Sprintf("FROM %s\nRUN date --utc > /root/date.txt", testImageAlpine), + ".devcontainer/devcontainer.json": `{ + "name": "Test", + "build": { + "dockerfile": "Dockerfile" + }, + }`, + }, + }) + + // Given: registry is not set up (in this case, not a registry) + notRegSrv := httptest.NewServer(http.NotFoundHandler()) + notRegURL := strings.TrimPrefix(notRegSrv.URL, "http://") + "/test" + + // When: we run envbuilder with PUSH_IMAGE set + _, err := runEnvbuilder(t, options{env: []string{ + envbuilderEnv("GIT_URL", srv.URL), + envbuilderEnv("CACHE_REPO", notRegURL), + }}) + // Then: should fail with a descriptive error + require.ErrorContains(t, err, "unexpected status code 404 Not Found") + }) +} + +func setupInMemoryRegistry(t *testing.T) string { + t.Helper() + tempDir := t.TempDir() + testReg := registry.New(registry.WithBlobHandler(registry.NewDiskBlobHandler(tempDir))) + regSrv := httptest.NewServer(testReg) + t.Cleanup(func() { regSrv.Close() }) + regSrvURL, err := url.Parse(regSrv.URL) + require.NoError(t, err) + return fmt.Sprintf("localhost:%s", regSrvURL.Port()) +} + // TestMain runs before all tests to build the envbuilder image. func TestMain(m *testing.M) { checkTestRegistry()