Skip to content

Commit d1eae8f

Browse files
committed
add test for multi stage cache invalidation
1 parent 46cad9c commit d1eae8f

File tree

1 file changed

+152
-7
lines changed

1 file changed

+152
-7
lines changed

integration/integration_test.go

+152-7
Original file line numberDiff line numberDiff line change
@@ -1489,10 +1489,22 @@ RUN date --utc > /root/date.txt`, testImageAlpine),
14891489

14901490
srv := gittest.CreateGitServer(t, gittest.Options{
14911491
Files: map[string]string{
1492-
"Dockerfile": fmt.Sprintf(`FROM %s AS a
1493-
RUN date --utc > /root/date.txt
1494-
FROM %s as b
1495-
COPY --from=a /root/date.txt /date.txt`, testImageAlpine, testImageAlpine),
1492+
"Dockerfile": fmt.Sprintf(`
1493+
FROM %[1]s AS prebuild
1494+
RUN mkdir /the-past /the-future \
1495+
&& echo "hello from the past" > /the-past/hello.txt \
1496+
&& cd /the-past \
1497+
&& ln -s hello.txt hello.link \
1498+
&& echo "hello from the future" > /the-future/hello.txt
1499+
1500+
FROM %[1]s
1501+
USER root
1502+
ARG WORKDIR=/
1503+
WORKDIR $WORKDIR
1504+
ENV FOO=bar
1505+
COPY --from=prebuild /the-past /the-past
1506+
COPY --from=prebuild /the-future/hello.txt /the-future/hello.txt
1507+
`, testImageAlpine),
14961508
},
14971509
})
14981510

@@ -1517,16 +1529,122 @@ COPY --from=a /root/date.txt /date.txt`, testImageAlpine, testImageAlpine),
15171529
require.ErrorContains(t, err, "NAME_UNKNOWN", "expected image to not be present before build + push")
15181530

