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)