Skip to content

Commit ef8c417

Browse files
committed
cli: replace open vscode container with devcontainer sub agent
1 parent 627957c commit ef8c417

File tree

2 files changed

+190
-252
lines changed

2 files changed

+190
-252
lines changed

cli/open.go

Lines changed: 66 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import (
1111
"runtime"
1212
"slices"
1313
"strings"
14+
"time"
1415

16+
"github.com/google/uuid"
1517
"github.com/skratchdot/open-golang/open"
1618
"golang.org/x/xerrors"
1719

@@ -42,7 +44,6 @@ func (r *RootCmd) openVSCode() *serpent.Command {
4244
generateToken bool
4345
testOpenError bool
4446
appearanceConfig codersdk.AppearanceConfig
45-
containerName string
4647
)
4748

4849
client := new(codersdk.Client)
@@ -79,6 +80,54 @@ func (r *RootCmd) openVSCode() *serpent.Command {
7980
workspaceName := workspace.Name + "." + workspaceAgent.Name
8081
insideThisWorkspace := insideAWorkspace && inWorkspaceName == workspaceName
8182

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+
82131
if !insideThisWorkspace {
83132
// Wait for the agent to connect, we don't care about readiness
84133
// otherwise (e.g. wait).
@@ -99,6 +148,9 @@ func (r *RootCmd) openVSCode() *serpent.Command {
99148
// the created state, so we need to wait for that to happen.
100149
// However, if no directory is set, the expanded directory will
101150
// not be set either.
151+
//
152+
// Note that this is irrelevant for devcontainer sub agents, as
153+
// they always have a directory set.
102154
if workspaceAgent.Directory != "" {
103155
workspace, workspaceAgent, err = waitForAgentCond(ctx, client, workspace, workspaceAgent, func(_ codersdk.WorkspaceAgent) bool {
104156
return workspaceAgent.LifecycleState != codersdk.WorkspaceAgentLifecycleCreated
@@ -114,41 +166,6 @@ func (r *RootCmd) openVSCode() *serpent.Command {
114166
directory = inv.Args[1]
115167
}
116168

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-
152169
directory, err = resolveAgentAbsPath(workspaceAgent.ExpandedDirectory, directory, workspaceAgent.OperatingSystem, insideThisWorkspace)
153170
if err != nil {
154171
return xerrors.Errorf("resolve agent path: %w", err)
@@ -174,14 +191,16 @@ func (r *RootCmd) openVSCode() *serpent.Command {
174191
u *url.URL
175192
qp url.Values
176193
)
177-
if containerName != "" {
194+
if devcontainer.ID != uuid.Nil {
178195
u, qp = buildVSCodeWorkspaceDevContainerLink(
179196
token,
180197
client.URL.String(),
181198
workspace,
182-
workspaceAgent,
183-
containerName,
199+
parentWorkspaceAgent,
200+
devcontainer.Container.FriendlyName,
184201
directory,
202+
devcontainer.WorkspaceFolder,
203+
devcontainer.ConfigPath,
185204
)
186205
} else {
187206
u, qp = buildVSCodeWorkspaceLink(
@@ -247,13 +266,6 @@ func (r *RootCmd) openVSCode() *serpent.Command {
247266
),
248267
Value: serpent.BoolOf(&generateToken),
249268
},
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-
},
257269
{
258270
Flag: "test.open-error",
259271
Description: "Don't run the open command.",
@@ -430,8 +442,14 @@ func buildVSCodeWorkspaceDevContainerLink(
430442
workspaceAgent codersdk.WorkspaceAgent,
431443
containerName string,
432444
containerFolder string,
445+
localWorkspaceFolder string,
446+
localConfigFile string,
433447
) (*url.URL, url.Values) {
434448
containerFolder = filepath.ToSlash(containerFolder)
449+
localWorkspaceFolder = filepath.ToSlash(localWorkspaceFolder)
450+
if localConfigFile != "" {
451+
localConfigFile = filepath.ToSlash(localConfigFile)
452+
}
435453

436454
qp := url.Values{}
437455
qp.Add("url", clientURL)
@@ -440,6 +458,8 @@ func buildVSCodeWorkspaceDevContainerLink(
440458
qp.Add("agent", workspaceAgent.Name)
441459
qp.Add("devContainerName", containerName)
442460
qp.Add("devContainerFolder", containerFolder)
461+
qp.Add("localWorkspaceFolder", localWorkspaceFolder)
462+
qp.Add("localConfigFile", localConfigFile)
443463

444464
if token != "" {
445465
qp.Add("token", token)

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