Skip to content

Commit c29d517

Browse files
feat(cli): support opening devcontainers in vscode
1 parent fcac4ab commit c29d517

File tree

2 files changed

+461
-18
lines changed

2 files changed

+461
-18
lines changed

cli/open.go

Lines changed: 122 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ func (r *RootCmd) openVSCode() *serpent.Command {
4242
generateToken bool
4343
testOpenError bool
4444
appearanceConfig codersdk.AppearanceConfig
45+
containerName string
4546
)
4647

4748
client := new(codersdk.Client)
@@ -112,27 +113,46 @@ func (r *RootCmd) openVSCode() *serpent.Command {
112113
if len(inv.Args) > 1 {
113114
directory = inv.Args[1]
114115
}
115-
directory, err = resolveAgentAbsPath(workspaceAgent.ExpandedDirectory, directory, workspaceAgent.OperatingSystem, insideThisWorkspace)
116-
if err != nil {
117-
return xerrors.Errorf("resolve agent path: %w", err)
118-
}
119116

120-
u := &url.URL{
121-
Scheme: "vscode",
122-
Host: "coder.coder-remote",
123-
Path: "/open",
124-
}
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+
}
125122

126-
qp := url.Values{}
123+
var foundContainer bool
127124

128-
qp.Add("url", client.URL.String())
129-
qp.Add("owner", workspace.OwnerName)
130-
qp.Add("workspace", workspace.Name)
131-
qp.Add("agent", workspaceAgent.Name)
132-
if directory != "" {
133-
qp.Add("folder", directory)
125+
for _, container := range containers.Containers {
126+
if container.FriendlyName == containerName {
127+
foundContainer = true
128+
129+
if directory == "" {
130+
localFolder, ok := container.Labels["devcontainer.local_folder"]
131+
if !ok {
132+
return xerrors.New("container missing `devcontainer.local_folder` label")
133+
}
134+
135+
directory, ok = container.Volumes[localFolder]
136+
if !ok {
137+
return xerrors.New("container missing volume for `devcontainer.local_folder`")
138+
}
139+
}
140+
141+
break
142+
}
143+
}
144+
145+
if !foundContainer {
146+
return xerrors.New("no container found")
147+
}
148+
}
149+
150+
directory, err = resolveAgentAbsPath(workspaceAgent.ExpandedDirectory, directory, workspaceAgent.OperatingSystem, insideThisWorkspace)
151+
if err != nil {
152+
return xerrors.Errorf("resolve agent path: %w", err)
134153
}
135154

155+
var token string
136156
// We always set the token if we believe we can open without
137157
// printing the URI, otherwise the token must be explicitly
138158
// requested as it will be printed in plain text.
@@ -145,10 +165,31 @@ func (r *RootCmd) openVSCode() *serpent.Command {
145165
if err != nil {
146166
return xerrors.Errorf("create API key: %w", err)
147167
}
148-
qp.Add("token", apiKey.Key)
168+
token = apiKey.Key
149169
}
150170

151-
u.RawQuery = qp.Encode()
171+
var (
172+
u *url.URL
173+
qp url.Values
174+
)
175+
if containerName != "" {
176+
u, qp = buildVSCodeWorkspaceDevContainerLink(
177+
token,
178+
client.URL.String(),
179+
workspace,
180+
workspaceAgent,
181+
containerName,
182+
directory,
183+
)
184+
} else {
185+
u, qp = buildVSCodeWorkspaceLink(
186+
token,
187+
client.URL.String(),
188+
workspace,
189+
workspaceAgent,
190+
directory,
191+
)
192+
}
152193

153194
openingPath := workspaceName
154195
if directory != "" {
@@ -204,6 +245,12 @@ func (r *RootCmd) openVSCode() *serpent.Command {
204245
),
205246
Value: serpent.BoolOf(&generateToken),
206247
},
248+
{
249+
Flag: "container",
250+
FlagShorthand: "c",
251+
Description: "Container name to connect to in the workspace.",
252+
Value: serpent.StringOf(&containerName),
253+
},
207254
{
208255
Flag: "test.open-error",
209256
Description: "Don't run the open command.",
@@ -344,6 +391,63 @@ func (r *RootCmd) openApp() *serpent.Command {
344391
return cmd
345392
}
346393

394+
func buildVSCodeWorkspaceLink(
395+
token string,
396+
clientURL string,
397+
workspace codersdk.Workspace,
398+
workspaceAgent codersdk.WorkspaceAgent,
399+
directory string,
400+
) (*url.URL, url.Values) {
401+
qp := url.Values{}
402+
qp.Add("url", clientURL)
403+
qp.Add("owner", workspace.OwnerName)
404+
qp.Add("workspace", workspace.Name)
405+
qp.Add("agent", workspaceAgent.Name)
406+
407+
if directory != "" {
408+
qp.Add("folder", directory)
409+
}
410+
411+
if token != "" {
412+
qp.Add("token", token)
413+
}
414+
415+
return &url.URL{
416+
Scheme: "vscode",
417+
Host: "coder.coder-remote",
418+
Path: "/open",
419+
RawQuery: qp.Encode(),
420+
}, qp
421+
}
422+
423+
func buildVSCodeWorkspaceDevContainerLink(
424+
token string,
425+
clientURL string,
426+
workspace codersdk.Workspace,
427+
workspaceAgent codersdk.WorkspaceAgent,
428+
containerName string,
429+
containerFolder string,
430+
) (*url.URL, url.Values) {
431+
qp := url.Values{}
432+
qp.Add("url", clientURL)
433+
qp.Add("owner", workspace.OwnerName)
434+
qp.Add("workspace", workspace.Name)
435+
qp.Add("agent", workspaceAgent.Name)
436+
qp.Add("devContainerName", containerName)
437+
qp.Add("devContainerFolder", containerFolder)
438+
439+
if token != "" {
440+
qp.Add("token", token)
441+
}
442+
443+
return &url.URL{
444+
Scheme: "vscode",
445+
Host: "coder.coder-remote",
446+
Path: "/openDevContainer",
447+
RawQuery: qp.Encode(),
448+
}, qp
449+
}
450+
347451
// waitForAgentCond uses the watch workspace API to update the agent information
348452
// until the condition is met.
349453
func waitForAgentCond(ctx context.Context, client *codersdk.Client, workspace codersdk.Workspace, workspaceAgent codersdk.WorkspaceAgent, cond func(codersdk.WorkspaceAgent) bool) (codersdk.Workspace, codersdk.WorkspaceAgent, error) {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy