@@ -3,6 +3,7 @@ package agentcontainers
33import (
44"fmt"
55"os"
6+ "slices"
67"strconv"
78"strings"
89"testing"
@@ -47,10 +48,13 @@ func TestIntegrationDocker(t *testing.T) {
4748// Pick a random port to expose for testing port bindings.
4849testRandPort := testutil .RandomPortNoListen (t )
4950ct ,err := pool .RunWithOptions (& dockertest.RunOptions {
50- Repository :"busybox" ,
51- Tag :"latest" ,
52- Cmd : []string {"sleep" ,"infnity" },
53- Labels :map [string ]string {"com.coder.test" :testLabelValue },
51+ Repository :"busybox" ,
52+ Tag :"latest" ,
53+ Cmd : []string {"sleep" ,"infnity" },
54+ Labels :map [string ]string {
55+ "com.coder.test" :testLabelValue ,
56+ "devcontainer.metadata" :`{"remoteEnv": {"FOO": "bar", "MULTILINE": "foo\nbar\nbaz"}}` ,
57+ },
5458Mounts : []string {testTempDir + ":" + testTempDir },
5559ExposedPorts : []string {fmt .Sprintf ("%d/tcp" ,testRandPort )},
5660PortBindings :map [docker.Port ][]docker.PortBinding {
@@ -122,6 +126,12 @@ func TestIntegrationDocker(t *testing.T) {
122126matchHostnameOuput := func (line string )bool {
123127return strings .Contains (strings .TrimSpace (line ),ct .Container .Config .Hostname )
124128}
129+ matchEnvCmd := func (line string )bool {
130+ return strings .Contains (strings .TrimSpace (line ),"env" )
131+ }
132+ matchEnvOutput := func (line string )bool {
133+ return strings .Contains (line ,"FOO=bar" )|| strings .Contains (line ,"MULTILINE=foo" )
134+ }
125135require .NoError (t ,tr .ReadUntil (ctx ,matchPrompt ),"failed to match prompt" )
126136t .Logf ("Matched prompt" )
127137_ ,err = ptyCmd .InputWriter ().Write ([]byte ("hostname\r \n " ))
@@ -131,6 +141,13 @@ func TestIntegrationDocker(t *testing.T) {
131141t .Logf ("Matched hostname command" )
132142require .NoError (t ,tr .ReadUntil (ctx ,matchHostnameOuput ),"failed to match hostname output" )
133143t .Logf ("Matched hostname output" )
144+ _ ,err = ptyCmd .InputWriter ().Write ([]byte ("env\r \n " ))
145+ require .NoError (t ,err ,"failed to write to pty" )
146+ t .Logf ("Wrote env command" )
147+ require .NoError (t ,tr .ReadUntil (ctx ,matchEnvCmd ),"failed to match env command" )
148+ t .Logf ("Matched env command" )
149+ require .NoError (t ,tr .ReadUntil (ctx ,matchEnvOutput ),"failed to match env output" )
150+ t .Logf ("Matched env output" )
134151break
135152}
136153}
@@ -403,49 +420,56 @@ func TestConvertDockerVolume(t *testing.T) {
403420// CODER_TEST_USE_DOCKER=1 go test ./agent/agentcontainers -run TestDockerEnvInfoer
404421func TestDockerEnvInfoer (t * testing.T ) {
405422t .Parallel ()
406- if ctud ,ok := os .LookupEnv ("CODER_TEST_USE_DOCKER" );! ok || ctud != "1" {
407- t .Skip ("Set CODER_TEST_USE_DOCKER=1 to run this test" )
408- }
423+ // if ctud, ok := os.LookupEnv("CODER_TEST_USE_DOCKER"); !ok || ctud != "1" {
424+ // t.Skip("Set CODER_TEST_USE_DOCKER=1 to run this test")
425+ // }
409426
410427pool ,err := dockertest .NewPool ("" )
411428require .NoError (t ,err ,"Could not connect to docker" )
412429// nolint:paralleltest // variable recapture no longer required
413430for idx ,tt := range []struct {
414431image string
415- env []string
432+ labels map [string ]string
433+ expectedEnv []string
416434containerUser string
417435expectedUsername string
418436expectedUserShell string
419437}{
420438{
421- image :"busybox:latest" ,
422- env : []string {"FOO=bar" ,"MULTILINE=foo\n bar\n baz" },
439+ image :"busybox:latest" ,
440+ labels :map [string ]string {`devcontainer.metadata` :`{"remoteEnv": {"FOO": "bar", "MULTILINE": "foo\nbar\nbaz"}}` },
441+
442+ expectedEnv : []string {"FOO=bar" ,"MULTILINE=foo\n bar\n baz" },
423443expectedUsername :"root" ,
424444expectedUserShell :"/bin/sh" ,
425445},
426446{
427447image :"busybox:latest" ,
428- env : []string {"FOO=bar" ,"MULTILINE=foo\n bar\n baz" },
448+ labels :map [string ]string {`devcontainer.metadata` :`{"remoteEnv": {"FOO": "bar", "MULTILINE": "foo\nbar\nbaz"}}` },
449+ expectedEnv : []string {"FOO=bar" ,"MULTILINE=foo\n bar\n baz" },
429450containerUser :"root" ,
430451expectedUsername :"root" ,
431452expectedUserShell :"/bin/sh" ,
432453},
433454{
434455image :"codercom/enterprise-minimal:ubuntu" ,
435- env : []string {"FOO=bar" ,"MULTILINE=foo\n bar\n baz" },
456+ labels :map [string ]string {`devcontainer.metadata` :`{"remoteEnv": {"FOO": "bar", "MULTILINE": "foo\nbar\nbaz"}}` },
457+ expectedEnv : []string {"FOO=bar" ,"MULTILINE=foo\n bar\n baz" },
436458expectedUsername :"coder" ,
437459expectedUserShell :"/bin/bash" ,
438460},
439461{
440462image :"codercom/enterprise-minimal:ubuntu" ,
441- env : []string {"FOO=bar" ,"MULTILINE=foo\n bar\n baz" },
463+ labels :map [string ]string {`devcontainer.metadata` :`{"remoteEnv": {"FOO": "bar", "MULTILINE": "foo\nbar\nbaz"}}` },
464+ expectedEnv : []string {"FOO=bar" ,"MULTILINE=foo\n bar\n baz" },
442465containerUser :"coder" ,
443466expectedUsername :"coder" ,
444467expectedUserShell :"/bin/bash" ,
445468},
446469{
447470image :"codercom/enterprise-minimal:ubuntu" ,
448- env : []string {"FOO=bar" ,"MULTILINE=foo\n bar\n baz" },
471+ labels :map [string ]string {`devcontainer.metadata` :`{"remoteEnv": {"FOO": "bar", "MULTILINE": "foo\nbar\nbaz"}}` },
472+ expectedEnv : []string {"FOO=bar" ,"MULTILINE=foo\n bar\n baz" },
449473containerUser :"root" ,
450474expectedUsername :"root" ,
451475expectedUserShell :"/bin/bash" ,
@@ -462,7 +486,7 @@ func TestDockerEnvInfoer(t *testing.T) {
462486Repository :image ,
463487Tag :tag ,
464488Cmd : []string {"sleep" ,"infinity" },
465- Env :tt .env ,
489+ Labels :tt .labels ,
466490},func (config * docker.HostConfig ) {
467491config .AutoRemove = true
468492config .RestartPolicy = docker.RestartPolicy {Name :"no" }
@@ -490,9 +514,25 @@ func TestDockerEnvInfoer(t *testing.T) {
490514require .NoError (t ,err ,"Expected no error from UserShell()" )
491515require .Equal (t ,tt .expectedUserShell ,sh ,"Expected user shell to match" )
492516
493- // TODO: environment from devcontainer labels.
494- // environ := dei.Environ()
495- // require.Subset(t, environ, tt.env, "Expected environment to match")
517+ // We don't need to test the actual environment variables here.
518+ environ := dei .Environ ()
519+ require .NotEmpty (t ,environ ,"Expected environ to be non-empty" )
520+
521+ // Test that the environment variables are present in modified command
522+ // output.
523+ envCmd ,envArgs := dei .ModifyCommand ("env" )
524+ for _ ,env := range tt .expectedEnv {
525+ require .Subset (t ,envArgs , []string {"--env" ,env })
526+ }
527+ // Run the command in the container and check the output
528+ // HACK: we remove the --tty argument because we're not running in a tty
529+ envArgs = slices .DeleteFunc (envArgs ,func (s string )bool {return s == "--tty" })
530+ stdout ,stderr ,err := run (ctx ,agentexec .DefaultExecer ,envCmd ,envArgs ... )
531+ require .Empty (t ,stderr ,"Expected no stderr output" )
532+ require .NoError (t ,err ,"Expected no error from running command" )
533+ for _ ,env := range tt .expectedEnv {
534+ require .Contains (t ,stdout ,env )
535+ }
496536})
497537}
498538}