7
7
"maps"
8
8
"net"
9
9
"net/http"
10
+ "os"
10
11
"runtime"
11
12
"strconv"
12
13
"strings"
@@ -15,18 +16,21 @@ import (
15
16
"time"
16
17
17
18
"github.com/go-jose/go-jose/v4/jwt"
19
+ "github.com/google/go-cmp/cmp"
18
20
"github.com/google/uuid"
19
21
"github.com/ory/dockertest/v3"
20
22
"github.com/ory/dockertest/v3/docker"
21
23
"github.com/stretchr/testify/assert"
22
24
"github.com/stretchr/testify/require"
25
+ "go.uber.org/mock/gomock"
23
26
"golang.org/x/xerrors"
24
27
"google.golang.org/protobuf/types/known/timestamppb"
25
28
"tailscale.com/tailcfg"
26
29
27
30
"cdr.dev/slog"
28
31
"cdr.dev/slog/sloggers/slogtest"
29
32
"github.com/coder/coder/v2/agent"
33
+ "github.com/coder/coder/v2/agent/agentcontainers"
30
34
"github.com/coder/coder/v2/agent/agenttest"
31
35
agentproto "github.com/coder/coder/v2/agent/proto"
32
36
"github.com/coder/coder/v2/coderd/coderdtest"
@@ -1058,82 +1062,182 @@ func TestWorkspaceAgentListeningPorts(t *testing.T) {
1058
1062
func TestWorkspaceAgentContainers (t * testing.T ) {
1059
1063
t .Parallel ()
1060
1064
1061
- if runtime .GOOS != "linux" {
1062
- t .Skip ("this test creates containers, which is flaky on non-linux runners" )
1063
- }
1065
+ // This test will not normally run in CI, but is kept here as a semi-manual
1066
+ // test for local development. Run it as follows:
1067
+ // CODER_TEST_USE_DOCKER=1 go test -run TestWorkspaceAgentContainers/Docker ./coderd
1068
+ t .Run ("Docker" , func (t * testing.T ) {
1069
+ t .Parallel ()
1070
+ if ctud , ok := os .LookupEnv ("CODER_TEST_USE_DOCKER" ); ! ok || ctud != "1" {
1071
+ t .Skip ("Set CODER_TEST_USE_DOCKER=1 to run this test" )
1072
+ }
1064
1073
1065
- pool , err := dockertest .NewPool ("" )
1066
- require .NoError (t , err , "Could not connect to docker" )
1067
- testLabels := map [string ]string {
1068
- "com.coder.test" : uuid .New ().String (),
1069
- }
1070
- ct , err := pool .RunWithOptions (& dockertest.RunOptions {
1071
- Repository : "busybox" ,
1072
- Tag : "latest" ,
1073
- Cmd : []string {"sleep" , "infinity" },
1074
- Labels : testLabels ,
1075
- }, func (config * docker.HostConfig ) {
1076
- config .AutoRemove = true
1077
- config .RestartPolicy = docker.RestartPolicy {Name : "no" }
1078
- })
1079
- require .NoError (t , err , "Could not start test docker container" )
1080
- t .Cleanup (func () {
1081
- assert .NoError (t , pool .Purge (ct ), "Could not purge resource %q" , ct .Container .Name )
1082
- })
1074
+ pool , err := dockertest .NewPool ("" )
1075
+ require .NoError (t , err , "Could not connect to docker" )
1076
+ testLabels := map [string ]string {
1077
+ "com.coder.test" : uuid .New ().String (),
1078
+ }
1079
+ ct , err := pool .RunWithOptions (& dockertest.RunOptions {
1080
+ Repository : "busybox" ,
1081
+ Tag : "latest" ,
1082
+ Cmd : []string {"sleep" , "infinity" },
1083
+ Labels : testLabels ,
1084
+ }, func (config * docker.HostConfig ) {
1085
+ config .AutoRemove = true
1086
+ config .RestartPolicy = docker.RestartPolicy {Name : "no" }
1087
+ })
1088
+ require .NoError (t , err , "Could not start test docker container" )
1089
+ t .Cleanup (func () {
1090
+ assert .NoError (t , pool .Purge (ct ), "Could not purge resource %q" , ct .Container .Name )
1091
+ })
1083
1092
1084
- // Start another container which we will expect to ignore.
1085
- ct2 , err := pool .RunWithOptions (& dockertest.RunOptions {
1086
- Repository : "busybox" ,
1087
- Tag : "latest" ,
1088
- Cmd : []string {"sleep" , "infinity" },
1089
- Labels : map [string ]string {"com.coder.test" : "ignoreme" },
1090
- }, func (config * docker.HostConfig ) {
1091
- config .AutoRemove = true
1092
- config .RestartPolicy = docker.RestartPolicy {Name : "no" }
1093
- })
1094
- require .NoError (t , err , "Could not start second test docker container" )
1095
- t .Cleanup (func () {
1096
- assert .NoError (t , pool .Purge (ct2 ), "Could not purge resource %q" , ct2 .Container .Name )
1093
+ // Start another container which we will expect to ignore.
1094
+ ct2 , err := pool .RunWithOptions (& dockertest.RunOptions {
1095
+ Repository : "busybox" ,
1096
+ Tag : "latest" ,
1097
+ Cmd : []string {"sleep" , "infinity" },
1098
+ Labels : map [string ]string {"com.coder.test" : "ignoreme" },
1099
+ }, func (config * docker.HostConfig ) {
1100
+ config .AutoRemove = true
1101
+ config .RestartPolicy = docker.RestartPolicy {Name : "no" }
1102
+ })
1103
+ require .NoError (t , err , "Could not start second test docker container" )
1104
+ t .Cleanup (func () {
1105
+ assert .NoError (t , pool .Purge (ct2 ), "Could not purge resource %q" , ct2 .Container .Name )
1106
+ })
1107
+
1108
+ client , db := coderdtest .NewWithDatabase (t , & coderdtest.Options {})
1109
+
1110
+ user := coderdtest .CreateFirstUser (t , client )
1111
+ r := dbfake .WorkspaceBuild (t , db , database.WorkspaceTable {
1112
+ OrganizationID : user .OrganizationID ,
1113
+ OwnerID : user .UserID ,
1114
+ }).WithAgent (func (agents []* proto.Agent ) []* proto.Agent {
1115
+ return agents
1116
+ }).Do ()
1117
+ _ = agenttest .New (t , client .URL , r .AgentToken , func (opts * agent.Options ) {
1118
+ opts .ContainerLister = & agentcontainers.DockerCLILister {}
1119
+ })
1120
+ resources := coderdtest .NewWorkspaceAgentWaiter (t , client , r .Workspace .ID ).Wait ()
1121
+ require .Len (t , resources , 1 , "expected one resource" )
1122
+ require .Len (t , resources [0 ].Agents , 1 , "expected one agent" )
1123
+ agentID := resources [0 ].Agents [0 ].ID
1124
+
1125
+ ctx := testutil .Context (t , testutil .WaitLong )
1126
+
1127
+ // If we filter by testLabels, we should only get one container back.
1128
+ res , err := client .WorkspaceAgentListContainers (ctx , agentID , testLabels )
1129
+ require .NoError (t , err , "failed to list containers filtered by test label" )
1130
+ require .Len (t , res .Containers , 1 , "expected exactly one container" )
1131
+ assert .Equal (t , ct .Container .ID , res .Containers [0 ].ID , "expected container ID to match" )
1132
+ assert .Equal (t , "busybox:latest" , res .Containers [0 ].Image , "expected container image to match" )
1133
+ assert .Equal (t , ct .Container .Config .Labels , res .Containers [0 ].Labels , "expected container labels to match" )
1134
+ assert .Equal (t , strings .TrimPrefix (ct .Container .Name , "/" ), res .Containers [0 ].FriendlyName , "expected container name to match" )
1135
+ assert .True (t , res .Containers [0 ].Running , "expected container to be running" )
1136
+ assert .Equal (t , "running" , res .Containers [0 ].Status , "expected container status to be running" )
1137
+
1138
+ // List all containers and ensure we get at least both (there may be more).
1139
+ res , err = client .WorkspaceAgentListContainers (ctx , agentID , nil )
1140
+ require .NoError (t , err , "failed to list all containers" )
1141
+ require .NotEmpty (t , res .Containers , "expected to find containers" )
1142
+ var found []string
1143
+ for _ , c := range res .Containers {
1144
+ found = append (found , c .ID )
1145
+ }
1146
+ require .Contains (t , found , ct .Container .ID , "expected to find first container without label filter" )
1147
+ require .Contains (t , found , ct2 .Container .ID , "expected to find first container without label filter" )
1097
1148
})
1098
1149
1099
- client , db := coderdtest .NewWithDatabase (t , & coderdtest.Options {})
1150
+ // This test will normally run in CI. It uses a mock implementation of
1151
+ // agentcontainers.Lister instead of introducing a hard dependency on Docker.
1152
+ t .Run ("Mock" , func (t * testing.T ) {
1153
+ t .Parallel ()
1100
1154
1101
- user := coderdtest .CreateFirstUser (t , client )
1102
- r := dbfake .WorkspaceBuild (t , db , database.WorkspaceTable {
1103
- OrganizationID : user .OrganizationID ,
1104
- OwnerID : user .UserID ,
1105
- }).WithAgent (func (agents []* proto.Agent ) []* proto.Agent {
1106
- return agents
1107
- }).Do ()
1108
- _ = agenttest .New (t , client .URL , r .AgentToken , func (_ * agent.Options ) {})
1109
- resources := coderdtest .NewWorkspaceAgentWaiter (t , client , r .Workspace .ID ).Wait ()
1110
- require .Len (t , resources , 1 , "expected one resource" )
1111
- require .Len (t , resources [0 ].Agents , 1 , "expected one agent" )
1112
- agentID := resources [0 ].Agents [0 ].ID
1155
+ // begin test fixtures
1156
+ testLabels := map [string ]string {
1157
+ "com.coder.test" : uuid .New ().String (),
1158
+ }
1159
+ testResponse := codersdk.WorkspaceAgentListContainersResponse {
1160
+ Containers : []codersdk.WorkspaceAgentDevcontainer {
1161
+ {
1162
+ ID : uuid .NewString (),
1163
+ CreatedAt : dbtime .Now (),
1164
+ FriendlyName : testutil .GetRandomName (t ),
1165
+ Image : "busybox:latest" ,
1166
+ Labels : testLabels ,
1167
+ Running : true ,
1168
+ Status : "running" ,
1169
+ Ports : []codersdk.WorkspaceAgentListeningPort {
1170
+ {
1171
+ Network : "tcp" ,
1172
+ Port : 80 ,
1173
+ },
1174
+ },
1175
+ Volumes : map [string ]string {
1176
+ "/host" : "/container" ,
1177
+ },
1178
+ },
1179
+ },
1180
+ }
1181
+ // end test fixtures
1113
1182
1114
- ctx := testutil .Context (t , testutil .WaitLong )
1183
+ for _ , tc := range []struct {
1184
+ name string
1185
+ setupMock func (* agentcontainers.MockLister ) (codersdk.WorkspaceAgentListContainersResponse , error )
1186
+ }{
1187
+ {
1188
+ name : "test response" ,
1189
+ setupMock : func (mcl * agentcontainers.MockLister ) (codersdk.WorkspaceAgentListContainersResponse , error ) {
1190
+ mcl .EXPECT ().List (gomock .Any ()).Return (testResponse , nil ).Times (1 )
1191
+ return testResponse , nil
1192
+ },
1193
+ },
1194
+ {
1195
+ name : "error response" ,
1196
+ setupMock : func (mcl * agentcontainers.MockLister ) (codersdk.WorkspaceAgentListContainersResponse , error ) {
1197
+ mcl .EXPECT ().List (gomock .Any ()).Return (codersdk.WorkspaceAgentListContainersResponse {}, assert .AnError ).Times (1 )
1198
+ return codersdk.WorkspaceAgentListContainersResponse {}, assert .AnError
1199
+ },
1200
+ },
1201
+ } {
1202
+ tc := tc
1203
+ t .Run (tc .name , func (t * testing.T ) {
1204
+ t .Parallel ()
1115
1205
1116
- // If we filter by testLabels, we should only get one container back.
1117
- res , err := client .WorkspaceAgentListContainers (ctx , agentID , testLabels )
1118
- require .NoError (t , err , "failed to list containers filtered by test label" )
1119
- require .Len (t , res .Containers , 1 , "expected exactly one container" )
1120
- assert .Equal (t , ct .Container .ID , res .Containers [0 ].ID , "expected container ID to match" )
1121
- assert .Equal (t , "busybox:latest" , res .Containers [0 ].Image , "expected container image to match" )
1122
- assert .Equal (t , ct .Container .Config .Labels , res .Containers [0 ].Labels , "expected container labels to match" )
1123
- assert .Equal (t , strings .TrimPrefix (ct .Container .Name , "/" ), res .Containers [0 ].FriendlyName , "expected container name to match" )
1124
- assert .True (t , res .Containers [0 ].Running , "expected container to be running" )
1125
- assert .Equal (t , "running" , res .Containers [0 ].Status , "expected container status to be running" )
1126
-
1127
- // List all containers and ensure we get at least both (there may be more).
1128
- res , err = client .WorkspaceAgentListContainers (ctx , agentID , nil )
1129
- require .NoError (t , err , "failed to list all containers" )
1130
- require .NotEmpty (t , res .Containers , "expected to find containers" )
1131
- var found []string
1132
- for _ , c := range res .Containers {
1133
- found = append (found , c .ID )
1134
- }
1135
- require .Contains (t , found , ct .Container .ID , "expected to find first container without label filter" )
1136
- require .Contains (t , found , ct2 .Container .ID , "expected to find first container without label filter" )
1206
+ ctrl := gomock .NewController (t )
1207
+ mcl := agentcontainers .NewMockLister (ctrl )
1208
+ expected , expectedErr := tc .setupMock (mcl )
1209
+ client , db := coderdtest .NewWithDatabase (t , & coderdtest.Options {})
1210
+ user := coderdtest .CreateFirstUser (t , client )
1211
+ r := dbfake .WorkspaceBuild (t , db , database.WorkspaceTable {
1212
+ OrganizationID : user .OrganizationID ,
1213
+ OwnerID : user .UserID ,
1214
+ }).WithAgent (func (agents []* proto.Agent ) []* proto.Agent {
1215
+ return agents
1216
+ }).Do ()
1217
+ _ = agenttest .New (t , client .URL , r .AgentToken , func (opts * agent.Options ) {
1218
+ opts .ContainerLister = mcl
1219
+ })
1220
+ resources := coderdtest .NewWorkspaceAgentWaiter (t , client , r .Workspace .ID ).Wait ()
1221
+ require .Len (t , resources , 1 , "expected one resource" )
1222
+ require .Len (t , resources [0 ].Agents , 1 , "expected one agent" )
1223
+ agentID := resources [0 ].Agents [0 ].ID
1224
+
1225
+ ctx := testutil .Context (t , testutil .WaitLong )
1226
+
1227
+ // List containers and ensure we get the expected mocked response.
1228
+ res , err := client .WorkspaceAgentListContainers (ctx , agentID , nil )
1229
+ if expectedErr != nil {
1230
+ require .Contains (t , err .Error (), expectedErr .Error (), "unexpected error" )
1231
+ require .Empty (t , res , "expected empty response" )
1232
+ } else {
1233
+ require .NoError (t , err , "failed to list all containers" )
1234
+ if diff := cmp .Diff (expected , res ); diff != "" {
1235
+ t .Fatalf ("unexpected response (-want +got):\n %s" , diff )
1236
+ }
1237
+ }
1238
+ })
1239
+ }
1240
+ })
1137
1241
}
1138
1242
1139
1243
func TestWorkspaceAgentAppHealth (t * testing.T ) {
0 commit comments