Skip to content

Commit 6c3d31d

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

File tree

2 files changed

+197
-252
lines changed

2 files changed

+197
-252
lines changed

cli/open.go

Lines changed: 73 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,61 @@ 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+
if devcontainer.Status != codersdk.WorkspaceAgentDevcontainerStatusStarting {
123+
return xerrors.Errorf("devcontainer %q is in unexpected status %q, expected %q or %q",
124+
devcontainer.Name, devcontainer.Status,
125+
codersdk.WorkspaceAgentDevcontainerStatusRunning,
126+
codersdk.WorkspaceAgentDevcontainerStatusStarting,
127+
)
128+
}
129+
130+
if !printedWaiting {
131+
_, _ = fmt.Fprintf(inv.Stderr, "Waiting for devcontainer %q status to change from %q to %q...\n", devcontainer.Name, devcontainer.Status, codersdk.WorkspaceAgentDevcontainerStatusRunning)
132+
printedWaiting = true
133+
}
134+
time.Sleep(5 * time.Second) // Wait a bit before retrying.
135+
}
136+
}
137+
82138
if !insideThisWorkspace {
83139
// Wait for the agent to connect, we don't care about readiness
84140
// otherwise (e.g. wait).
@@ -99,6 +155,9 @@ func (r *RootCmd) openVSCode() *serpent.Command {
99155
// the created state, so we need to wait for that to happen.
100156
// However, if no directory is set, the expanded directory will
101157
// not be set either.
158+
//
159+
// Note that this is irrelevant for devcontainer sub agents, as
160+
// they always have a directory set.
102161
if workspaceAgent.Directory != "" {
103162
workspace, workspaceAgent, err = waitForAgentCond(ctx, client, workspace, workspaceAgent, func(_ codersdk.WorkspaceAgent) bool {
104163
return workspaceAgent.LifecycleState != codersdk.WorkspaceAgentLifecycleCreated
@@ -114,41 +173,6 @@ func (r *RootCmd) openVSCode() *serpent.Command {
114173
directory = inv.Args[1]
115174
}
116175

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-
152176
directory, err = resolveAgentAbsPath(workspaceAgent.ExpandedDirectory, directory, workspaceAgent.OperatingSystem, insideThisWorkspace)
153177
if err != nil {
154178
return xerrors.Errorf("resolve agent path: %w", err)
@@ -174,14 +198,16 @@ func (r *RootCmd) openVSCode() *serpent.Command {
174198
u *url.URL
175199
qp url.Values
176200
)
177-
if containerName != "" {
201+
if devcontainer.ID != uuid.Nil {
178202
u, qp = buildVSCodeWorkspaceDevContainerLink(
179203
token,
180204
client.URL.String(),
181205
workspace,
182-
workspaceAgent,
183-
containerName,
206+
parentWorkspaceAgent,
207+
devcontainer.Container.FriendlyName,
184208
directory,
209+
devcontainer.WorkspaceFolder,
210+
devcontainer.ConfigPath,
185211
)
186212
} else {
187213
u, qp = buildVSCodeWorkspaceLink(
@@ -247,13 +273,6 @@ func (r *RootCmd) openVSCode() *serpent.Command {
247273
),
248274
Value: serpent.BoolOf(&generateToken),
249275
},
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-
},
257276
{
258277
Flag: "test.open-error",
259278
Description: "Don't run the open command.",
@@ -430,8 +449,14 @@ func buildVSCodeWorkspaceDevContainerLink(
430449
workspaceAgent codersdk.WorkspaceAgent,
431450
containerName string,
432451
containerFolder string,
452+
localWorkspaceFolder string,
453+
localConfigFile string,
433454
) (*url.URL, url.Values) {
434455
containerFolder = filepath.ToSlash(containerFolder)
456+
localWorkspaceFolder = filepath.ToSlash(localWorkspaceFolder)
457+
if localConfigFile != "" {
458+
localConfigFile = filepath.ToSlash(localConfigFile)
459+
}
435460

436461
qp := url.Values{}
437462
qp.Add("url", clientURL)
@@ -440,6 +465,8 @@ func buildVSCodeWorkspaceDevContainerLink(
440465
qp.Add("agent", workspaceAgent.Name)
441466
qp.Add("devContainerName", containerName)
442467
qp.Add("devContainerFolder", containerFolder)
468+
qp.Add("localWorkspaceFolder", localWorkspaceFolder)
469+
qp.Add("localConfigFile", localConfigFile)
443470

444471
if token != "" {
445472
qp.Add("token", token)

0 commit comments

Comments
 (0)