@@ -1262,10 +1262,6 @@ func TestAgent_SSHConnectionLoginVars(t *testing.T) {
1262
1262
key : "LOGNAME" ,
1263
1263
want : u .Username ,
1264
1264
},
1265
- {
1266
- key : "HOME" ,
1267
- want : u .HomeDir ,
1268
- },
1269
1265
{
1270
1266
key : "SHELL" ,
1271
1267
want : shell ,
@@ -1502,7 +1498,7 @@ func TestAgent_Lifecycle(t *testing.T) {
1502
1498
1503
1499
_ , client , _ , _ , _ := setupAgent (t , agentsdk.Manifest {
1504
1500
Scripts : []codersdk.WorkspaceAgentScript {{
1505
- Script : "true " ,
1501
+ Script : "echo foo " ,
1506
1502
Timeout : 30 * time .Second ,
1507
1503
RunOnStart : true ,
1508
1504
}},
@@ -1935,8 +1931,6 @@ func TestAgent_ReconnectingPTYContainer(t *testing.T) {
1935
1931
t .Skip ("Set CODER_TEST_USE_DOCKER=1 to run this test" )
1936
1932
}
1937
1933
1938
- ctx := testutil .Context (t , testutil .WaitLong )
1939
-
1940
1934
pool , err := dockertest .NewPool ("" )
1941
1935
require .NoError (t , err , "Could not connect to docker" )
1942
1936
ct , err := pool .RunWithOptions (& dockertest.RunOptions {
@@ -1948,10 +1942,10 @@ func TestAgent_ReconnectingPTYContainer(t *testing.T) {
1948
1942
config .RestartPolicy = docker.RestartPolicy {Name : "no" }
1949
1943
})
1950
1944
require .NoError (t , err , "Could not start container" )
1951
- t . Cleanup ( func () {
1945
+ defer func () {
1952
1946
err := pool .Purge (ct )
1953
1947
require .NoError (t , err , "Could not stop container" )
1954
- })
1948
+ }( )
1955
1949
// Wait for container to start
1956
1950
require .Eventually (t , func () bool {
1957
1951
ct , ok := pool .ContainerByName (ct .Container .Name )
@@ -1962,6 +1956,7 @@ func TestAgent_ReconnectingPTYContainer(t *testing.T) {
1962
1956
conn , _ , _ , _ , _ := setupAgent (t , agentsdk.Manifest {}, 0 , func (_ * agenttest.Client , o * agent.Options ) {
1963
1957
o .ExperimentalDevcontainersEnabled = true
1964
1958
})
1959
+ ctx := testutil .Context (t , testutil .WaitLong )
1965
1960
ac , err := conn .ReconnectingPTY (ctx , uuid .New (), 80 , 80 , "/bin/sh" , func (arp * workspacesdk.AgentReconnectingPTYInit ) {
1966
1961
arp .Container = ct .Container .ID
1967
1962
})
@@ -2005,9 +2000,6 @@ func TestAgent_DevcontainerAutostart(t *testing.T) {
2005
2000
t .Skip ("Set CODER_TEST_USE_DOCKER=1 to run this test" )
2006
2001
}
2007
2002
2008
- ctx := testutil .Context (t , testutil .WaitLong )
2009
-
2010
- // Connect to Docker
2011
2003
pool , err := dockertest .NewPool ("" )
2012
2004
require .NoError (t , err , "Could not connect to docker" )
2013
2005
@@ -2051,7 +2043,7 @@ func TestAgent_DevcontainerAutostart(t *testing.T) {
2051
2043
},
2052
2044
},
2053
2045
}
2054
- // nolint: dogsled
2046
+ //nolint:dogsled
2055
2047
conn , _ , _ , _ , _ := setupAgent (t , manifest , 0 , func (_ * agenttest.Client , o * agent.Options ) {
2056
2048
o .ExperimentalDevcontainersEnabled = true
2057
2049
})
@@ -2079,8 +2071,7 @@ func TestAgent_DevcontainerAutostart(t *testing.T) {
2079
2071
2080
2072
return false
2081
2073
}, testutil .WaitSuperLong , testutil .IntervalMedium , "no container with workspace folder label found" )
2082
-
2083
- t .Cleanup (func () {
2074
+ defer func () {
2084
2075
// We can't rely on pool here because the container is not
2085
2076
// managed by it (it is managed by @devcontainer/cli).
2086
2077
err := pool .Client .RemoveContainer (docker.RemoveContainerOptions {
@@ -2089,13 +2080,15 @@ func TestAgent_DevcontainerAutostart(t *testing.T) {
2089
2080
Force : true ,
2090
2081
})
2091
2082
assert .NoError (t , err , "remove container" )
2092
- })
2083
+ }( )
2093
2084
2094
2085
containerInfo , err := pool .Client .InspectContainer (container .ID )
2095
2086
require .NoError (t , err , "inspect container" )
2096
2087
t .Logf ("Container state: status: %v" , containerInfo .State .Status )
2097
2088
require .True (t , containerInfo .State .Running , "container should be running" )
2098
2089
2090
+ ctx := testutil .Context (t , testutil .WaitLong )
2091
+
2099
2092
ac , err := conn .ReconnectingPTY (ctx , uuid .New (), 80 , 80 , "" , func (opts * workspacesdk.AgentReconnectingPTYInit ) {
2100
2093
opts .Container = container .ID
2101
2094
})
@@ -2124,6 +2117,173 @@ func TestAgent_DevcontainerAutostart(t *testing.T) {
2124
2117
require .NoError (t , err , "file should exist outside devcontainer" )
2125
2118
}
2126
2119
2120
+ // TestAgent_DevcontainerRecreate tests that RecreateDevcontainer
2121
+ // recreates a devcontainer and emits logs.
2122
+ //
2123
+ // This tests end-to-end functionality of auto-starting a devcontainer.
2124
+ // It runs "devcontainer up" which creates a real Docker container. As
2125
+ // such, it does not run by default in CI.
2126
+ //
2127
+ // You can run it manually as follows:
2128
+ //
2129
+ // CODER_TEST_USE_DOCKER=1 go test -count=1 ./agent -run TestAgent_DevcontainerRecreate
2130
+ func TestAgent_DevcontainerRecreate (t * testing.T ) {
2131
+ if os .Getenv ("CODER_TEST_USE_DOCKER" ) != "1" {
2132
+ t .Skip ("Set CODER_TEST_USE_DOCKER=1 to run this test" )
2133
+ }
2134
+ t .Parallel ()
2135
+
2136
+ pool , err := dockertest .NewPool ("" )
2137
+ require .NoError (t , err , "Could not connect to docker" )
2138
+
2139
+ // Prepare temporary devcontainer for test (mywork).
2140
+ devcontainerID := uuid .New ()
2141
+ devcontainerLogSourceID := uuid .New ()
2142
+ workspaceFolder := filepath .Join (t .TempDir (), "mywork" )
2143
+ t .Logf ("Workspace folder: %s" , workspaceFolder )
2144
+ devcontainerPath := filepath .Join (workspaceFolder , ".devcontainer" )
2145
+ err = os .MkdirAll (devcontainerPath , 0o755 )
2146
+ require .NoError (t , err , "create devcontainer directory" )
2147
+ devcontainerFile := filepath .Join (devcontainerPath , "devcontainer.json" )
2148
+ err = os .WriteFile (devcontainerFile , []byte (`{
2149
+ "name": "mywork",
2150
+ "image": "busybox:latest",
2151
+ "cmd": ["sleep", "infinity"]
2152
+ }` ), 0o600 )
2153
+ require .NoError (t , err , "write devcontainer.json" )
2154
+
2155
+ manifest := agentsdk.Manifest {
2156
+ // Set up pre-conditions for auto-starting a devcontainer, the
2157
+ // script is used to extract the log source ID.
2158
+ Devcontainers : []codersdk.WorkspaceAgentDevcontainer {
2159
+ {
2160
+ ID : devcontainerID ,
2161
+ Name : "test" ,
2162
+ WorkspaceFolder : workspaceFolder ,
2163
+ },
2164
+ },
2165
+ Scripts : []codersdk.WorkspaceAgentScript {
2166
+ {
2167
+ ID : devcontainerID ,
2168
+ LogSourceID : devcontainerLogSourceID ,
2169
+ },
2170
+ },
2171
+ }
2172
+
2173
+ //nolint:dogsled
2174
+ conn , client , _ , _ , _ := setupAgent (t , manifest , 0 , func (_ * agenttest.Client , o * agent.Options ) {
2175
+ o .ExperimentalDevcontainersEnabled = true
2176
+ })
2177
+
2178
+ ctx := testutil .Context (t , testutil .WaitLong )
2179
+
2180
+ // We enabled autostart for the devcontainer, so ready is a good
2181
+ // indication that the devcontainer is up and running. Importantly,
2182
+ // this also means that the devcontainer startup is no longer
2183
+ // producing logs that may interfere with the recreate logs.
2184
+ testutil .Eventually (ctx , t , func (context.Context ) bool {
2185
+ states := client .GetLifecycleStates ()
2186
+ return slices .Contains (states , codersdk .WorkspaceAgentLifecycleReady )
2187
+ }, testutil .IntervalMedium , "devcontainer not ready" )
2188
+
2189
+ t .Logf ("Looking for container with label: devcontainer.local_folder=%s" , workspaceFolder )
2190
+
2191
+ var container docker.APIContainers
2192
+ testutil .Eventually (ctx , t , func (context.Context ) bool {
2193
+ containers , err := pool .Client .ListContainers (docker.ListContainersOptions {All : true })
2194
+ if err != nil {
2195
+ t .Logf ("Error listing containers: %v" , err )
2196
+ return false
2197
+ }
2198
+ for _ , c := range containers {
2199
+ t .Logf ("Found container: %s with labels: %v" , c .ID [:12 ], c .Labels )
2200
+ if v , ok := c .Labels ["devcontainer.local_folder" ]; ok && v == workspaceFolder {
2201
+ t .Logf ("Found matching container: %s" , c .ID [:12 ])
2202
+ container = c
2203
+ return true
2204
+ }
2205
+ }
2206
+ return false
2207
+ }, testutil .IntervalMedium , "no container with workspace folder label found" )
2208
+ defer func (container docker.APIContainers ) {
2209
+ // We can't rely on pool here because the container is not
2210
+ // managed by it (it is managed by @devcontainer/cli).
2211
+ err := pool .Client .RemoveContainer (docker.RemoveContainerOptions {
2212
+ ID : container .ID ,
2213
+ RemoveVolumes : true ,
2214
+ Force : true ,
2215
+ })
2216
+ assert .Error (t , err , "container should be removed by recreate" )
2217
+ }(container )
2218
+
2219
+ ctx = testutil .Context (t , testutil .WaitLong ) // Reset context.
2220
+
2221
+ // Capture logs via ScriptLogger.
2222
+ logsCh := make (chan * proto.BatchCreateLogsRequest , 1 )
2223
+ client .SetLogsChannel (logsCh )
2224
+
2225
+ // Invoke recreate to trigger the destruction and recreation of the
2226
+ // devcontainer, we do it in a goroutine so we can process logs
2227
+ // concurrently.
2228
+ go func (container docker.APIContainers ) {
2229
+ err := conn .RecreateDevcontainer (ctx , container .ID )
2230
+ assert .NoError (t , err , "recreate devcontainer should succeed" )
2231
+ }(container )
2232
+
2233
+ t .Logf ("Checking recreate logs for outcome..." )
2234
+
2235
+ // Wait for the logs to be emitted, the @devcontainer/cli up command
2236
+ // will emit a log with the outcome at the end suggesting we did
2237
+ // receive all the logs.
2238
+ waitForOutcomeLoop:
2239
+ for {
2240
+ batch := testutil .RequireReceive (ctx , t , logsCh )
2241
+
2242
+ if bytes .Equal (batch .LogSourceId , devcontainerLogSourceID [:]) {
2243
+ for _ , log := range batch .Logs {
2244
+ t .Logf ("Received log: %s" , log .Output )
2245
+ if strings .Contains (log .Output , "\" outcome\" " ) {
2246
+ break waitForOutcomeLoop
2247
+ }
2248
+ }
2249
+ }
2250
+ }
2251
+
2252
+ t .Logf ("Checking there's a new container with label: devcontainer.local_folder=%s" , workspaceFolder )
2253
+
2254
+ // Make sure the container exists and isn't the same as the old one.
2255
+ testutil .Eventually (ctx , t , func (context.Context ) bool {
2256
+ containers , err := pool .Client .ListContainers (docker.ListContainersOptions {All : true })
2257
+ if err != nil {
2258
+ t .Logf ("Error listing containers: %v" , err )
2259
+ return false
2260
+ }
2261
+ for _ , c := range containers {
2262
+ t .Logf ("Found container: %s with labels: %v" , c .ID [:12 ], c .Labels )
2263
+ if v , ok := c .Labels ["devcontainer.local_folder" ]; ok && v == workspaceFolder {
2264
+ if c .ID == container .ID {
2265
+ t .Logf ("Found same container: %s" , c .ID [:12 ])
2266
+ return false
2267
+ }
2268
+ t .Logf ("Found new container: %s" , c .ID [:12 ])
2269
+ container = c
2270
+ return true
2271
+ }
2272
+ }
2273
+ return false
2274
+ }, testutil .IntervalMedium , "new devcontainer not found" )
2275
+ defer func (container docker.APIContainers ) {
2276
+ // We can't rely on pool here because the container is not
2277
+ // managed by it (it is managed by @devcontainer/cli).
2278
+ err := pool .Client .RemoveContainer (docker.RemoveContainerOptions {
2279
+ ID : container .ID ,
2280
+ RemoveVolumes : true ,
2281
+ Force : true ,
2282
+ })
2283
+ assert .NoError (t , err , "remove container" )
2284
+ }(container )
2285
+ }
2286
+
2127
2287
func TestAgent_Dial (t * testing.T ) {
2128
2288
t .Parallel ()
2129
2289
0 commit comments