Skip to content

Commit dd656a7

Browse files
committed
Merge remote-tracking branch 'origin/main' into jjs/insert-prebuilds
2 parents b15b97a + b60934b commit dd656a7

20 files changed

+927
-64
lines changed

cli/open.go

+127-18
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,48 @@ 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+
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+
directory, err = resolveAgentAbsPath(workspaceAgent.ExpandedDirectory, directory, workspaceAgent.OperatingSystem, insideThisWorkspace)
153+
if err != nil {
154+
return xerrors.Errorf("resolve agent path: %w", err)
134155
}
135156

157+
var token string
136158
// We always set the token if we believe we can open without
137159
// printing the URI, otherwise the token must be explicitly
138160
// requested as it will be printed in plain text.
@@ -145,10 +167,31 @@ func (r *RootCmd) openVSCode() *serpent.Command {
145167
if err != nil {
146168
return xerrors.Errorf("create API key: %w", err)
147169
}
148-
qp.Add("token", apiKey.Key)
170+
token = apiKey.Key
149171
}
150172

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

153196
openingPath := workspaceName
154197
if directory != "" {
@@ -204,6 +247,13 @@ func (r *RootCmd) openVSCode() *serpent.Command {
204247
),
205248
Value: serpent.BoolOf(&generateToken),
206249
},
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+
},
207257
{
208258
Flag: "test.open-error",
209259
Description: "Don't run the open command.",
@@ -344,6 +394,65 @@ func (r *RootCmd) openApp() *serpent.Command {
344394
return cmd
345395
}
346396

397+
func buildVSCodeWorkspaceLink(
398+
token string,
399+
clientURL string,
400+
workspace codersdk.Workspace,
401+
workspaceAgent codersdk.WorkspaceAgent,
402+
directory string,
403+
) (*url.URL, url.Values) {
404+
qp := url.Values{}
405+
qp.Add("url", clientURL)
406+
qp.Add("owner", workspace.OwnerName)
407+
qp.Add("workspace", workspace.Name)
408+
qp.Add("agent", workspaceAgent.Name)
409+
410+
if directory != "" {
411+
qp.Add("folder", directory)
412+
}
413+
414+
if token != "" {
415+
qp.Add("token", token)
416+
}
417+
418+
return &url.URL{
419+
Scheme: "vscode",
420+
Host: "coder.coder-remote",
421+
Path: "/open",
422+
RawQuery: qp.Encode(),
423+
}, qp
424+
}
425+
426+
func buildVSCodeWorkspaceDevContainerLink(
427+
token string,
428+
clientURL string,
429+
workspace codersdk.Workspace,
430+
workspaceAgent codersdk.WorkspaceAgent,
431+
containerName string,
432+
containerFolder string,
433+
) (*url.URL, url.Values) {
434+
containerFolder = filepath.ToSlash(containerFolder)
435+
436+
qp := url.Values{}
437+
qp.Add("url", clientURL)
438+
qp.Add("owner", workspace.OwnerName)
439+
qp.Add("workspace", workspace.Name)
440+
qp.Add("agent", workspaceAgent.Name)
441+
qp.Add("devContainerName", containerName)
442+
qp.Add("devContainerFolder", containerFolder)
443+
444+
if token != "" {
445+
qp.Add("token", token)
446+
}
447+
448+
return &url.URL{
449+
Scheme: "vscode",
450+
Host: "coder.coder-remote",
451+
Path: "/openDevContainer",
452+
RawQuery: qp.Encode(),
453+
}, qp
454+
}
455+
347456
// waitForAgentCond uses the watch workspace API to update the agent information
348457
// until the condition is met.
349458
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)