@@ -18,6 +18,7 @@ import (
1818 "os/exec"
1919 "path/filepath"
2020 "regexp"
21+ "slices"
2122 "strings"
2223 "testing"
2324 "time"
@@ -39,6 +40,7 @@ import (
3940 "github.com/docker/docker/api/types/volume"
4041 "github.com/docker/docker/client"
4142 "github.com/docker/docker/pkg/stdcopy"
43+ "github.com/google/go-cmp/cmp"
4244 "github.com/google/go-containerregistry/pkg/authn"
4345 "github.com/google/go-containerregistry/pkg/name"
4446 "github.com/google/go-containerregistry/pkg/registry"
@@ -1312,6 +1314,126 @@ RUN date --utc > /root/date.txt`, testImageAlpine),
13121314 require .NotEmpty (t , strings .TrimSpace (out ))
13131315 })
13141316
1317+ t .Run ("CompareBuiltAndCachedImageEnvironment" , func (t * testing.T ) {
1318+ t .Parallel ()
1319+
1320+ ctx , cancel := context .WithCancel (context .Background ())
1321+ t .Cleanup (cancel )
1322+
1323+ wantOverrides := []string {
1324+ "FROM_CONTAINER_ENV=containerEnv" ,
1325+ "FROM_REMOTE_ENV=remoteEnv" ,
1326+ "CONTAINER_OVERRIDE_C=containerEnv" ,
1327+ "CONTAINER_OVERRIDE_CR=remoteEnv" ,
1328+ "CONTAINER_OVERRIDE_R=remoteEnv" ,
1329+ }
1330+
1331+ srv := gittest .CreateGitServer (t , gittest.Options {
1332+ Files : map [string ]string {
1333+ ".devcontainer/Dockerfile" : fmt .Sprintf (`
1334+ FROM %s
1335+ ENV FROM_CONTAINER=container
1336+ ENV CONTAINER_OVERRIDE_C=container
1337+ ENV CONTAINER_OVERRIDE_CR=container
1338+ ENV CONTAINER_OVERRIDE_R=container
1339+ ` , testImageAlpine ),
1340+ ".devcontainer/devcontainer.json" : `
1341+ {
1342+ "dockerFile": "Dockerfile",
1343+ "containerEnv": {
1344+ "CONTAINER_OVERRIDE_C": "containerEnv",
1345+ "CONTAINER_OVERRIDE_CR": "containerEnv",
1346+ "FROM_CONTAINER_ENV": "containerEnv",
1347+ },
1348+ "remoteEnv": {
1349+ "CONTAINER_OVERRIDE_CR": "remoteEnv",
1350+ "CONTAINER_OVERRIDE_R": "remoteEnv",
1351+ "FROM_REMOTE_ENV": "remoteEnv",
1352+ },
1353+ "onCreateCommand": "echo onCreateCommand",
1354+ "postCreateCommand": "echo postCreateCommand",
1355+ }
1356+ ` ,
1357+ },
1358+ })
1359+
1360+ // Given: an empty registry
1361+ testReg := setupInMemoryRegistry (t , setupInMemoryRegistryOpts {})
1362+ testRepo := testReg + "/test"
1363+ ref , err := name .ParseReference (testRepo + ":latest" )
1364+ require .NoError (t , err )
1365+ _ , err = remote .Image (ref )
1366+ require .ErrorContains (t , err , "NAME_UNKNOWN" , "expected image to not be present before build + push" )
1367+
1368+ opts := []string {
1369+ envbuilderEnv ("GIT_URL" , srv .URL ),
1370+ envbuilderEnv ("CACHE_REPO" , testRepo ),
1371+ envbuilderEnv ("INIT_SCRIPT" , "echo '[start]' && whoami && env && echo '[end]'" ),
1372+ envbuilderEnv ("INIT_COMMAND" , "/bin/ash" ),
1373+ }
1374+
1375+ // When: we run envbuilder with PUSH_IMAGE set
1376+ ctrID , err := runEnvbuilder (t , runOpts {env : append (opts , envbuilderEnv ("PUSH_IMAGE" , "1" ))})
1377+ require .NoError (t , err , "envbuilder push image failed" )
1378+
1379+ cli , err := client .NewClientWithOpts (client .FromEnv , client .WithAPIVersionNegotiation ())
1380+ require .NoError (t , err )
1381+ defer cli .Close ()
1382+
1383+ var started bool
1384+ var wantEnv , gotEnv []string
1385+ logs , _ := streamContainerLogs (t , cli , ctrID )
1386+ for {
1387+ log := <- logs
1388+ if log == "[start]" {
1389+ started = true
1390+ continue
1391+ }
1392+ if log == "[end]" {
1393+ break
1394+ }
1395+ if started {
1396+ wantEnv = append (wantEnv , log )
1397+ }
1398+ }
1399+ started = false
1400+
1401+ // Then: re-running envbuilder with GET_CACHED_IMAGE should succeed
1402+ cachedRef := getCachedImage (ctx , t , cli , opts ... )
1403+
1404+ // When: we run the image we just built
1405+ ctrID , err = runEnvbuilder (t , runOpts {
1406+ image : cachedRef .String (),
1407+ env : opts ,
1408+ })
1409+ require .NoError (t , err , "envbuilder run cached image failed" )
1410+
1411+ logs , _ = streamContainerLogs (t , cli , ctrID )
1412+ for {
1413+ log := <- logs
1414+ if log == "[start]" {
1415+ started = true
1416+ continue
1417+ }
1418+ if log == "[end]" {
1419+ break
1420+ }
1421+ if started {
1422+ gotEnv = append (gotEnv , log )
1423+ }
1424+ }
1425+
1426+ slices .Sort (wantEnv )
1427+ slices .Sort (gotEnv )
1428+ if diff := cmp .Diff (wantEnv , gotEnv ); diff != "" {
1429+ t .Fatalf ("unexpected output (-want +got):\n %s" , diff )
1430+ }
1431+
1432+ for _ , want := range wantOverrides {
1433+ assert .Contains (t , gotEnv , want , "expected env var %q to be present" , want )
1434+ }
1435+ })
1436+
13151437 t .Run ("CacheAndPushWithNoChangeLayers" , func (t * testing.T ) {
13161438 t .Parallel ()
13171439
@@ -2003,7 +2125,7 @@ func startContainerFromRef(ctx context.Context, t *testing.T, cli *client.Client
20032125 rc , err := cli .ImagePull (ctx , ref .String (), image.PullOptions {})
20042126 require .NoError (t , err )
20052127 t .Cleanup (func () { _ = rc .Close () })
2006- _ , err = io .ReadAll ( rc )
2128+ _ , err = io .Copy ( io . Discard , rc )
20072129 require .NoError (t , err )
20082130
20092131 // Start the container.
@@ -2033,6 +2155,7 @@ func startContainerFromRef(ctx context.Context, t *testing.T, cli *client.Client
20332155}
20342156
20352157type runOpts struct {
2158+ image string
20362159 binds []string
20372160 env []string
20382161 volumes map [string ]string
@@ -2063,8 +2186,18 @@ func runEnvbuilder(t *testing.T, opts runOpts) (string, error) {
20632186 _ = cli .VolumeRemove (ctx , volName , true )
20642187 })
20652188 }
2189+ img := "envbuilder:latest"
2190+ if opts .image != "" {
2191+ // Pull the image first so we can start it afterwards.
2192+ rc , err := cli .ImagePull (ctx , opts .image , image.PullOptions {})
2193+ require .NoError (t , err , "failed to pull image" )
2194+ t .Cleanup (func () { _ = rc .Close () })
2195+ _ , err = io .Copy (io .Discard , rc )
2196+ require .NoError (t , err , "failed to read image pull response" )
2197+ img = opts .image
2198+ }
20662199 ctr , err := cli .ContainerCreate (ctx , & container.Config {
2067- Image : "envbuilder:latest" ,
2200+ Image : img ,
20682201 Env : opts .env ,
20692202 Labels : map [string ]string {
20702203 testContainerLabel : "true" ,
0 commit comments