@@ -12,6 +12,7 @@ import (
12
12
"golang.org/x/mod/semver"
13
13
14
14
"github.com/coder/coder/v2/coderd/database/dbtime"
15
+ "github.com/coder/coder/v2/coderd/util/slice"
15
16
"github.com/coder/coder/v2/codersdk"
16
17
"github.com/coder/pretty"
17
18
)
@@ -29,6 +30,7 @@ type WorkspaceResourcesOptions struct {
29
30
ServerVersion string
30
31
ListeningPorts map [uuid.UUID ]codersdk.WorkspaceAgentListeningPortsResponse
31
32
Devcontainers map [uuid.UUID ]codersdk.WorkspaceAgentListContainersResponse
33
+ ShowDetails bool
32
34
}
33
35
34
36
// WorkspaceResources displays the connection status and tree-view of provided resources.
@@ -69,7 +71,11 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource
69
71
70
72
totalAgents := 0
71
73
for _ ,resource := range resources {
72
- totalAgents += len (resource .Agents )
74
+ for _ ,agent := range resource .Agents {
75
+ if ! agent .ParentID .Valid {
76
+ totalAgents ++
77
+ }
78
+ }
73
79
}
74
80
75
81
for _ ,resource := range resources {
@@ -94,12 +100,15 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource
94
100
"" ,
95
101
})
96
102
// Display all agents associated with the resource.
97
- for index ,agent := range resource .Agents {
103
+ agents := slice .Filter (resource .Agents ,func (agent codersdk.WorkspaceAgent )bool {
104
+ return ! agent .ParentID .Valid
105
+ })
106
+ for index ,agent := range agents {
98
107
tableWriter .AppendRow (renderAgentRow (agent ,index ,totalAgents ,options ))
99
108
for _ ,row := range renderListeningPorts (options ,agent .ID ,index ,totalAgents ) {
100
109
tableWriter .AppendRow (row )
101
110
}
102
- for _ ,row := range renderDevcontainers (options ,agent .ID ,index ,totalAgents ) {
111
+ for _ ,row := range renderDevcontainers (resources , options ,agent .ID ,index ,totalAgents ) {
103
112
tableWriter .AppendRow (row )
104
113
}
105
114
}
@@ -125,7 +134,7 @@ func renderAgentRow(agent codersdk.WorkspaceAgent, index, totalAgents int, optio
125
134
}
126
135
if ! options .HideAccess {
127
136
sshCommand := "coder ssh " + options .WorkspaceName
128
- if totalAgents > 1 {
137
+ if totalAgents > 1 || len ( options . Devcontainers ) > 0 {
129
138
sshCommand += "." + agent .Name
130
139
}
131
140
sshCommand = pretty .Sprint (DefaultStyles .Code ,sshCommand )
@@ -164,45 +173,129 @@ func renderPortRow(port codersdk.WorkspaceAgentListeningPort, idx, total int) ta
164
173
return table.Row {sb .String ()}
165
174
}
166
175
167
- func renderDevcontainers (wro WorkspaceResourcesOptions ,agentID uuid.UUID ,index ,totalAgents int ) []table.Row {
176
+ func renderDevcontainers (resources []codersdk. WorkspaceResource , wro WorkspaceResourcesOptions ,agentID uuid.UUID ,index ,totalAgents int ) []table.Row {
168
177
var rows []table.Row
169
178
if wro .Devcontainers == nil {
170
179
return []table.Row {}
171
180
}
172
181
dc ,ok := wro .Devcontainers [agentID ]
173
- if ! ok || len (dc .Containers )== 0 {
182
+ if ! ok || len (dc .Devcontainers )== 0 {
174
183
return []table.Row {}
175
184
}
176
185
rows = append (rows , table.Row {
177
186
fmt .Sprintf (" %s─ %s" ,renderPipe (index ,totalAgents ),"Devcontainers" ),
178
187
})
179
- for idx ,container := range dc .Containers {
180
- rows = append (rows ,renderDevcontainerRow (container , idx ,len (dc .Containers )) )
188
+ for idx ,devcontainer := range dc .Devcontainers {
189
+ rows = append (rows ,renderDevcontainerRow (resources , devcontainer , idx ,len (dc .Devcontainers ), wro ) ... )
181
190
}
182
191
return rows
183
192
}
184
193
185
- func renderDevcontainerRow (container codersdk.WorkspaceAgentContainer ,index ,total int ) table.Row {
186
- var row table.Row
187
- var sb strings.Builder
188
- _ ,_ = sb .WriteString (" " )
189
- _ ,_ = sb .WriteString (renderPipe (index ,total ))
190
- _ ,_ = sb .WriteString ("─ " )
191
- _ ,_ = sb .WriteString (pretty .Sprintf (DefaultStyles .Code ,"%s" ,container .FriendlyName ))
192
- row = append (row ,sb .String ())
193
- sb .Reset ()
194
- if container .Running {
195
- _ ,_ = sb .WriteString (pretty .Sprintf (DefaultStyles .Keyword ,"(%s)" ,container .Status ))
196
- }else {
197
- _ ,_ = sb .WriteString (pretty .Sprintf (DefaultStyles .Error ,"(%s)" ,container .Status ))
194
+ func renderDevcontainerRow (resources []codersdk.WorkspaceResource ,devcontainer codersdk.WorkspaceAgentDevcontainer ,index ,total int ,wro WorkspaceResourcesOptions ) []table.Row {
195
+ var rows []table.Row
196
+
197
+ // If the devcontainer is running and has an associated agent, we want to
198
+ // display the agent's details. Otherwise, we just display the devcontainer
199
+ // name and status.
200
+ var subAgent * codersdk.WorkspaceAgent
201
+ displayName := devcontainer .Name
202
+ if devcontainer .Agent != nil && devcontainer .Status == codersdk .WorkspaceAgentDevcontainerStatusRunning {
203
+ for _ ,resource := range resources {
204
+ if agent ,found := slice .Find (resource .Agents ,func (agent codersdk.WorkspaceAgent )bool {
205
+ return agent .ID == devcontainer .Agent .ID
206
+ });found {
207
+ subAgent = & agent
208
+ break
209
+ }
210
+ }
211
+ if subAgent != nil {
212
+ displayName = subAgent .Name
213
+ displayName += fmt .Sprintf (" (%s, %s)" ,subAgent .OperatingSystem ,subAgent .Architecture )
214
+ }
215
+ }
216
+
217
+ if devcontainer .Container != nil {
218
+ displayName += " " + pretty .Sprint (DefaultStyles .Keyword ,"[" + devcontainer .Container .FriendlyName + "]" )
219
+ }
220
+
221
+ // Build the main row.
222
+ row := table.Row {
223
+ fmt .Sprintf (" %s─ %s" ,renderPipe (index ,total ),displayName ),
224
+ }
225
+
226
+ // Add status, health, and version columns.
227
+ if ! wro .HideAgentState {
228
+ if subAgent != nil {
229
+ row = append (row ,renderAgentStatus (* subAgent ))
230
+ row = append (row ,renderAgentHealth (* subAgent ))
231
+ row = append (row ,renderAgentVersion (subAgent .Version ,wro .ServerVersion ))
232
+ }else {
233
+ row = append (row ,renderDevcontainerStatus (devcontainer .Status ))
234
+ row = append (row ,"" )// No health for devcontainer without agent.
235
+ row = append (row ,"" )// No version for devcontainer without agent.
236
+ }
237
+ }
238
+
239
+ // Add access column.
240
+ if ! wro .HideAccess {
241
+ if subAgent != nil {
242
+ accessString := fmt .Sprintf ("coder ssh %s.%s" ,wro .WorkspaceName ,subAgent .Name )
243
+ row = append (row ,pretty .Sprint (DefaultStyles .Code ,accessString ))
244
+ }else {
245
+ row = append (row ,"" )// No access for devcontainers without agent.
246
+ }
247
+ }
248
+
249
+ rows = append (rows ,row )
250
+
251
+ // Add error message if present.
252
+ if errorMessage := devcontainer .Error ;errorMessage != "" {
253
+ // Cap error message length for display.
254
+ if ! wro .ShowDetails && len (errorMessage )> 80 {
255
+ errorMessage = errorMessage [:79 ]+ "…"
256
+ }
257
+ errorRow := table.Row {
258
+ " × " + pretty .Sprint (DefaultStyles .Error ,errorMessage ),
259
+ "" ,
260
+ "" ,
261
+ "" ,
262
+ }
263
+ if ! wro .HideAccess {
264
+ errorRow = append (errorRow ,"" )
265
+ }
266
+ rows = append (rows ,errorRow )
267
+ }
268
+
269
+ // Add listening ports for the devcontainer agent.
270
+ if subAgent != nil {
271
+ portRows := renderListeningPorts (wro ,subAgent .ID ,index ,total )
272
+ for _ ,portRow := range portRows {
273
+ // Adjust indentation for ports under devcontainer agent.
274
+ if len (portRow )> 0 {
275
+ if str ,ok := portRow [0 ].(string );ok {
276
+ portRow [0 ]= " " + str // Add extra indentation.
277
+ }
278
+ }
279
+ rows = append (rows ,portRow )
280
+ }
281
+ }
282
+
283
+ return rows
284
+ }
285
+
286
+ func renderDevcontainerStatus (status codersdk.WorkspaceAgentDevcontainerStatus )string {
287
+ switch status {
288
+ case codersdk .WorkspaceAgentDevcontainerStatusRunning :
289
+ return pretty .Sprint (DefaultStyles .Keyword ,"▶ running" )
290
+ case codersdk .WorkspaceAgentDevcontainerStatusStopped :
291
+ return pretty .Sprint (DefaultStyles .Placeholder ,"⏹ stopped" )
292
+ case codersdk .WorkspaceAgentDevcontainerStatusStarting :
293
+ return pretty .Sprint (DefaultStyles .Warn ,"⧗ starting" )
294
+ case codersdk .WorkspaceAgentDevcontainerStatusError :
295
+ return pretty .Sprint (DefaultStyles .Error ,"✘ error" )
296
+ default :
297
+ return pretty .Sprint (DefaultStyles .Placeholder ,"○ " + string (status ))
198
298
}
199
- row = append (row ,sb .String ())
200
- sb .Reset ()
201
- // "health" is not applicable here.
202
- row = append (row ,sb .String ())
203
- _ ,_ = sb .WriteString (container .Image )
204
- row = append (row ,sb .String ())
205
- return row
206
299
}
207
300
208
301
func renderAgentStatus (agent codersdk.WorkspaceAgent )string {