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)
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