Skip to content

Commit e61234f

Browse files
kylecarbsmafredri
andauthored
feat: Add vscodeipc subcommand for VS Code Extension (coder#5326)
* Add extio * feat: Add `vscodeipc` subcommand for VS Code Extension This enables the VS Code extension to communicate with a Coder client. The extension will download the slim binary from `/bin/*` for the respective client architecture and OS, then execute `coder vscodeipc` for the connecting workspace. * Add authentication header, improve comments, and add tests for the CLI * Update cli/vscodeipc_test.go Co-authored-by: Mathias Fredriksson <mafredri@gmail.com> * Update cli/vscodeipc_test.go Co-authored-by: Mathias Fredriksson <mafredri@gmail.com> * Update cli/vscodeipc/vscodeipc_test.go Co-authored-by: Mathias Fredriksson <mafredri@gmail.com> * Fix requested changes * Fix IPC tests * Fix shell execution * Fix nix flake * Silence usage Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
1 parent d1f8fec commit e61234f

File tree

13 files changed

+712
-33
lines changed

13 files changed

+712
-33
lines changed

cli/root.go

+1
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ func Core() []*cobra.Command {
9898
users(),
9999
versionCmd(),
100100
workspaceAgent(),
101+
vscodeipcCmd(),
101102
}
102103
}
103104

cli/speedtest.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func speedtest() *cobra.Command {
7171
return ctx.Err()
7272
case <-ticker.C:
7373
}
74-
dur, err := conn.Ping(ctx)
74+
dur, p2p, err := conn.Ping(ctx)
7575
if err != nil {
7676
continue
7777
}
@@ -80,7 +80,7 @@ func speedtest() *cobra.Command {
8080
continue
8181
}
8282
peer := status.Peer[status.Peers()[0]]
83-
if peer.CurAddr == "" && direct {
83+
if !p2p && direct {
8484
cmd.Printf("Waiting for a direct connection... (%dms via %s)\n", dur.Milliseconds(), peer.Relay)
8585
continue
8686
}

cli/ssh_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ func setupWorkspaceForAgent(t *testing.T, mutate func([]*proto.Agent) []*proto.A
6565
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
6666
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
6767
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
68+
workspace, err := client.Workspace(context.Background(), workspace.ID)
69+
require.NoError(t, err)
6870

6971
return client, workspace, agentToken
7072
}

cli/vscodeipc.go

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
"net"
6+
"net/http"
7+
"net/url"
8+
9+
"github.com/google/uuid"
10+
"github.com/spf13/cobra"
11+
"golang.org/x/xerrors"
12+
13+
"github.com/coder/coder/cli/cliflag"
14+
"github.com/coder/coder/cli/vscodeipc"
15+
"github.com/coder/coder/codersdk"
16+
)
17+
18+
// vscodeipcCmd spawns a local HTTP server on the provided port that listens to messages.
19+
// It's made for use by the Coder VS Code extension. See: https://github.com/coder/vscode-coder
20+
func vscodeipcCmd() *cobra.Command {
21+
var (
22+
rawURL string
23+
token string
24+
port uint16
25+
)
26+
cmd := &cobra.Command{
27+
Use: "vscodeipc <workspace-agent>",
28+
Args: cobra.ExactArgs(1),
29+
SilenceUsage: true,
30+
Hidden: true,
31+
RunE: func(cmd *cobra.Command, args []string) error {
32+
if rawURL == "" {
33+
return xerrors.New("CODER_URL must be set!")
34+
}
35+
// token is validated in a header on each request to prevent
36+
// unauthenticated clients from connecting.
37+
if token == "" {
38+
return xerrors.New("CODER_TOKEN must be set!")
39+
}
40+
listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
41+
if err != nil {
42+
return xerrors.Errorf("listen: %w", err)
43+
}
44+
defer listener.Close()
45+
addr, ok := listener.Addr().(*net.TCPAddr)
46+
if !ok {
47+
return xerrors.Errorf("listener.Addr() is not a *net.TCPAddr: %T", listener.Addr())
48+
}
49+
url, err := url.Parse(rawURL)
50+
if err != nil {
51+
return err
52+
}
53+
agentID, err := uuid.Parse(args[0])
54+
if err != nil {
55+
return err
56+
}
57+
client := codersdk.New(url)
58+
client.SetSessionToken(token)
59+
60+
handler, closer, err := vscodeipc.New(cmd.Context(), client, agentID, nil)
61+
if err != nil {
62+
return err
63+
}
64+
defer closer.Close()
65+
// nolint:gosec
66+
server := http.Server{
67+
Handler: handler,
68+
}
69+
defer server.Close()
70+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s\n", addr.String())
71+
errChan := make(chan error, 1)
72+
go func() {
73+
err := server.Serve(listener)
74+
errChan <- err
75+
}()
76+
select {
77+
case <-cmd.Context().Done():
78+
return cmd.Context().Err()
79+
case err := <-errChan:
80+
return err
81+
}
82+
},
83+
}
84+
cliflag.StringVarP(cmd.Flags(), &rawURL, "url", "u", "CODER_URL", "", "The URL of the Coder instance!")
85+
cliflag.StringVarP(cmd.Flags(), &token, "token", "t", "CODER_TOKEN", "", "The session token of the user!")
86+
cmd.Flags().Uint16VarP(&port, "port", "p", 0, "The port to listen on!")
87+
return cmd
88+
}

0 commit comments

Comments
 (0)