15191531
// When: we run envbuilder with PUSH_IMAGE set
1532+
_, err = runEnvbuilder(t, runOpts{env: []string{
1533+
envbuilderEnv("GIT_URL", srv.URL),
1534+
envbuilderEnv("CACHE_REPO", testRepo),
1535+
envbuilderEnv("PUSH_IMAGE", "1"),
1536+
envbuilderEnv("DOCKERFILE_PATH", "Dockerfile"),
1537+
}})
1538+
require.NoError(t, err)
1539+
1540+
// Then: the image should be pushed
1541+
_, err = remote.Image(ref)
1542+
require.NoError(t, err, "expected image to be present after build + push")
1543+
1544+
// Then: re-running envbuilder with GET_CACHED_IMAGE should succeed
15201545
ctrID, err := runEnvbuilder(t, runOpts{env: []string{
1546+
envbuilderEnv("GIT_URL", srv.URL),
1547+
envbuilderEnv("CACHE_REPO", testRepo),
1548+
envbuilderEnv("GET_CACHED_IMAGE", "1"),
1549+
envbuilderEnv("DOCKERFILE_PATH", "Dockerfile"),
1550+
}})
1551+
require.NoError(t, err)
1552+
1553+
// Then: the cached image ref should be emitted in the container logs
1554+
ctx, cancel := context.WithCancel(context.Background())
1555+
t.Cleanup(cancel)
1556+
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
1557+
require.NoError(t, err)
1558+
defer cli.Close()
1559+
logs, err := cli.ContainerLogs(ctx, ctrID, container.LogsOptions{
1560+
ShowStdout: true,
1561+
ShowStderr: true,
1562+
})
1563+
require.NoError(t, err)
1564+
defer logs.Close()
1565+
logBytes, err := io.ReadAll(logs)
1566+
require.NoError(t, err)
1567+
require.Regexp(t, `ENVBUILDER_CACHED_IMAGE=(\S+)`, string(logBytes))
1568+
1569+
// When: we pull the image we just built
1570+
rc, err := cli.ImagePull(ctx, ref.String(), image.PullOptions{})
1571+
require.NoError(t, err)
1572+
t.Cleanup(func() { _ = rc.Close() })
1573+
_, err = io.ReadAll(rc)
1574+
require.NoError(t, err)
1575+
1576+
// When: we run the image we just built
1577+
ctr, err := cli.ContainerCreate(ctx, &container.Config{
1578+
Image: ref.String(),
1579+
Entrypoint: []string{"sleep", "infinity"},
1580+
Labels: map[string]string{
1581+
testContainerLabel: "true",
1582+
},
1583+
}, nil, nil, nil, "")
1584+
require.NoError(t, err)
1585+
t.Cleanup(func() {
1586+
_ = cli.ContainerRemove(ctx, ctr.ID, container.RemoveOptions{
1587+
RemoveVolumes: true,
1588+
Force: true,
1589+
})
1590+
})
1591+
err = cli.ContainerStart(ctx, ctr.ID, container.StartOptions{})
1592+
require.NoError(t, err)
1593+
1594+
// Then: The files from the prebuild stage are present.
1595+
out := execContainer(t, ctr.ID, "/bin/sh -c 'cat /the-past/hello.txt /the-future/hello.txt; readlink -f /the-past/hello.link'")
1596+
require.Equal(t, "hello from the past\nhello from the future\n/the-past/hello.txt", strings.TrimSpace(out))
1597+
})
1598+
1599+
t.Run("MultistgeCacheMissAfterChange", func(t *testing.T) {
1600+
t.Parallel()
1601+
dockerfilePrebuildContents := fmt.Sprintf(`
1602+
FROM %[1]s AS prebuild
1603+
RUN mkdir /the-past /the-future \
1604+
&& echo "hello from the past" > /the-past/hello.txt \
1605+
&& cd /the-past \
1606+
&& ln -s hello.txt hello.link \
1607+
&& echo "hello from the future" > /the-future/hello.txt
1608+
1609+
# Workaround for https://github.com/coder/envbuilder/issues/231
1610+
FROM %[1]s
1611+
`, testImageAlpine)
1612+
1613+
dockerfileContents := fmt.Sprintf(`
1614+
FROM %s
1615+
USER root
1616+
ARG WORKDIR=/
1617+
WORKDIR $WORKDIR
1618+
ENV FOO=bar
1619+
COPY --from=prebuild /the-past /the-past
1620+
COPY --from=prebuild /the-future/hello.txt /the-future/hello.txt
1621+
RUN echo $FOO > /root/foo.txt
1622+
RUN date --utc > /root/date.txt
1623+
`, testImageAlpine)
1624+
1625+
newServer := func(dockerfile string) *httptest.Server {
1626+
return gittest.CreateGitServer(t, gittest.Options{
1627+
Files: map[string]string{"Dockerfile": dockerfile},
1628+
})
1629+
}
1630+
srv := newServer(dockerfilePrebuildContents + dockerfileContents)
1631+
1632+
// Given: an empty registry
1633+
testReg := setupInMemoryRegistry(t, setupInMemoryRegistryOpts{})
1634+
testRepo := testReg + "/test"
1635+
ref, err := name.ParseReference(testRepo + ":latest")
1636+
require.NoError(t, err)
1637+
_, err = remote.Image(ref)
1638+
require.ErrorContains(t, err, "NAME_UNKNOWN", "expected image to not be present before build + push")
1639+
1640+
// When: we run envbuilder with PUSH_IMAGE set
1641+
_, err = runEnvbuilder(t, runOpts{env: []string{
15211642
envbuilderEnv("GIT_URL", srv.URL),
15221643
envbuilderEnv("CACHE_REPO", testRepo),
15231644
envbuilderEnv("PUSH_IMAGE", "1"),
15241645
envbuilderEnv("DOCKERFILE_PATH", "Dockerfile"),
15251646
}})
15261647
require.NoError(t, err)
1527-
// Then: The file copied from stage a should be present
1528-
out := execContainer(t, ctrID, "cat /date.txt")
1529-
require.NotEmpty(t, out)
15301648

15311649
// Then: the image should be pushed
15321650
_, err = remote.Image(ref)
@@ -1540,6 +1658,33 @@ COPY --from=a /root/date.txt /date.txt`, testImageAlpine, testImageAlpine),
15401658
envbuilderEnv("DOCKERFILE_PATH", "Dockerfile"),
15411659
}})
15421660
require.NoError(t, err)
1661+
1662+
// When: we change the Dockerfile
1663+
srv.Close()
1664+
dockerfilePrebuildContents = strings.Replace(dockerfilePrebuildContents, "hello from the future", "hello from the future, but different", 1)
1665+
srv = newServer(dockerfilePrebuildContents)
1666+
1667+
// When: we rebuild the prebuild stage so that the cache is created
1668+
_, err = runEnvbuilder(t, runOpts{env: []string{
1669+
envbuilderEnv("GIT_URL", srv.URL),
1670+
envbuilderEnv("CACHE_REPO", testRepo),
1671+
envbuilderEnv("PUSH_IMAGE", "1"),
1672+
envbuilderEnv("DOCKERFILE_PATH", "Dockerfile"),
1673+
}})
1674+
require.NoError(t, err)
1675+
1676+
// Then: re-running envbuilder with GET_CACHED_IMAGE should still fail
1677+
// on the second stage because the first stage file has changed.
1678+
srv.Close()
1679+
srv = newServer(dockerfilePrebuildContents + dockerfileContents)
1680+
_, err = runEnvbuilder(t, runOpts{env: []string{
1681+
envbuilderEnv("GIT_URL", srv.URL),
1682+
envbuilderEnv("CACHE_REPO", testRepo),
1683+
envbuilderEnv("GET_CACHED_IMAGE", "1"),
1684+
envbuilderEnv("DOCKERFILE_PATH", "Dockerfile"),
1685+
envbuilderEnv("VERBOSE", "1"),
1686+
}})
1687+
require.ErrorContains(t, err, "error probing build cache: uncached COPY command")
15431688
})
15441689

15451690
t.Run("PushImageRequiresCache", func(t *testing.T) {

0 commit comments

Comments
 (0)