@@ -11,7 +11,9 @@ import (
11
11
"runtime"
12
12
"slices"
13
13
"strings"
14
+ "time"
14
15
16
+ "github.com/google/uuid"
15
17
"github.com/skratchdot/open-golang/open"
16
18
"golang.org/x/xerrors"
17
19
@@ -42,7 +44,6 @@ func (r *RootCmd) openVSCode() *serpent.Command {
42
44
generateToken bool
43
45
testOpenError bool
44
46
appearanceConfig codersdk.AppearanceConfig
45
- containerName string
46
47
)
47
48
48
49
client := new (codersdk.Client )
@@ -79,6 +80,61 @@ func (r *RootCmd) openVSCode() *serpent.Command {
79
80
workspaceName := workspace .Name + "." + workspaceAgent .Name
80
81
insideThisWorkspace := insideAWorkspace && inWorkspaceName == workspaceName
81
82
83
+ var parentWorkspaceAgent codersdk.WorkspaceAgent
84
+ var devcontainer codersdk.WorkspaceAgentDevcontainer
85
+ if workspaceAgent .ParentID .Valid {
86
+ // This is likely a devcontainer agent, so we need to find the
87
+ // parent workspace agent as well as the devcontainer.
88
+ for _ , otherAgent := range otherWorkspaceAgents {
89
+ if otherAgent .ID == workspaceAgent .ParentID .UUID {
90
+ parentWorkspaceAgent = otherAgent
91
+ break
92
+ }
93
+ }
94
+ if parentWorkspaceAgent .ID == uuid .Nil {
95
+ return xerrors .Errorf ("parent workspace agent %s not found" , workspaceAgent .ParentID .UUID )
96
+ }
97
+
98
+ printedWaiting := false
99
+ for {
100
+ resp , err := client .WorkspaceAgentListContainers (ctx , parentWorkspaceAgent .ID , nil )
101
+ if err != nil {
102
+ return xerrors .Errorf ("list parent workspace agent containers: %w" , err )
103
+ }
104
+
105
+ for _ , dc := range resp .Devcontainers {
106
+ if dc .Agent .ID == workspaceAgent .ID {
107
+ devcontainer = dc
108
+ break
109
+ }
110
+ }
111
+ if devcontainer .ID == uuid .Nil {
112
+ cliui .Warnf (inv .Stderr , "Devcontainer for agent %q not found, opening as a regular workspace" , workspaceAgent .Name )
113
+ parentWorkspaceAgent = codersdk.WorkspaceAgent {} // Reset to empty, so we don't use it later.
114
+ break
115
+ }
116
+
117
+ // Precondition, the devcontainer must be running to enter
118
+ // it. Once running, devcontainer.Container will be set.
119
+ if devcontainer .Status == codersdk .WorkspaceAgentDevcontainerStatusRunning {
120
+ break
121
+ }
122
+ if devcontainer .Status != codersdk .WorkspaceAgentDevcontainerStatusStarting {
123
+ return xerrors .Errorf ("devcontainer %q is in unexpected status %q, expected %q or %q" ,
124
+ devcontainer .Name , devcontainer .Status ,
125
+ codersdk .WorkspaceAgentDevcontainerStatusRunning ,
126
+ codersdk .WorkspaceAgentDevcontainerStatusStarting ,
127
+ )
128
+ }
129
+
130
+ if ! printedWaiting {
131
+ _ , _ = fmt .Fprintf (inv .Stderr , "Waiting for devcontainer %q status to change from %q to %q...\n " , devcontainer .Name , devcontainer .Status , codersdk .WorkspaceAgentDevcontainerStatusRunning )
132
+ printedWaiting = true
133
+ }
134
+ time .Sleep (5 * time .Second ) // Wait a bit before retrying.
135
+ }
136
+ }
137
+
82
138
if ! insideThisWorkspace {
83
139
// Wait for the agent to connect, we don't care about readiness
84
140
// otherwise (e.g. wait).
@@ -99,6 +155,9 @@ func (r *RootCmd) openVSCode() *serpent.Command {
99
155
// the created state, so we need to wait for that to happen.
100
156
// However, if no directory is set, the expanded directory will
101
157
// not be set either.
158
+ //
159
+ // Note that this is irrelevant for devcontainer sub agents, as
160
+ // they always have a directory set.
102
161
if workspaceAgent .Directory != "" {
103
162
workspace , workspaceAgent , err = waitForAgentCond (ctx , client , workspace , workspaceAgent , func (_ codersdk.WorkspaceAgent ) bool {
104
163
return workspaceAgent .LifecycleState != codersdk .WorkspaceAgentLifecycleCreated
@@ -114,41 +173,6 @@ func (r *RootCmd) openVSCode() *serpent.Command {
114
173
directory = inv .Args [1 ]
115
174
}
116
175
117
- if containerName != "" {
118
- containers , err := client .WorkspaceAgentListContainers (ctx , workspaceAgent .ID , map [string ]string {"devcontainer.local_folder" : "" })
119
- if err != nil {
120
- return xerrors .Errorf ("list workspace agent containers: %w" , err )
121
- }
122
-
123
- var foundContainer bool
124
-
125
- for _ , container := range containers .Containers {
126
- if container .FriendlyName != containerName {
127
- continue
128
- }
129
-
130
- foundContainer = true
131
-
132
- if directory == "" {
133
- localFolder , ok := container .Labels ["devcontainer.local_folder" ]
134
- if ! ok {
135
- return xerrors .New ("container missing `devcontainer.local_folder` label" )
136
- }
137
-
138
- directory , ok = container .Volumes [localFolder ]
139
- if ! ok {
140
- return xerrors .New ("container missing volume for `devcontainer.local_folder`" )
141
- }
142
- }
143
-
144
- break
145
- }
146
-
147
- if ! foundContainer {
148
- return xerrors .New ("no container found" )
149
- }
150
- }
151
-
152
176
directory , err = resolveAgentAbsPath (workspaceAgent .ExpandedDirectory , directory , workspaceAgent .OperatingSystem , insideThisWorkspace )
153
177
if err != nil {
154
178
return xerrors .Errorf ("resolve agent path: %w" , err )
@@ -174,14 +198,16 @@ func (r *RootCmd) openVSCode() *serpent.Command {
174
198
u * url.URL
175
199
qp url.Values
176
200
)
177
- if containerName != "" {
201
+ if devcontainer . ID != uuid . Nil {
178
202
u , qp = buildVSCodeWorkspaceDevContainerLink (
179
203
token ,
180
204
client .URL .String (),
181
205
workspace ,
182
- workspaceAgent ,
183
- containerName ,
206
+ parentWorkspaceAgent ,
207
+ devcontainer . Container . FriendlyName ,
184
208
directory ,
209
+ devcontainer .WorkspaceFolder ,
210
+ devcontainer .ConfigPath ,
185
211
)
186
212
} else {
187
213
u , qp = buildVSCodeWorkspaceLink (
@@ -247,13 +273,6 @@ func (r *RootCmd) openVSCode() *serpent.Command {
247
273
),
248
274
Value : serpent .BoolOf (& generateToken ),
249
275
},
250
- {
251
- Flag : "container" ,
252
- FlagShorthand : "c" ,
253
- Description : "Container name to connect to in the workspace." ,
254
- Value : serpent .StringOf (& containerName ),
255
- Hidden : true , // Hidden until this features is at least in beta.
256
- },
257
276
{
258
277
Flag : "test.open-error" ,
259
278
Description : "Don't run the open command." ,
@@ -430,8 +449,14 @@ func buildVSCodeWorkspaceDevContainerLink(
430
449
workspaceAgent codersdk.WorkspaceAgent ,
431
450
containerName string ,
432
451
containerFolder string ,
452
+ localWorkspaceFolder string ,
453
+ localConfigFile string ,
433
454
) (* url.URL , url.Values ) {
434
455
containerFolder = filepath .ToSlash (containerFolder )
456
+ localWorkspaceFolder = filepath .ToSlash (localWorkspaceFolder )
457
+ if localConfigFile != "" {
458
+ localConfigFile = filepath .ToSlash (localConfigFile )
459
+ }
435
460
436
461
qp := url.Values {}
437
462
qp .Add ("url" , clientURL )
@@ -440,6 +465,8 @@ func buildVSCodeWorkspaceDevContainerLink(
440
465
qp .Add ("agent" , workspaceAgent .Name )
441
466
qp .Add ("devContainerName" , containerName )
442
467
qp .Add ("devContainerFolder" , containerFolder )
468
+ qp .Add ("localWorkspaceFolder" , localWorkspaceFolder )
469
+ qp .Add ("localConfigFile" , localConfigFile )
443
470
444
471
if token != "" {
445
472
qp .Add ("token" , token )
0 commit comments