diff --git a/envbuilder.go b/envbuilder.go index 6fbc4f04..889db1e4 100644 --- a/envbuilder.go +++ b/envbuilder.go @@ -404,9 +404,29 @@ func Run(ctx context.Context, options Options) error { util.AddToDefaultIgnoreList(util.IgnoreListEntry{ Path: ignorePath, PrefixMatchOnly: false, + AllowedPaths: nil, }) } + // In order to allow 'resuming' envbuilder, embed the binary into the image + // if it is being pushed + if options.PushImage { + exePath, err := os.Executable() + if err != nil { + return xerrors.Errorf("get exe path: %w", err) + } + // Add an exception for the current running binary in kaniko ignore list + if err := util.AddAllowedPathToDefaultIgnoreList(exePath); err != nil { + return xerrors.Errorf("add exe path to ignore list: %w", err) + } + // Copy the envbuilder binary into the build context. + buildParams.DockerfileContent += fmt.Sprintf("\nCOPY %s %s", exePath, exePath) + dst := filepath.Join(buildParams.BuildContext, exePath) + if err := copyFile(exePath, dst); err != nil { + return xerrors.Errorf("copy running binary to build context: %w", err) + } + } + // temp move of all ro mounts tempRemountDest := filepath.Join("/", MagicDir, "mnt") ignorePrefixes := []string{tempRemountDest, "/proc", "/sys"} @@ -1182,3 +1202,21 @@ func maybeDeleteFilesystem(log LoggerFunc, force bool) error { return util.DeleteFilesystem() } + +func copyFile(src, dst string) error { + content, err := os.ReadFile(src) + if err != nil { + return xerrors.Errorf("read file failed: %w", err) + } + + err = os.MkdirAll(filepath.Dir(dst), 0o755) + if err != nil { + return xerrors.Errorf("mkdir all failed: %w", err) + } + + err = os.WriteFile(dst, content, 0o644) + if err != nil { + return xerrors.Errorf("write file failed: %w", err) + } + return nil +} diff --git a/go.mod b/go.mod index e806943d..c831fdfc 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.22.3 // There are a few options we need added to Kaniko! // See: https://github.com/GoogleContainerTools/kaniko/compare/main...coder:kaniko:main -replace github.com/GoogleContainerTools/kaniko => github.com/coder/kaniko v0.0.0-20240612094751-9d2f7eaa733c +replace github.com/GoogleContainerTools/kaniko => github.com/coder/kaniko v0.0.0-20240624091120-7208a49f5b15 require ( cdr.dev/slog v1.6.2-0.20240126064726-20367d4aede6 diff --git a/go.sum b/go.sum index 1e3db752..ee16941c 100644 --- a/go.sum +++ b/go.sum @@ -126,8 +126,8 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= -github.com/coder/kaniko v0.0.0-20240612094751-9d2f7eaa733c h1:m/cK7QW+IIydq+7zmuGesY1k6CEZlKooSF+KtIcXke8= -github.com/coder/kaniko v0.0.0-20240612094751-9d2f7eaa733c/go.mod h1:YMK7BlxerzLlMwihGxNWUaFoN9LXCij4P+w/8/fNlcM= +github.com/coder/kaniko v0.0.0-20240624091120-7208a49f5b15 h1:Rne2frxrqtLEQ/v4f/wS550Yp/WXLCRFzDuxg8b9woM= +github.com/coder/kaniko v0.0.0-20240624091120-7208a49f5b15/go.mod h1:YMK7BlxerzLlMwihGxNWUaFoN9LXCij4P+w/8/fNlcM= github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 h1:3A0ES21Ke+FxEM8CXx9n47SZOKOpgSE1bbJzlE4qPVs= github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0/go.mod h1:5UuS2Ts+nTToAMeOjNlnHFkPahrtDkmpydBen/3wgZc= github.com/coder/retry v1.5.1 h1:iWu8YnD8YqHs3XwqrqsjoBTAVqT9ml6z9ViJ2wlMiqc= diff --git a/integration/integration_test.go b/integration/integration_test.go index caacad56..aed4adae 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -30,6 +30,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/volume" "github.com/docker/docker/client" @@ -1396,6 +1397,74 @@ COPY --from=a /root/date.txt /date.txt`, testImageAlpine, testImageAlpine), }) } +func TestEmbedBinaryImage(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" + }, + }`, + }, + }) + + testReg := setupInMemoryRegistry(t, setupInMemoryRegistryOpts{}) + testRepo := testReg + "/test-embed-binary-image" + ref, err := name.ParseReference(testRepo + ":latest") + require.NoError(t, err) + + _, err = runEnvbuilder(t, options{env: []string{ + envbuilderEnv("GIT_URL", srv.URL), + envbuilderEnv("CACHE_REPO", testRepo), + envbuilderEnv("PUSH_IMAGE", "1"), + }}) + require.NoError(t, err) + + _, err = remote.Image(ref) + require.NoError(t, err, "expected image to be present after build + push") + + ctx := context.Background() + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + require.NoError(t, err) + t.Cleanup(func() { + cli.Close() + }) + + // Pull the image we just built + rc, err := cli.ImagePull(ctx, ref.String(), image.PullOptions{}) + require.NoError(t, err) + t.Cleanup(func() { _ = rc.Close() }) + _, err = io.ReadAll(rc) + require.NoError(t, err) + + // Run it + ctr, err := cli.ContainerCreate(ctx, &container.Config{ + Image: ref.String(), + Cmd: []string{"sleep", "infinity"}, + Labels: map[string]string{ + testContainerLabel: "true", + }, + }, nil, nil, nil, "") + require.NoError(t, err) + t.Cleanup(func() { + _ = cli.ContainerRemove(ctx, ctr.ID, container.RemoveOptions{ + RemoveVolumes: true, + Force: true, + }) + }) + err = cli.ContainerStart(ctx, ctr.ID, container.StartOptions{}) + require.NoError(t, err) + + out := execContainer(t, ctr.ID, "[[ -f \"/.envbuilder/bin/envbuilder\" ]] && echo \"exists\"") + require.Equal(t, "exists", strings.TrimSpace(out)) + out = execContainer(t, ctr.ID, "cat /root/date.txt") + require.NotEmpty(t, strings.TrimSpace(out)) +} + func TestChownHomedir(t *testing.T) { t.Parallel()