Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat(cli): replace open vscode container with devcontainer subagent#18765

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 32 additions & 32 deletionsagent/agentcontainers/devcontainercli.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -106,63 +106,63 @@ type DevcontainerCLI interface {

// DevcontainerCLIUpOptions are options for the devcontainer CLI Up
// command.
type DevcontainerCLIUpOptions func(*devcontainerCLIUpConfig)
type DevcontainerCLIUpOptions func(*DevcontainerCLIUpConfig)

typedevcontainerCLIUpConfig struct {
args []string // Additional arguments for the Up command.
stdout io.Writer
stderr io.Writer
typeDevcontainerCLIUpConfig struct {
Args []string // Additional arguments for the Up command.
Stdout io.Writer
Stderr io.Writer
}

// WithRemoveExistingContainer is an option to remove the existing
// container.
func WithRemoveExistingContainer() DevcontainerCLIUpOptions {
return func(o *devcontainerCLIUpConfig) {
o.args = append(o.args, "--remove-existing-container")
return func(o *DevcontainerCLIUpConfig) {
o.Args = append(o.Args, "--remove-existing-container")
}
}

// WithUpOutput sets additional stdout and stderr writers for logs
// during Up operations.
func WithUpOutput(stdout, stderr io.Writer) DevcontainerCLIUpOptions {
return func(o *devcontainerCLIUpConfig) {
o.stdout = stdout
o.stderr = stderr
return func(o *DevcontainerCLIUpConfig) {
o.Stdout = stdout
o.Stderr = stderr
}
}

// DevcontainerCLIExecOptions are options for the devcontainer CLI Exec
// command.
type DevcontainerCLIExecOptions func(*devcontainerCLIExecConfig)
type DevcontainerCLIExecOptions func(*DevcontainerCLIExecConfig)

typedevcontainerCLIExecConfig struct {
args []string // Additional arguments for the Exec command.
stdout io.Writer
stderr io.Writer
typeDevcontainerCLIExecConfig struct {
Args []string // Additional arguments for the Exec command.
Stdout io.Writer
Stderr io.Writer
}

// WithExecOutput sets additional stdout and stderr writers for logs
// during Exec operations.
func WithExecOutput(stdout, stderr io.Writer) DevcontainerCLIExecOptions {
return func(o *devcontainerCLIExecConfig) {
o.stdout = stdout
o.stderr = stderr
return func(o *DevcontainerCLIExecConfig) {
o.Stdout = stdout
o.Stderr = stderr
}
}

// WithExecContainerID sets the container ID to target a specific
// container.
func WithExecContainerID(id string) DevcontainerCLIExecOptions {
return func(o *devcontainerCLIExecConfig) {
o.args = append(o.args, "--container-id", id)
return func(o *DevcontainerCLIExecConfig) {
o.Args = append(o.Args, "--container-id", id)
}
}

// WithRemoteEnv sets environment variables for the Exec command.
func WithRemoteEnv(env ...string) DevcontainerCLIExecOptions {
return func(o *devcontainerCLIExecConfig) {
return func(o *DevcontainerCLIExecConfig) {
for _, e := range env {
o.args = append(o.args, "--remote-env", e)
o.Args = append(o.Args, "--remote-env", e)
}
}
}
Expand All@@ -185,8 +185,8 @@ func WithReadConfigOutput(stdout, stderr io.Writer) DevcontainerCLIReadConfigOpt
}
}

func applyDevcontainerCLIUpOptions(opts []DevcontainerCLIUpOptions)devcontainerCLIUpConfig {
conf :=devcontainerCLIUpConfig{stdout: io.Discard,stderr: io.Discard}
func applyDevcontainerCLIUpOptions(opts []DevcontainerCLIUpOptions)DevcontainerCLIUpConfig {
conf :=DevcontainerCLIUpConfig{Stdout: io.Discard,Stderr: io.Discard}
for _, opt := range opts {
if opt != nil {
opt(&conf)
Expand All@@ -195,8 +195,8 @@ func applyDevcontainerCLIUpOptions(opts []DevcontainerCLIUpOptions) devcontainer
return conf
}

func applyDevcontainerCLIExecOptions(opts []DevcontainerCLIExecOptions)devcontainerCLIExecConfig {
conf :=devcontainerCLIExecConfig{stdout: io.Discard,stderr: io.Discard}
func applyDevcontainerCLIExecOptions(opts []DevcontainerCLIExecOptions)DevcontainerCLIExecConfig {
conf :=DevcontainerCLIExecConfig{Stdout: io.Discard,Stderr: io.Discard}
for _, opt := range opts {
if opt != nil {
opt(&conf)
Expand DownExpand Up@@ -241,7 +241,7 @@ func (d *devcontainerCLI) Up(ctx context.Context, workspaceFolder, configPath st
if configPath != "" {
args = append(args, "--config", configPath)
}
args = append(args, conf.args...)
args = append(args, conf.Args...)
cmd := d.execer.CommandContext(ctx, "devcontainer", args...)

// Capture stdout for parsing and stream logs for both default and provided writers.
Expand All@@ -251,14 +251,14 @@ func (d *devcontainerCLI) Up(ctx context.Context, workspaceFolder, configPath st
&devcontainerCLILogWriter{
ctx: ctx,
logger: logger.With(slog.F("stdout", true)),
writer: conf.stdout,
writer: conf.Stdout,
},
)
// Stream stderr logs and provided writer if any.
cmd.Stderr = &devcontainerCLILogWriter{
ctx: ctx,
logger: logger.With(slog.F("stderr", true)),
writer: conf.stderr,
writer: conf.Stderr,
}

if err := cmd.Run(); err != nil {
Expand DownExpand Up@@ -293,17 +293,17 @@ func (d *devcontainerCLI) Exec(ctx context.Context, workspaceFolder, configPath
if configPath != "" {
args = append(args, "--config", configPath)
}
args = append(args, conf.args...)
args = append(args, conf.Args...)
args = append(args, cmd)
args = append(args, cmdArgs...)
c := d.execer.CommandContext(ctx, "devcontainer", args...)

c.Stdout = io.MultiWriter(conf.stdout, &devcontainerCLILogWriter{
c.Stdout = io.MultiWriter(conf.Stdout, &devcontainerCLILogWriter{
ctx: ctx,
logger: logger.With(slog.F("stdout", true)),
writer: io.Discard,
})
c.Stderr = io.MultiWriter(conf.stderr, &devcontainerCLILogWriter{
c.Stderr = io.MultiWriter(conf.Stderr, &devcontainerCLILogWriter{
ctx: ctx,
logger: logger.With(slog.F("stderr", true)),
writer: io.Discard,
Expand Down
2 changes: 1 addition & 1 deletioncli/exp_rpty.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -97,7 +97,7 @@ func handleRPTY(inv *serpent.Invocation, client *codersdk.Client, args handleRPT
reconnectID = uuid.New()
}

ws, agt, err := getWorkspaceAndAgent(ctx, inv, client, true, args.NamedWorkspace)
ws, agt,_,err := getWorkspaceAndAgent(ctx, inv, client, true, args.NamedWorkspace)
if err != nil {
return err
}
Expand Down
134 changes: 85 additions & 49 deletionscli/open.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,7 +11,9 @@ import (
"runtime"
"slices"
"strings"
"time"

"github.com/google/uuid"
"github.com/skratchdot/open-golang/open"
"golang.org/x/xerrors"

Expand DownExpand Up@@ -42,7 +44,6 @@ func (r *RootCmd) openVSCode() *serpent.Command {
generateToken bool
testOpenError bool
appearanceConfig codersdk.AppearanceConfig
containerName string
)

client := new(codersdk.Client)
Expand DownExpand Up@@ -71,14 +72,78 @@ func (r *RootCmd) openVSCode() *serpent.Command {
// need to wait for the agent to start.
workspaceQuery := inv.Args[0]
autostart := true
workspace, workspaceAgent, err := getWorkspaceAndAgent(ctx, inv, client, autostart, workspaceQuery)
workspace, workspaceAgent,otherWorkspaceAgents,err := getWorkspaceAndAgent(ctx, inv, client, autostart, workspaceQuery)
if err != nil {
return xerrors.Errorf("get workspace and agent: %w", err)
}

workspaceName := workspace.Name + "." + workspaceAgent.Name
insideThisWorkspace := insideAWorkspace && inWorkspaceName == workspaceName

// To properly work with devcontainers, VS Code has to connect to
// parent workspace agent. It will then proceed to enter the
// container given the correct parameters. There is inherently no
// dependency on the devcontainer agent in this scenario, but
// relying on it simplifies the logic and ensures the devcontainer
// is ready. To eliminate the dependency we would need to know that
// a sub-agent that hasn't been created yet may be a devcontainer,
// and thus will be created at a later time as well as expose the
// container folder on the API response.
var parentWorkspaceAgent codersdk.WorkspaceAgent
var devcontainer codersdk.WorkspaceAgentDevcontainer
if workspaceAgent.ParentID.Valid {
// This is likely a devcontainer agent, so we need to find the
// parent workspace agent as well as the devcontainer.
for _, otherAgent := range otherWorkspaceAgents {
if otherAgent.ID == workspaceAgent.ParentID.UUID {
parentWorkspaceAgent = otherAgent
break
}
}
if parentWorkspaceAgent.ID == uuid.Nil {
return xerrors.Errorf("parent workspace agent %s not found", workspaceAgent.ParentID.UUID)
}

printedWaiting := false
for {
resp, err := client.WorkspaceAgentListContainers(ctx, parentWorkspaceAgent.ID, nil)
if err != nil {
return xerrors.Errorf("list parent workspace agent containers: %w", err)
}

for _, dc := range resp.Devcontainers {
if dc.Agent.ID == workspaceAgent.ID {
devcontainer = dc
break
}
}
if devcontainer.ID == uuid.Nil {
cliui.Warnf(inv.Stderr, "Devcontainer %q not found, opening as a regular workspace...", workspaceAgent.Name)
parentWorkspaceAgent = codersdk.WorkspaceAgent{} // Reset to empty, so we don't use it later.
break
}

// Precondition, the devcontainer must be running to enter
// it. Once running, devcontainer.Container will be set.
if devcontainer.Status == codersdk.WorkspaceAgentDevcontainerStatusRunning {
break
}
if devcontainer.Status != codersdk.WorkspaceAgentDevcontainerStatusStarting {
return xerrors.Errorf("devcontainer %q is in unexpected status %q, expected %q or %q",
devcontainer.Name, devcontainer.Status,
codersdk.WorkspaceAgentDevcontainerStatusRunning,
codersdk.WorkspaceAgentDevcontainerStatusStarting,
)
}

if !printedWaiting {
_, _ = fmt.Fprintf(inv.Stderr, "Waiting for devcontainer %q status to change from %q to %q...\n", devcontainer.Name, devcontainer.Status, codersdk.WorkspaceAgentDevcontainerStatusRunning)
printedWaiting = true
}
time.Sleep(5 * time.Second) // Wait a bit before retrying.
}
}

if !insideThisWorkspace {
// Wait for the agent to connect, we don't care about readiness
// otherwise (e.g. wait).
Expand All@@ -99,6 +164,9 @@ func (r *RootCmd) openVSCode() *serpent.Command {
// the created state, so we need to wait for that to happen.
// However, if no directory is set, the expanded directory will
// not be set either.
//
// Note that this is irrelevant for devcontainer sub agents, as
// they always have a directory set.
if workspaceAgent.Directory != "" {
workspace, workspaceAgent, err = waitForAgentCond(ctx, client, workspace, workspaceAgent, func(_ codersdk.WorkspaceAgent) bool {
return workspaceAgent.LifecycleState != codersdk.WorkspaceAgentLifecycleCreated
Expand All@@ -114,41 +182,6 @@ func (r *RootCmd) openVSCode() *serpent.Command {
directory = inv.Args[1]
}

if containerName != "" {
containers, err := client.WorkspaceAgentListContainers(ctx, workspaceAgent.ID, map[string]string{"devcontainer.local_folder": ""})
if err != nil {
return xerrors.Errorf("list workspace agent containers: %w", err)
}

var foundContainer bool

for _, container := range containers.Containers {
if container.FriendlyName != containerName {
continue
}

foundContainer = true

if directory == "" {
localFolder, ok := container.Labels["devcontainer.local_folder"]
if !ok {
return xerrors.New("container missing `devcontainer.local_folder` label")
}

directory, ok = container.Volumes[localFolder]
if !ok {
return xerrors.New("container missing volume for `devcontainer.local_folder`")
}
}

break
}

if !foundContainer {
return xerrors.New("no container found")
}
}

directory, err = resolveAgentAbsPath(workspaceAgent.ExpandedDirectory, directory, workspaceAgent.OperatingSystem, insideThisWorkspace)
if err != nil {
return xerrors.Errorf("resolve agent path: %w", err)
Expand All@@ -174,14 +207,16 @@ func (r *RootCmd) openVSCode() *serpent.Command {
u *url.URL
qp url.Values
)
ifcontainerName !="" {
ifdevcontainer.ID !=uuid.Nil {
u, qp = buildVSCodeWorkspaceDevContainerLink(
token,
client.URL.String(),
workspace,
workspaceAgent,
containerName,
parentWorkspaceAgent,
devcontainer.Container.FriendlyName,
directory,
devcontainer.WorkspaceFolder,
devcontainer.ConfigPath,
)
} else {
u, qp = buildVSCodeWorkspaceLink(
Expand DownExpand Up@@ -247,13 +282,6 @@ func (r *RootCmd) openVSCode() *serpent.Command {
),
Value: serpent.BoolOf(&generateToken),
},
{
Flag: "container",
FlagShorthand: "c",
Description: "Container name to connect to in the workspace.",
Value: serpent.StringOf(&containerName),
Hidden: true, // Hidden until this features is at least in beta.
},
{
Flag: "test.open-error",
Description: "Don't run the open command.",
Expand DownExpand Up@@ -288,7 +316,7 @@ func (r *RootCmd) openApp() *serpent.Command {
}

workspaceName := inv.Args[0]
ws, agt, err := getWorkspaceAndAgent(ctx, inv, client, false, workspaceName)
ws, agt,_,err := getWorkspaceAndAgent(ctx, inv, client, false, workspaceName)
if err != nil {
var sdkErr *codersdk.Error
if errors.As(err, &sdkErr) && sdkErr.StatusCode() == http.StatusNotFound {
Expand DownExpand Up@@ -430,8 +458,14 @@ func buildVSCodeWorkspaceDevContainerLink(
workspaceAgent codersdk.WorkspaceAgent,
containerName string,
containerFolder string,
localWorkspaceFolder string,
localConfigFile string,
) (*url.URL, url.Values) {
containerFolder = filepath.ToSlash(containerFolder)
localWorkspaceFolder = filepath.ToSlash(localWorkspaceFolder)
if localConfigFile != "" {
localConfigFile = filepath.ToSlash(localConfigFile)
}

qp := url.Values{}
qp.Add("url", clientURL)
Expand All@@ -440,6 +474,8 @@ func buildVSCodeWorkspaceDevContainerLink(
qp.Add("agent", workspaceAgent.Name)
qp.Add("devContainerName", containerName)
qp.Add("devContainerFolder", containerFolder)
qp.Add("localWorkspaceFolder", localWorkspaceFolder)
qp.Add("localConfigFile", localConfigFile)

if token != "" {
qp.Add("token", token)
Expand DownExpand Up@@ -469,7 +505,7 @@ func waitForAgentCond(ctx context.Context, client *codersdk.Client, workspace co
}

for workspace = range wc {
workspaceAgent, err = getWorkspaceAgent(workspace, workspaceAgent.Name)
workspaceAgent,_,err = getWorkspaceAgent(workspace, workspaceAgent.Name)
if err != nil {
return workspace, workspaceAgent, xerrors.Errorf("get workspace agent: %w", err)
}
Expand Down
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp