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