@@ -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,54 @@ 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
+
123
+ if ! printedWaiting {
124
+ _ , _ = fmt .Fprintf (inv .Stderr , "Waiting for devcontainer %q status to change from %q to %q...\n " , devcontainer .Name , devcontainer .Status , codersdk .WorkspaceAgentDevcontainerStatusRunning )
125
+ printedWaiting = true
126
+ }
127
+ time .Sleep (5 * time .Second ) // Wait a bit before retrying.
128
+ }
129
+ }
130
+
82
131
if ! insideThisWorkspace {
83
132
// Wait for the agent to connect, we don't care about readiness
84
133
// otherwise (e.g. wait).
@@ -99,6 +148,9 @@ func (r *RootCmd) openVSCode() *serpent.Command {
99
148
// the created state, so we need to wait for that to happen.
100
149
// However, if no directory is set, the expanded directory will
101
150
// not be set either.
151
+ //
152
+ // Note that this is irrelevant for devcontainer sub agents, as
153
+ // they always have a directory set.
102
154
if workspaceAgent .Directory != "" {
103
155
workspace , workspaceAgent , err = waitForAgentCond (ctx , client , workspace , workspaceAgent , func (_ codersdk.WorkspaceAgent ) bool {
104
156
return workspaceAgent .LifecycleState != codersdk .WorkspaceAgentLifecycleCreated
@@ -114,41 +166,6 @@ func (r *RootCmd) openVSCode() *serpent.Command {
114
166
directory = inv .Args [1 ]
115
167
}
116
168
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
169
directory , err = resolveAgentAbsPath (workspaceAgent .ExpandedDirectory , directory , workspaceAgent .OperatingSystem , insideThisWorkspace )
153
170
if err != nil {
154
171
return xerrors .Errorf ("resolve agent path: %w" , err )
@@ -174,14 +191,16 @@ func (r *RootCmd) openVSCode() *serpent.Command {
174
191
u * url.URL
175
192
qp url.Values
176
193
)
177
- if containerName != "" {
194
+ if devcontainer . ID != uuid . Nil {
178
195
u , qp = buildVSCodeWorkspaceDevContainerLink (
179
196
token ,
180
197
client .URL .String (),
181
198
workspace ,
182
- workspaceAgent ,
183
- containerName ,
199
+ parentWorkspaceAgent ,
200
+ devcontainer . Container . FriendlyName ,
184
201
directory ,
202
+ devcontainer .WorkspaceFolder ,
203
+ devcontainer .ConfigPath ,
185
204
)
186
205
} else {
187
206
u , qp = buildVSCodeWorkspaceLink (
@@ -247,13 +266,6 @@ func (r *RootCmd) openVSCode() *serpent.Command {
247
266
),
248
267
Value : serpent .BoolOf (& generateToken ),
249
268
},
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
269
{
258
270
Flag : "test.open-error" ,
259
271
Description : "Don't run the open command." ,
@@ -430,8 +442,14 @@ func buildVSCodeWorkspaceDevContainerLink(
430
442
workspaceAgent codersdk.WorkspaceAgent ,
431
443
containerName string ,
432
444
containerFolder string ,
445
+ localWorkspaceFolder string ,
446
+ localConfigFile string ,
433
447
) (* url.URL , url.Values ) {
434
448
containerFolder = filepath .ToSlash (containerFolder )
449
+ localWorkspaceFolder = filepath .ToSlash (localWorkspaceFolder )
450
+ if localConfigFile != "" {
451
+ localConfigFile = filepath .ToSlash (localConfigFile )
452
+ }
435
453
436
454
qp := url.Values {}
437
455
qp .Add ("url" , clientURL )
@@ -440,6 +458,8 @@ func buildVSCodeWorkspaceDevContainerLink(
440
458
qp .Add ("agent" , workspaceAgent .Name )
441
459
qp .Add ("devContainerName" , containerName )
442
460
qp .Add ("devContainerFolder" , containerFolder )
461
+ qp .Add ("localWorkspaceFolder" , localWorkspaceFolder )
462
+ qp .Add ("localConfigFile" , localConfigFile )
443
463
444
464
if token != "" {
445
465
qp .Add ("token" , token )
0 commit comments