Files
coder/cli/vscodeipc.go
Kyle Carberry e61234f260 feat: Add vscodeipc subcommand for VS Code Extension (#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>
2022-12-18 17:50:06 -06:00

89 lines
2.3 KiB
Go

package cli
import (
"fmt"
"net"
"net/http"
"net/url"
"github.com/google/uuid"
"github.com/spf13/cobra"
"golang.org/x/xerrors"
"github.com/coder/coder/cli/cliflag"
"github.com/coder/coder/cli/vscodeipc"
"github.com/coder/coder/codersdk"
)
// vscodeipcCmd spawns a local HTTP server on the provided port that listens to messages.
// It's made for use by the Coder VS Code extension. See: https://github.com/coder/vscode-coder
func vscodeipcCmd() *cobra.Command {
var (
rawURL string
token string
port uint16
)
cmd := &cobra.Command{
Use: "vscodeipc <workspace-agent>",
Args: cobra.ExactArgs(1),
SilenceUsage: true,
Hidden: true,
RunE: func(cmd *cobra.Command, args []string) error {
if rawURL == "" {
return xerrors.New("CODER_URL must be set!")
}
// token is validated in a header on each request to prevent
// unauthenticated clients from connecting.
if token == "" {
return xerrors.New("CODER_TOKEN must be set!")
}
listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
if err != nil {
return xerrors.Errorf("listen: %w", err)
}
defer listener.Close()
addr, ok := listener.Addr().(*net.TCPAddr)
if !ok {
return xerrors.Errorf("listener.Addr() is not a *net.TCPAddr: %T", listener.Addr())
}
url, err := url.Parse(rawURL)
if err != nil {
return err
}
agentID, err := uuid.Parse(args[0])
if err != nil {
return err
}
client := codersdk.New(url)
client.SetSessionToken(token)
handler, closer, err := vscodeipc.New(cmd.Context(), client, agentID, nil)
if err != nil {
return err
}
defer closer.Close()
// nolint:gosec
server := http.Server{
Handler: handler,
}
defer server.Close()
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s\n", addr.String())
errChan := make(chan error, 1)
go func() {
err := server.Serve(listener)
errChan <- err
}()
select {
case <-cmd.Context().Done():
return cmd.Context().Err()
case err := <-errChan:
return err
}
},
}
cliflag.StringVarP(cmd.Flags(), &rawURL, "url", "u", "CODER_URL", "", "The URL of the Coder instance!")
cliflag.StringVarP(cmd.Flags(), &token, "token", "t", "CODER_TOKEN", "", "The session token of the user!")
cmd.Flags().Uint16VarP(&port, "port", "p", 0, "The port to listen on!")
return cmd
}