diff --git a/go.mod b/go.mod index 1c2e25f3..e1d01738 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.22.4 // 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-20240815135021-647365bde8a7 +replace github.com/GoogleContainerTools/kaniko => github.com/coder/kaniko v0.0.0-20240830092517-0668f96c8816 // Required to import codersdk due to gvisor dependency. replace tailscale.com => github.com/coder/tailscale v1.1.1-0.20240702054557-aa558fbe5374 @@ -178,7 +178,6 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // indirect github.com/jsimonetti/rtnetlink v1.3.5 // indirect - github.com/karrick/godirwalk v1.16.1 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/compress v1.17.9 // indirect github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect @@ -246,6 +245,7 @@ require ( github.com/tailscale/wireguard-go v0.0.0-20231121184858-cc193a0b3272 // indirect github.com/tcnksm/go-httpstat v0.2.0 // indirect github.com/tinylib/msgp v1.1.8 // indirect + github.com/twpayne/go-vfs/v5 v5.0.4 // indirect github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a // indirect github.com/valyala/fasthttp v1.55.0 // indirect github.com/vbatts/tar-split v0.11.5 // indirect diff --git a/go.sum b/go.sum index 2b463e4f..6101dcf6 100644 --- a/go.sum +++ b/go.sum @@ -171,8 +171,8 @@ github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoC github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/coder/coder/v2 v2.10.1-0.20240704130443-c2d44d16a352 h1:L/EjCuZxs5tOcqqCaASj/nu65TRYEFcTt8qRQfHZXX0= github.com/coder/coder/v2 v2.10.1-0.20240704130443-c2d44d16a352/go.mod h1:P1KoQSgnKEAG6Mnd3YlGzAophty+yKA9VV48LpfNRvo= -github.com/coder/kaniko v0.0.0-20240815135021-647365bde8a7 h1:i5CTDhAUlZMXr4PdMz5RxTBiG0xltxj1npbEi1Ggzek= -github.com/coder/kaniko v0.0.0-20240815135021-647365bde8a7/go.mod h1:xlfIeo8SYBw3zwKb73wzz4Q5Q1wtnJy8ofYqGDAl/NA= +github.com/coder/kaniko v0.0.0-20240830092517-0668f96c8816 h1:idB8jAnkYWkHYddbJ+WnGtM2wrhh3+JpjPwHcQ2lYhU= +github.com/coder/kaniko v0.0.0-20240830092517-0668f96c8816/go.mod h1:XoTDIhNF0Ll4tLmRYdOn31udU9w5zFrY2PME/crSRCA= 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/quartz v0.1.0 h1:cLL+0g5l7xTf6ordRnUMMiZtRE8Sq5LxpghS63vEXrQ= @@ -489,8 +489,6 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= -github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= @@ -733,6 +731,8 @@ github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/twpayne/go-vfs/v5 v5.0.4 h1:/ne3h+rW7f5YOyOFguz+3ztfUwzOLR0Vts3y0mMAitg= +github.com/twpayne/go-vfs/v5 v5.0.4/go.mod h1:zTPFJUbgsEMFNSWnWQlLq9wh4AN83edZzx3VXbxrS1w= github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a h1:BH1SOPEvehD2kVrndDnGJiUF0TrBpNs+iyYocu6h0og= github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= diff --git a/integration/integration_test.go b/integration/integration_test.go index af051473..43d728c2 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -1485,22 +1485,26 @@ RUN date --utc > /root/date.txt`, testImageAlpine), }) t.Run("CacheAndPushMultistage", func(t *testing.T) { - // Currently fails with: - // /home/coder/src/coder/envbuilder/integration/integration_test.go:1417: "error: unable to get cached image: error fake building stage: failed to optimize instructions: failed to get files used from context: failed to get fileinfo for /.envbuilder/0/root/date.txt: lstat /.envbuilder/0/root/date.txt: no such file or directory" - // /home/coder/src/coder/envbuilder/integration/integration_test.go:1156: - // Error Trace: /home/coder/src/coder/envbuilder/integration/integration_test.go:1156 - // Error: Received unexpected error: - // error: unable to get cached image: error fake building stage: failed to optimize instructions: failed to get files used from context: failed to get fileinfo for /.envbuilder/0/root/date.txt: lstat /.envbuilder/0/root/date.txt: no such file or directory - // Test: TestPushImage/CacheAndPushMultistage - t.Skip("TODO: https://github.com/coder/envbuilder/issues/230") t.Parallel() srv := gittest.CreateGitServer(t, gittest.Options{ Files: map[string]string{ - "Dockerfile": fmt.Sprintf(`FROM %s AS a -RUN date --utc > /root/date.txt -FROM %s as b -COPY --from=a /root/date.txt /date.txt`, testImageAlpine, testImageAlpine), + "Dockerfile": fmt.Sprintf(` +FROM %[1]s AS prebuild +RUN mkdir /the-past /the-future \ + && echo "hello from the past" > /the-past/hello.txt \ + && cd /the-past \ + && ln -s hello.txt hello.link \ + && echo "hello from the future" > /the-future/hello.txt + +FROM %[1]s +USER root +ARG WORKDIR=/ +WORKDIR $WORKDIR +ENV FOO=bar +COPY --from=prebuild /the-past /the-past +COPY --from=prebuild /the-future/hello.txt /the-future/hello.txt +`, testImageAlpine), }, }) @@ -1525,16 +1529,122 @@ COPY --from=a /root/date.txt /date.txt`, testImageAlpine, testImageAlpine), 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, runOpts{env: []string{ + envbuilderEnv("GIT_URL", srv.URL), + envbuilderEnv("CACHE_REPO", testRepo), + envbuilderEnv("PUSH_IMAGE", "1"), + envbuilderEnv("DOCKERFILE_PATH", "Dockerfile"), + }}) + 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") + + // Then: re-running envbuilder with GET_CACHED_IMAGE should succeed ctrID, err := runEnvbuilder(t, runOpts{env: []string{ + envbuilderEnv("GIT_URL", srv.URL), + envbuilderEnv("CACHE_REPO", testRepo), + envbuilderEnv("GET_CACHED_IMAGE", "1"), + envbuilderEnv("DOCKERFILE_PATH", "Dockerfile"), + }}) + require.NoError(t, err) + + // 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() }) + _, err = io.ReadAll(rc) + require.NoError(t, err) + + // When: we run the image we just built + ctr, err := cli.ContainerCreate(ctx, &container.Config{ + Image: ref.String(), + Entrypoint: []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) + + // Then: The files from the prebuild stage are present. + out := execContainer(t, ctr.ID, "/bin/sh -c 'cat /the-past/hello.txt /the-future/hello.txt; readlink -f /the-past/hello.link'") + require.Equal(t, "hello from the past\nhello from the future\n/the-past/hello.txt", strings.TrimSpace(out)) + }) + + t.Run("MultistgeCacheMissAfterChange", func(t *testing.T) { + t.Parallel() + dockerfilePrebuildContents := fmt.Sprintf(` +FROM %[1]s AS prebuild +RUN mkdir /the-past /the-future \ + && echo "hello from the past" > /the-past/hello.txt \ + && cd /the-past \ + && ln -s hello.txt hello.link \ + && echo "hello from the future" > /the-future/hello.txt + +# Workaround for https://github.com/coder/envbuilder/issues/231 +FROM %[1]s +`, testImageAlpine) + + dockerfileContents := fmt.Sprintf(` +FROM %s +USER root +ARG WORKDIR=/ +WORKDIR $WORKDIR +ENV FOO=bar +COPY --from=prebuild /the-past /the-past +COPY --from=prebuild /the-future/hello.txt /the-future/hello.txt +RUN echo $FOO > /root/foo.txt +RUN date --utc > /root/date.txt +`, testImageAlpine) + + newServer := func(dockerfile string) *httptest.Server { + return gittest.CreateGitServer(t, gittest.Options{ + Files: map[string]string{"Dockerfile": dockerfile}, + }) + } + srv := newServer(dockerfilePrebuildContents + dockerfileContents) + + // 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") + + // When: we run envbuilder with PUSH_IMAGE set + _, err = runEnvbuilder(t, runOpts{env: []string{ envbuilderEnv("GIT_URL", srv.URL), envbuilderEnv("CACHE_REPO", testRepo), envbuilderEnv("PUSH_IMAGE", "1"), envbuilderEnv("DOCKERFILE_PATH", "Dockerfile"), }}) require.NoError(t, err) - // Then: The file copied from stage a should be present - out := execContainer(t, ctrID, "cat /date.txt") - require.NotEmpty(t, out) // Then: the image should be pushed _, err = remote.Image(ref) @@ -1548,6 +1658,33 @@ COPY --from=a /root/date.txt /date.txt`, testImageAlpine, testImageAlpine), envbuilderEnv("DOCKERFILE_PATH", "Dockerfile"), }}) require.NoError(t, err) + + // When: we change the Dockerfile + srv.Close() + dockerfilePrebuildContents = strings.Replace(dockerfilePrebuildContents, "hello from the future", "hello from the future, but different", 1) + srv = newServer(dockerfilePrebuildContents) + + // When: we rebuild the prebuild stage so that the cache is created + _, err = runEnvbuilder(t, runOpts{env: []string{ + envbuilderEnv("GIT_URL", srv.URL), + envbuilderEnv("CACHE_REPO", testRepo), + envbuilderEnv("PUSH_IMAGE", "1"), + envbuilderEnv("DOCKERFILE_PATH", "Dockerfile"), + }}) + require.NoError(t, err) + + // Then: re-running envbuilder with GET_CACHED_IMAGE should still fail + // on the second stage because the first stage file has changed. + srv.Close() + srv = newServer(dockerfilePrebuildContents + dockerfileContents) + _, err = runEnvbuilder(t, runOpts{env: []string{ + envbuilderEnv("GIT_URL", srv.URL), + envbuilderEnv("CACHE_REPO", testRepo), + envbuilderEnv("GET_CACHED_IMAGE", "1"), + envbuilderEnv("DOCKERFILE_PATH", "Dockerfile"), + envbuilderEnv("VERBOSE", "1"), + }}) + require.ErrorContains(t, err, "error probing build cache: uncached COPY command") }) t.Run("PushImageRequiresCache", func(t *testing.T) {