Skip to content

Commit 295f2ab

Browse files
committed
Add basic vscode command
1 parent da36812 commit 295f2ab

File tree

2 files changed

+134
-0
lines changed

2 files changed

+134
-0
lines changed

cli/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ func Root() *cobra.Command {
7373
workspaceTunnel(),
7474
gitssh(),
7575
publickey(),
76+
vscode(),
7677
workspaceAgent(),
7778
)
7879

cli/vscode.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package cli
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os/exec"
7+
"strings"
8+
9+
"github.com/coder/coder/cli/cliui"
10+
"github.com/coder/coder/coderd/database"
11+
"github.com/coder/coder/codersdk"
12+
"github.com/google/uuid"
13+
"github.com/spf13/cobra"
14+
"golang.org/x/xerrors"
15+
)
16+
17+
func vscode() *cobra.Command {
18+
return &cobra.Command{
19+
Use: "vscode <workspace>",
20+
Args: cobra.MinimumNArgs(1),
21+
RunE: func(cmd *cobra.Command, args []string) error {
22+
client, err := createClient(cmd)
23+
if err != nil {
24+
return err
25+
}
26+
27+
workspaceParts := strings.Split(args[0], ".")
28+
workspace, err := client.WorkspaceByName(cmd.Context(), codersdk.Me, workspaceParts[0])
29+
if err != nil {
30+
return err
31+
}
32+
33+
if workspace.LatestBuild.Transition != database.WorkspaceTransitionStart {
34+
return xerrors.New("workspace must be in start transition to ssh")
35+
}
36+
37+
if workspace.LatestBuild.Job.CompletedAt == nil {
38+
err = cliui.WorkspaceBuild(cmd.Context(), cmd.ErrOrStderr(), client, workspace.LatestBuild.ID, workspace.CreatedAt)
39+
if err != nil {
40+
return err
41+
}
42+
}
43+
44+
if workspace.LatestBuild.Transition == database.WorkspaceTransitionDelete {
45+
return xerrors.New("workspace is deleting...")
46+
}
47+
48+
resources, err := client.WorkspaceResourcesByBuild(cmd.Context(), workspace.LatestBuild.ID)
49+
if err != nil {
50+
return err
51+
}
52+
53+
agents := make([]codersdk.WorkspaceAgent, 0)
54+
for _, resource := range resources {
55+
agents = append(agents, resource.Agents...)
56+
}
57+
if len(agents) == 0 {
58+
return xerrors.New("workspace has no agents")
59+
}
60+
var agent codersdk.WorkspaceAgent
61+
if len(workspaceParts) >= 2 {
62+
for _, otherAgent := range agents {
63+
if otherAgent.Name != workspaceParts[1] {
64+
continue
65+
}
66+
agent = otherAgent
67+
break
68+
}
69+
if agent.ID == uuid.Nil {
70+
return xerrors.Errorf("agent not found by name %q", workspaceParts[1])
71+
}
72+
}
73+
if agent.ID == uuid.Nil {
74+
if len(agents) > 1 {
75+
return xerrors.New("you must specify the name of an agent")
76+
}
77+
agent = agents[0]
78+
}
79+
// OpenSSH passes stderr directly to the calling TTY.
80+
// This is required in "stdio" mode so a connecting indicator can be displayed.
81+
err = cliui.Agent(cmd.Context(), cmd.ErrOrStderr(), cliui.AgentOptions{
82+
WorkspaceName: workspace.Name,
83+
Fetch: func(ctx context.Context) (codersdk.WorkspaceAgent, error) {
84+
return client.WorkspaceAgent(ctx, agent.ID)
85+
},
86+
})
87+
if err != nil {
88+
return xerrors.Errorf("await agent: %w", err)
89+
}
90+
91+
vscodePath, err := exec.LookPath("code")
92+
if err != nil {
93+
return xerrors.New(`The "code" binary must exist on your path!`)
94+
}
95+
output, err := exec.CommandContext(cmd.Context(), vscodePath, "--list-extensions").Output()
96+
if err != nil {
97+
return xerrors.Errorf("list extensions: %w", err)
98+
}
99+
extensions := strings.Split(string(output), "\n")
100+
hasRemote := false
101+
for _, extension := range extensions {
102+
if extension != "ms-vscode-remote.remote-ssh" {
103+
continue
104+
}
105+
hasRemote = true
106+
break
107+
}
108+
if !hasRemote {
109+
_, err := cliui.Prompt(cmd, cliui.PromptOptions{
110+
Text: "Would you like to install the VS Code Remote SSH extension?",
111+
IsConfirm: true,
112+
})
113+
if err != nil {
114+
return err
115+
}
116+
output, err := exec.CommandContext(cmd.Context(), vscodePath, "--install-extension", "ms-vscode-remote.remote-ssh").CombinedOutput()
117+
if err != nil {
118+
return xerrors.Errorf("install remote extension: %w: %s", err, output)
119+
}
120+
}
121+
err = configSSH().RunE(cmd, []string{})
122+
if err != nil {
123+
return xerrors.Errorf("config ssh: %w", err)
124+
}
125+
output, err = exec.CommandContext(cmd.Context(), vscodePath,
126+
"--remote", fmt.Sprintf("ssh-remote+coder.%s", args[0])).CombinedOutput()
127+
if err != nil {
128+
return xerrors.Errorf("launch vs code remote: %w: %s", err, output)
129+
}
130+
return nil
131+
},
132+
}
133+
}

0 commit comments

Comments
 (0)