@@ -1485,22 +1485,26 @@ RUN date --utc > /root/date.txt`, testImageAlpine),
14851485 })
14861486
14871487 t .Run ("CacheAndPushMultistage" , func (t * testing.T ) {
1488- // Currently fails with:
1489- // /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"
1490- // /home/coder/src/coder/envbuilder/integration/integration_test.go:1156:
1491- // Error Trace: /home/coder/src/coder/envbuilder/integration/integration_test.go:1156
1492- // Error: Received unexpected error:
1493- // 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
1494- // Test: TestPushImage/CacheAndPushMultistage
1495- t .Skip ("TODO: https://github.com/coder/envbuilder/issues/230" )
14961488 t .Parallel ()
14971489
14981490 srv := gittest .CreateGitServer (t , gittest.Options {
14991491 Files : map [string ]string {
1500- "Dockerfile" : fmt .Sprintf (`FROM %s AS a
1501- RUN date --utc > /root/date.txt
1502- FROM %s as b
1503- 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 ),
15041508 },
15051509 })
15061510
@@ -1525,16 +1529,122 @@ COPY --from=a /root/date.txt /date.txt`, testImageAlpine, testImageAlpine),
15251529 require .ErrorContains (t , err , "NAME_UNKNOWN" , "expected image to not be present before build + push" )
15261530
15271531 // 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
15281545 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\n hello 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 {
15291642 envbuilderEnv ("GIT_URL" , srv .URL ),
15301643 envbuilderEnv ("CACHE_REPO" , testRepo ),
15311644 envbuilderEnv ("PUSH_IMAGE" , "1" ),
15321645 envbuilderEnv ("DOCKERFILE_PATH" , "Dockerfile" ),
15331646 }})
15341647 require .NoError (t , err )
1535- // Then: The file copied from stage a should be present
1536- out := execContainer (t , ctrID , "cat /date.txt" )
1537- require .NotEmpty (t , out )
15381648
15391649 // Then: the image should be pushed
15401650 _ , err = remote .Image (ref )
@@ -1548,6 +1658,33 @@ COPY --from=a /root/date.txt /date.txt`, testImageAlpine, testImageAlpine),
15481658 envbuilderEnv ("DOCKERFILE_PATH" , "Dockerfile" ),
15491659 }})
15501660 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" )
15511688 })
15521689
15531690 t .Run ("PushImageRequiresCache" , func (t * testing.T ) {
0 commit comments