diff --git a/README.md b/README.md index 87d1513..aa5c5e6 100644 --- a/README.md +++ b/README.md @@ -226,7 +226,30 @@ docker run -it --rm \ Each layer is stored in the registry as a separate image. The image tag is the hash of the layer's contents. The image digest is the hash of the image tag. The image digest is used to pull the layer from the registry. -The performance improvement of builds depends on the complexity of your Dockerfile. For [`coder/coder`](https://github.com/coder/coder/blob/main/.devcontainer/Dockerfile), uncached builds take 36m while cached builds take 40s (~98% improvement). +The performance improvement of builds depends on the complexity of your +Dockerfile. For +[`coder/coder`](https://github.com/coder/coder/blob/main/.devcontainer/Dockerfile), +uncached builds take 36m while cached builds take 40s (~98% improvement). + +## Pushing the built image + +Set `ENVBUILDER_PUSH_IMAGE=1` to push the entire image to the cache repo +in addition to individual layers. `ENVBUILDER_CACHE_REPO` **must** be set in +order for this to work. + +> **Note:** this option forces Envbuilder to perform a "reproducible" build. +> This will force timestamps for all newly added files to be set to the start of the UNIX epoch. + +## Probe Layer Cache + +To check for the presence of a pre-built image, set +`ENVBUILDER_GET_CACHED_IMAGE=1`. Instead of building the image, this will +perform a "dry-run" build of the image, consulting `ENVBUILDER_CACHE_REPO` for +each layer. + +If any layer is found not to be present in the cache repo, envbuilder +will exit with an error. Otherwise, the image will be emitted in the log output prefixed with the string +`ENVBUILDER_CACHED_IMAGE=...`. ## Image Caching diff --git a/envbuilder.go b/envbuilder.go index fdbfde9..5538ea9 100644 --- a/envbuilder.go +++ b/envbuilder.go @@ -566,7 +566,7 @@ ENTRYPOINT [%q]`, exePath, exePath, exePath) return nil, xerrors.Errorf("get cached image digest: %w", err) } endStage("🏗️ Found cached image!") - _, _ = fmt.Fprintf(os.Stdout, "%s@%s\n", options.CacheRepo, digest.String()) + _, _ = fmt.Fprintf(os.Stdout, "ENVBUILDER_CACHED_IMAGE=%s@%s\n", options.CacheRepo, digest.String()) os.Exit(0) } diff --git a/integration/integration_test.go b/integration/integration_test.go index decc5bd..1364e96 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -1172,19 +1172,30 @@ func TestPushImage(t *testing.T) { } // Then: re-running envbuilder with GET_CACHED_IMAGE should succeed - _, err = runEnvbuilder(t, options{env: []string{ + ctrID, err := runEnvbuilder(t, options{env: []string{ envbuilderEnv("GIT_URL", srv.URL), envbuilderEnv("CACHE_REPO", testRepo), envbuilderEnv("GET_CACHED_IMAGE", "1"), }}) require.NoError(t, err) - // When: we pull the image we just built + // Then: the cached image ref should be emitted in the container logs ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) require.NoError(t, err) defer cli.Close() + logs, err := cli.ContainerLogs(ctx, ctrID, container.LogsOptions{ + ShowStdout: true, + ShowStderr: true, + }) + require.NoError(t, err) + defer logs.Close() + logBytes, err := io.ReadAll(logs) + require.NoError(t, err) + require.Regexp(t, `ENVBUILDER_CACHED_IMAGE=(\S+)`, string(logBytes)) + + // When: we pull the image we just built rc, err := cli.ImagePull(ctx, ref.String(), image.PullOptions{}) require.NoError(t, err) t.Cleanup(func() { _ = rc.Close() })