diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml deleted file mode 100644 index d246670f..00000000 --- a/.github/workflows/build.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: build - -on: [push] - -jobs: - build: - runs-on: macos-latest - steps: - - name: Checkout - uses: actions/checkout@v1 - - name: Build - run: ./ci/build.sh - - name: Upload - uses: actions/upload-artifact@v2 - with: - name: coder-cli - path: ./ci/bin/coder-cli-* diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 5fd924f8..00000000 --- a/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -.idea -ci/bin -cmd/coder/coder diff --git a/README.md b/README.md index 427ad846..4fbeb053 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,13 @@ -# coder-cli +# Coder v1 CLI -`coder` is a command line utility for Coder Enterprise. +[![GitHub Release](https://img.shields.io/github/v/release/cdr/coder-cli?color=6b9ded&include_prerelease=false)](https://github.com/cdr/coder-cli/releases) +[![Documentation](https://godoc.org/cdr.dev/coder-cli?status.svg)](https://pkg.go.dev/cdr.dev/coder-cli/coder-sdk) -To view usage documentation, head over to [https://enterprise.coder.com](https://enterprise.coder.com/docs/getting-started). +This is the command line utility for [Coder v1](https://coder.com/docs/coder). If you are using +[Coder v2 / Coder OSS](https://coder.com/docs/coder-oss/latest), use +[these instructions](https://coder.com/docs/coder-oss/latest/install) to install the CLI. -To report bugs and request features, please [open an issue](https://github.com/cdr/coder-cli/issues/new). +The Coder v1 CLI is now closed-source. You may download binary releases from this repo. -## Install Release - -Download the latest [release](https://github.com/cdr/coder-cli/releases): - -1. Click a release and download the tar file for your operating system (ex: coder-cli-linux-amd64.tar.gz) -2. Extract the `coder` binary from the tar file, ex: - -```bash -cd ~/go/bin -tar -xvf ~/Downloads/coder-cli-linux-amd64.tar.gz -``` +[Coder v2](https://coder.com/docs/coder-oss/latest) is open-source and the recommended +version for new Coder users. \ No newline at end of file diff --git a/ci/build.sh b/ci/build.sh deleted file mode 100755 index 1930034b..00000000 --- a/ci/build.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -# Make pushd and popd silent -pushd () { builtin pushd "$@" > /dev/null ; } -popd () { builtin popd > /dev/null ; } - -set -euo pipefail -cd "$(dirname "$0")" - -export GOARCH=amd64 -tag=$(git describe --tags) - -mkdir -p bin - -build(){ - tmpdir=$(mktemp -d) - go build -ldflags "-s -w -X main.version=${tag}" -o "$tmpdir/coder" ../cmd/coder - - pushd "$tmpdir" - tarname="coder-cli-$GOOS-$GOARCH.tar.gz" - tar -czf "$tarname" coder - popd - - cp "$tmpdir/$tarname" bin - rm -rf "$tmpdir" -} - -# Darwin builds do not work from Linux, so only try to build them from Darwin. -# See: https://github.com/cdr/coder-cli/issues/20 -if [[ "$(uname)" == "Darwin" ]]; then - GOOS=linux build - CGO_ENABLED=1 GOOS=darwin build - exit 0 -fi - -echo "Warning: Darwin builds don't work on Linux." -echo "Please use an OSX machine to build Darwin tars." -GOOS=linux build diff --git a/cmd/coder/auth.go b/cmd/coder/auth.go deleted file mode 100644 index cf7a16b1..00000000 --- a/cmd/coder/auth.go +++ /dev/null @@ -1,32 +0,0 @@ -package main - -import ( - "net/url" - - "go.coder.com/flog" - - "cdr.dev/coder-cli/internal/config" - "cdr.dev/coder-cli/internal/entclient" -) - -func requireAuth() *entclient.Client { - sessionToken, err := config.Session.Read() - if err != nil { - flog.Fatal("read session: %v (did you run coder login?)", err) - } - - rawURL, err := config.URL.Read() - if err != nil { - flog.Fatal("read url: %v (did you run coder login?)", err) - } - - u, err := url.Parse(rawURL) - if err != nil { - flog.Fatal("url misformatted: %v (try runing coder login)", err) - } - - return &entclient.Client{ - BaseURL: u, - Token: sessionToken, - } -} diff --git a/cmd/coder/ceapi.go b/cmd/coder/ceapi.go deleted file mode 100644 index fd59046c..00000000 --- a/cmd/coder/ceapi.go +++ /dev/null @@ -1,72 +0,0 @@ -package main - -import ( - "go.coder.com/flog" - - "cdr.dev/coder-cli/internal/entclient" -) - -// Helpers for working with the Coder Enterprise API. - -// userOrgs gets a list of orgs the user is apart of. -func userOrgs(user *entclient.User, orgs []entclient.Org) []entclient.Org { - var uo []entclient.Org -outer: - for _, org := range orgs { - for _, member := range org.Members { - if member.ID != user.ID { - continue - } - uo = append(uo, org) - continue outer - } - } - return uo -} - -// getEnvs returns all environments for the user. -func getEnvs(client *entclient.Client) []entclient.Environment { - me, err := client.Me() - if err != nil { - flog.Fatal("get self: %+v", err) - } - - orgs, err := client.Orgs() - if err != nil { - flog.Fatal("get orgs: %+v", err) - } - - orgs = userOrgs(me, orgs) - - var allEnvs []entclient.Environment - - for _, org := range orgs { - envs, err := client.Envs(me, org) - if err != nil { - flog.Fatal("get envs for %v: %+v", org.Name, err) - } - for _, env := range envs { - allEnvs = append(allEnvs, env) - } - } - - return allEnvs -} - -// findEnv returns a single environment by name (if it exists.) -func findEnv(client *entclient.Client, name string) entclient.Environment { - envs := getEnvs(client) - - var found []string - - for _, env := range envs { - found = append(found, env.Name) - if env.Name == name { - return env - } - } - - flog.Info("found %q", found) - flog.Fatal("environment %q not found", name) - panic("unreachable") -} diff --git a/cmd/coder/configssh.go b/cmd/coder/configssh.go deleted file mode 100644 index ff751322..00000000 --- a/cmd/coder/configssh.go +++ /dev/null @@ -1,205 +0,0 @@ -package main - -import ( - "context" - "fmt" - "io/ioutil" - "net" - "net/url" - "os" - "path/filepath" - "strings" - "time" - - "github.com/spf13/pflag" - "go.coder.com/cli" - "go.coder.com/flog" - - "cdr.dev/coder-cli/internal/config" - "cdr.dev/coder-cli/internal/entclient" -) - -var ( - privateKeyFilepath = filepath.Join(os.Getenv("HOME"), ".ssh", "coder_enterprise") -) - -type configSSHCmd struct { - filepath string - remove bool - - startToken, startMessage, endToken string -} - -func (cmd *configSSHCmd) Spec() cli.CommandSpec { - return cli.CommandSpec{ - Name: "config-ssh", - Usage: "", - Desc: "add your Coder Enterprise environments to ~/.ssh/config", - } -} - -func (cmd *configSSHCmd) RegisterFlags(fl *pflag.FlagSet) { - fl.BoolVar(&cmd.remove, "remove", false, "remove the auto-generated Coder Enterprise ssh config") - home := os.Getenv("HOME") - defaultPath := filepath.Join(home, ".ssh", "config") - fl.StringVar(&cmd.filepath, "config-path", defaultPath, "overide the default path of your ssh config file") - - cmd.startToken = "# ------------START-CODER-ENTERPRISE-----------" - cmd.startMessage = `# The following has been auto-generated by "coder config-ssh" -# to make accessing your Coder Enterprise environments easier. -# -# To remove this blob, run: -# -# coder config-ssh --remove -# -# You should not hand-edit this section, unless you are deleting it.` - cmd.endToken = "# ------------END-CODER-ENTERPRISE------------" -} - -func (cmd *configSSHCmd) Run(fl *pflag.FlagSet) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - currentConfig, err := readStr(cmd.filepath) - if os.IsNotExist(err) { - // SSH configs are not always already there. - currentConfig = "" - } else if err != nil { - flog.Fatal("failed to read ssh config file %q: %v", cmd.filepath, err) - } - - startIndex := strings.Index(currentConfig, cmd.startToken) - endIndex := strings.Index(currentConfig, cmd.endToken) - - if cmd.remove { - if startIndex == -1 || endIndex == -1 { - flog.Fatal("the Coder Enterprise ssh configuration section could not be safely deleted or does not exist") - } - currentConfig = currentConfig[:startIndex-1] + currentConfig[endIndex+len(cmd.endToken)+1:] - - err = writeStr(cmd.filepath, currentConfig) - if err != nil { - flog.Fatal("failed to write to ssh config file %q: %v", cmd.filepath, err) - } - - return - } - - entClient := requireAuth() - - sshAvailable := cmd.ensureSSHAvailable(ctx) - if !sshAvailable { - flog.Fatal("SSH is disabled or not available for your Coder Enterprise deployment.") - } - - me, err := entClient.Me() - if err != nil { - flog.Fatal("failed to fetch username: %v", err) - } - - envs := getEnvs(entClient) - if len(envs) < 1 { - flog.Fatal("no environments found") - } - newConfig, err := cmd.makeNewConfigs(me.Username, envs) - if err != nil { - flog.Fatal("failed to make new ssh configurations: %v", err) - } - - // if we find the old config, remove those chars from the string - if startIndex != -1 && endIndex != -1 { - currentConfig = currentConfig[:startIndex-1] + currentConfig[endIndex+len(cmd.endToken)+1:] - } - - err = writeStr(cmd.filepath, currentConfig+newConfig) - if err != nil { - flog.Fatal("failed to write new configurations to ssh config file %q: %v", cmd.filepath, err) - } - err = writeSSHKey(ctx, entClient) - if err != nil { - flog.Fatal("failed to fetch and write ssh key: %v", err) - } - - fmt.Printf("An auto-generated ssh config was written to %q\n", cmd.filepath) - fmt.Printf("Your private ssh key was written to %q\n", privateKeyFilepath) - fmt.Println("You should now be able to ssh into your environment") - fmt.Printf("For example, try running\n\n\t$ ssh coder.%s\n\n", envs[0].Name) -} - -func writeSSHKey(ctx context.Context, client *entclient.Client) error { - key, err := client.SSHKey() - if err != nil { - return err - } - err = ioutil.WriteFile(privateKeyFilepath, []byte(key.PrivateKey), 0400) - if err != nil { - return err - } - return nil -} - -func (cmd *configSSHCmd) makeNewConfigs(userName string, envs []entclient.Environment) (string, error) { - hostname, err := configuredHostname() - if err != nil { - return "", nil - } - - newConfig := fmt.Sprintf("\n%s\n%s\n\n", cmd.startToken, cmd.startMessage) - for _, env := range envs { - newConfig += cmd.makeConfig(hostname, userName, env.Name) - } - newConfig += fmt.Sprintf("\n%s\n", cmd.endToken) - - return newConfig, nil -} - -func (cmd *configSSHCmd) makeConfig(host, userName, envName string) string { - return fmt.Sprintf( - `Host coder.%s - HostName %s - User %s-%s - StrictHostKeyChecking no - ConnectTimeout=0 - IdentityFile=%s - ServerAliveInterval 60 - ServerAliveCountMax 3 -`, envName, host, userName, envName, privateKeyFilepath) -} - -func (cmd *configSSHCmd) ensureSSHAvailable(ctx context.Context) bool { - ctx, cancel := context.WithTimeout(ctx, 3*time.Second) - defer cancel() - - host, err := configuredHostname() - if err != nil { - return false - } - - var dialer net.Dialer - _, err = dialer.DialContext(ctx, "tcp", net.JoinHostPort(host, "22")) - return err == nil -} - -func configuredHostname() (string, error) { - u, err := config.URL.Read() - if err != nil { - return "", err - } - url, err := url.Parse(u) - if err != nil { - return "", err - } - return url.Hostname(), nil -} - -func writeStr(filename, data string) error { - return ioutil.WriteFile(filename, []byte(data), 0777) -} - -func readStr(filename string) (string, error) { - contents, err := ioutil.ReadFile(filename) - if err != nil { - return "", err - } - return string(contents), nil -} diff --git a/cmd/coder/envs.go b/cmd/coder/envs.go deleted file mode 100644 index 5abd5f28..00000000 --- a/cmd/coder/envs.go +++ /dev/null @@ -1,29 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/spf13/pflag" - - "go.coder.com/cli" -) - -type envsCmd struct { -} - -func (cmd envsCmd) Spec() cli.CommandSpec { - return cli.CommandSpec{ - Name: "envs", - Desc: "get a list of active environment", - } -} - -func (cmd envsCmd) Run(fl *pflag.FlagSet) { - entClient := requireAuth() - - envs := getEnvs(entClient) - - for _, env := range envs { - fmt.Println(env.Name) - } -} diff --git a/cmd/coder/exit.go b/cmd/coder/exit.go deleted file mode 100644 index d4645e62..00000000 --- a/cmd/coder/exit.go +++ /dev/null @@ -1,12 +0,0 @@ -package main - -import ( - "os" - - "github.com/spf13/pflag" -) - -func exitUsage(fl *pflag.FlagSet) { - fl.Usage() - os.Exit(1) -} diff --git a/cmd/coder/login.go b/cmd/coder/login.go deleted file mode 100644 index fef1a38b..00000000 --- a/cmd/coder/login.go +++ /dev/null @@ -1,83 +0,0 @@ -package main - -import ( - "net" - "net/http" - "net/url" - "strings" - "sync" - - "github.com/pkg/browser" - "github.com/spf13/pflag" - - "go.coder.com/cli" - "go.coder.com/flog" - - "cdr.dev/coder-cli/internal/config" - "cdr.dev/coder-cli/internal/loginsrv" -) - -type loginCmd struct { -} - -func (cmd loginCmd) Spec() cli.CommandSpec { - return cli.CommandSpec{ - Name: "login", - Usage: "[Coder Enterprise URL eg. http://my.coder.domain/ ]", - Desc: "authenticate this client for future operations", - } -} -func (cmd loginCmd) Run(fl *pflag.FlagSet) { - rawURL := fl.Arg(0) - if rawURL == "" || !strings.HasPrefix(rawURL, "http") { - exitUsage(fl) - } - - u, err := url.Parse(rawURL) - if err != nil { - flog.Fatal("parse url: %v", err) - } - - listener, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - flog.Fatal("create login server: %+v", err) - } - defer listener.Close() - - srv := &loginsrv.Server{ - TokenCond: sync.NewCond(&sync.Mutex{}), - } - go func() { - _ = http.Serve( - listener, srv, - ) - }() - - err = config.URL.Write( - (&url.URL{Scheme: u.Scheme, Host: u.Host}).String(), - ) - if err != nil { - flog.Fatal("write url: %v", err) - } - - authURL := url.URL{ - Scheme: u.Scheme, - Host: u.Host, - Path: "/internal-auth/", - RawQuery: "local_service=http://" + listener.Addr().String(), - } - - err = browser.OpenURL(authURL.String()) - if err != nil { - // Tell the user to visit the URL instead. - flog.Info("visit %s to login", authURL.String()) - } - srv.TokenCond.L.Lock() - srv.TokenCond.Wait() - err = config.Session.Write(srv.Token) - srv.TokenCond.L.Unlock() - if err != nil { - flog.Fatal("set session: %v", err) - } - flog.Success("logged in") -} diff --git a/cmd/coder/logout.go b/cmd/coder/logout.go deleted file mode 100644 index c0dba553..00000000 --- a/cmd/coder/logout.go +++ /dev/null @@ -1,34 +0,0 @@ -package main - -import ( - "os" - - "github.com/spf13/pflag" - - "go.coder.com/cli" - "go.coder.com/flog" - - "cdr.dev/coder-cli/internal/config" -) - -type logoutCmd struct { -} - -func (cmd logoutCmd) Spec() cli.CommandSpec { - return cli.CommandSpec{ - Name: "logout", - Desc: "remote local authentication credentials (if any)", - } -} - -func (cmd logoutCmd) Run(_ *pflag.FlagSet) { - err := config.Session.Delete() - if err != nil { - if os.IsNotExist(err) { - flog.Info("no active session") - return - } - flog.Fatal("delete session: %v", err) - } - flog.Success("logged out") -} diff --git a/cmd/coder/main.go b/cmd/coder/main.go deleted file mode 100644 index 3cf2ec5e..00000000 --- a/cmd/coder/main.go +++ /dev/null @@ -1,52 +0,0 @@ -package main - -import ( - "log" - "net/http" - _ "net/http/pprof" - "os" - - "github.com/spf13/pflag" - - "go.coder.com/cli" -) - -var ( - version string = "No version built" -) - -type rootCmd struct{} - -func (r *rootCmd) Run(fl *pflag.FlagSet) { - fl.Usage() -} - -func (r *rootCmd) Spec() cli.CommandSpec { - return cli.CommandSpec{ - Name: "coder", - Usage: "[subcommand] [flags]", - Desc: "coder provides a CLI for working with an existing Coder Enterprise installation.", - } -} - -func (r *rootCmd) Subcommands() []cli.Command { - return []cli.Command{ - &envsCmd{}, - &loginCmd{}, - &logoutCmd{}, - &shellCmd{}, - &syncCmd{}, - &urlsCmd{}, - &versionCmd{}, - &configSSHCmd{}, - } -} - -func main() { - if os.Getenv("PPROF") != "" { - go func() { - log.Println(http.ListenAndServe("localhost:6060", nil)) - }() - } - cli.RunRoot(&rootCmd{}) -} diff --git a/cmd/coder/shell.go b/cmd/coder/shell.go deleted file mode 100644 index bf9c43be..00000000 --- a/cmd/coder/shell.go +++ /dev/null @@ -1,195 +0,0 @@ -package main - -import ( - "context" - "io" - "os" - "os/signal" - "time" - - "github.com/spf13/pflag" - "golang.org/x/crypto/ssh/terminal" - "golang.org/x/sys/unix" - "golang.org/x/time/rate" - "golang.org/x/xerrors" - "nhooyr.io/websocket" - - "go.coder.com/cli" - "go.coder.com/flog" - - "cdr.dev/coder-cli/internal/activity" - "cdr.dev/wsep" -) - -type shellCmd struct{} - -func (cmd *shellCmd) Spec() cli.CommandSpec { - return cli.CommandSpec{ - Name: "sh", - Usage: " []", - Desc: "executes a remote command on the environment\nIf no command is specified, the default shell is opened.", - RawArgs: true, - } -} - -func enableTerminal(fd int) (restore func(), err error) { - state, err := terminal.MakeRaw(fd) - if err != nil { - return restore, xerrors.Errorf("make raw term: %w", err) - } - return func() { - err := terminal.Restore(fd, state) - if err != nil { - flog.Error("restore term state: %v", err) - } - }, nil -} - -func sendResizeEvents(ctx context.Context, termfd int, process wsep.Process) { - sigs := make(chan os.Signal, 16) - signal.Notify(sigs, unix.SIGWINCH) - - // Limit the frequency of resizes to prevent a stuttering effect. - resizeLimiter := rate.NewLimiter(rate.Every(time.Millisecond*100), 1) - - for ctx.Err() == nil { - if ctx.Err() != nil { - return - } - width, height, err := terminal.GetSize(termfd) - if err != nil { - flog.Error("get term size: %v", err) - return - } - - err = process.Resize(ctx, uint16(height), uint16(width)) - if err != nil { - return - } - - // Do this last so the first resize is sent. - <-sigs - resizeLimiter.Wait(ctx) - } -} - -func (cmd *shellCmd) Run(fl *pflag.FlagSet) { - if len(fl.Args()) < 1 { - exitUsage(fl) - } - var ( - envName = fl.Arg(0) - command = fl.Arg(1) - ctx = context.Background() - ) - - var args []string - if command != "" { - args = fl.Args()[2:] - } - - // Bring user into shell if no command is specified. - if command == "" { - command = "sh" - args = []string{"-c", "exec $(getent passwd $(whoami) | awk -F: '{ print $7 }')"} - } - - err := runCommand(ctx, envName, command, args) - if exitErr, ok := err.(wsep.ExitError); ok { - os.Exit(exitErr.Code) - } - if err != nil { - flog.Fatal("run command: %v", err) - } -} - -func runCommand(ctx context.Context, envName string, command string, args []string) error { - var ( - entClient = requireAuth() - env = findEnv(entClient, envName) - ) - - termfd := int(os.Stdin.Fd()) - - tty := terminal.IsTerminal(termfd) - if tty { - restore, err := enableTerminal(termfd) - if err != nil { - return err - } - defer restore() - } - - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - conn, err := entClient.DialWsep(ctx, env) - if err != nil { - return err - } - go heartbeat(ctx, conn, 15*time.Second) - - execer := wsep.RemoteExecer(conn) - process, err := execer.Start(ctx, wsep.Command{ - Command: command, - Args: args, - TTY: tty, - Stdin: true, - Env: []string{"TERM=" + os.Getenv("TERM")}, - }) - if err != nil { - return err - } - - if tty { - go sendResizeEvents(ctx, termfd, process) - } - - go func() { - stdin := process.Stdin() - defer stdin.Close() - - ap := activity.NewPusher(entClient, env.ID, sshActivityName) - wr := ap.Writer(stdin) - _, err := io.Copy(wr, os.Stdin) - if err != nil { - cancel() - } - }() - go func() { - _, err := io.Copy(os.Stdout, process.Stdout()) - if err != nil { - cancel() - } - }() - go func() { - _, err := io.Copy(os.Stderr, process.Stderr()) - if err != nil { - cancel() - } - }() - err = process.Wait() - if err != nil && xerrors.Is(err, ctx.Err()) { - return xerrors.Errorf("network error, is %q online?", envName) - } - return err -} - -func heartbeat(ctx context.Context, c *websocket.Conn, interval time.Duration) { - ticker := time.NewTicker(interval) - defer ticker.Stop() - - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - err := c.Ping(ctx) - if err != nil { - flog.Error("failed to ping websocket: %v", err) - } - } - } -} - -const sshActivityName = "ssh" diff --git a/cmd/coder/sync.go b/cmd/coder/sync.go deleted file mode 100644 index 0e26af96..00000000 --- a/cmd/coder/sync.go +++ /dev/null @@ -1,81 +0,0 @@ -package main - -import ( - "os" - "path/filepath" - "strings" - - "github.com/spf13/pflag" - - "go.coder.com/cli" - "go.coder.com/flog" - - "cdr.dev/coder-cli/internal/sync" -) - -type syncCmd struct { - init bool -} - -func (cmd *syncCmd) Spec() cli.CommandSpec { - return cli.CommandSpec{ - Name: "sync", - Usage: "[local directory] [:]", - Desc: "establish a one way directory sync to a remote environment", - } -} - -func (cmd *syncCmd) RegisterFlags(fl *pflag.FlagSet) { - fl.BoolVarP(&cmd.init, "init", "i", false, "do initial transfer and exit") -} - -func (cmd *syncCmd) Run(fl *pflag.FlagSet) { - var ( - local = fl.Arg(0) - remote = fl.Arg(1) - ) - if local == "" || remote == "" { - exitUsage(fl) - } - - entClient := requireAuth() - - info, err := os.Stat(local) - if err != nil { - flog.Fatal("%v", err) - } - if !info.IsDir() { - flog.Fatal("%s must be a directory", local) - } - - remoteTokens := strings.SplitN(remote, ":", 2) - if len(remoteTokens) != 2 { - flog.Fatal("remote misformatted") - } - var ( - envName = remoteTokens[0] - remoteDir = remoteTokens[1] - ) - - env := findEnv(entClient, envName) - - absLocal, err := filepath.Abs(local) - if err != nil { - flog.Fatal("make abs path out of %v: %v", local, absLocal) - } - - s := sync.Sync{ - Init: cmd.init, - Env: env, - RemoteDir: remoteDir, - LocalDir: absLocal, - Client: entClient, - } - for err == nil || err == sync.ErrRestartSync { - err = s.Run() - } - - if err != nil { - flog.Fatal("%v", err) - } -} diff --git a/cmd/coder/urls.go b/cmd/coder/urls.go deleted file mode 100644 index 7626ae93..00000000 --- a/cmd/coder/urls.go +++ /dev/null @@ -1,220 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "net/http" - "os" - "strconv" - "strings" - "text/tabwriter" - - "github.com/spf13/pflag" - - "go.coder.com/cli" - "go.coder.com/flog" -) - -type urlsCmd struct{} - -// DevURL is the parsed json response record for a devURL from cemanager -type DevURL struct { - ID string `json:"id"` - URL string `json:"url"` - Port string `json:"port"` - Access string `json:"access"` -} - -var urlAccessLevel = map[string]string{ - //Remote API endpoint requires these in uppercase - "PRIVATE": "Only you can access", - "ORG": "All members of your organization can access", - "AUTHED": "Authenticated users can access", - "PUBLIC": "Anyone on the internet can access this link", -} - -func portIsValid(port string) bool { - p, err := strconv.ParseUint(port, 10, 16) - if p < 1 { - // port 0 means 'any free port', which we don't support - err = strconv.ErrRange - } - if err != nil { - fmt.Println("Invalid port") - } - return err == nil -} - -func accessLevelIsValid(level string) bool { - _, ok := urlAccessLevel[level] - if !ok { - fmt.Println("Invalid access level") - } - return ok -} - -type createSubCmd struct { - access string -} - -func (sub *createSubCmd) RegisterFlags(fl *pflag.FlagSet) { - fl.StringVarP(&sub.access, "access", "a", "private", "[private | org | authed | public] set devurl access") -} - -func (sub createSubCmd) Spec() cli.CommandSpec { - return cli.CommandSpec{ - Name: "create", - Usage: " [--access ]", - Desc: "create/update a devurl for external access", - } -} - -// Run creates or updates a devURL, specified by env ID and port -// (fl.Arg(0) and fl.Arg(1)), with access level (fl.Arg(2)) on -// the cemanager. -func (sub createSubCmd) Run(fl *pflag.FlagSet) { - envName := fl.Arg(0) - port := fl.Arg(1) - access := fl.Arg(2) - - if envName == "" { - exitUsage(fl) - } - - if !portIsValid(port) { - exitUsage(fl) - } - - access = strings.ToUpper(sub.access) - if !accessLevelIsValid(access) { - exitUsage(fl) - } - - entClient := requireAuth() - - env := findEnv(entClient, envName) - - _, found := devURLID(port, urlList(envName)) - if found { - fmt.Printf("Updating devurl for port %v\n", port) - } else { - fmt.Printf("Adding devurl for port %v\n", port) - } - - err := entClient.UpsertDevURL(env.ID, port, access) - if err != nil { - flog.Error("upsert devurl: %s", err.Error()) - } -} - -type delSubCmd struct{} - -func (sub delSubCmd) Spec() cli.CommandSpec { - return cli.CommandSpec{ - Name: "del", - Usage: " ", - Desc: "delete a devurl", - } -} - -// devURLID returns the ID of a devURL, given the env name and port. -// ("", false) is returned if no match is found. -func devURLID(port string, urls []DevURL) (string, bool) { - for _, url := range urls { - if url.Port == port { - return url.ID, true - } - } - return "", false -} - -// Run deletes a devURL, specified by env ID and port, from the cemanager. -func (sub delSubCmd) Run(fl *pflag.FlagSet) { - envName := fl.Arg(0) - port := fl.Arg(1) - - if envName == "" { - exitUsage(fl) - } - - if !portIsValid(port) { - exitUsage(fl) - } - - entClient := requireAuth() - - env := findEnv(entClient, envName) - - urlID, found := devURLID(port, urlList(envName)) - if found { - fmt.Printf("Deleting devurl for port %v\n", port) - } else { - flog.Fatal("No devurl found for port %v", port) - } - - err := entClient.DelDevURL(env.ID, urlID) - if err != nil { - flog.Error("delete devurl: %s", err.Error()) - } -} - -func (cmd urlsCmd) Spec() cli.CommandSpec { - return cli.CommandSpec{ - Name: "urls", - Usage: "", - Desc: "get all development urls for external access", - } -} - -// urlList returns the list of active devURLs from the cemanager. -func urlList(envName string) []DevURL { - entClient := requireAuth() - env := findEnv(entClient, envName) - - reqString := "%s/api/environments/%s/devurls?session_token=%s" - reqURL := fmt.Sprintf(reqString, entClient.BaseURL, env.ID, entClient.Token) - - resp, err := http.Get(reqURL) - if err != nil { - flog.Fatal("%v", err) - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - flog.Fatal("non-success status code: %d", resp.StatusCode) - } - - dec := json.NewDecoder(resp.Body) - - devURLs := make([]DevURL, 0) - err = dec.Decode(&devURLs) - if err != nil { - flog.Fatal("%v", err) - } - - if len(devURLs) == 0 { - fmt.Printf("no dev urls were found for environment: %s\n", envName) - } - - return devURLs -} - -// Run gets the list of active devURLs from the cemanager for the -// specified environment and outputs info to stdout. -func (cmd urlsCmd) Run(fl *pflag.FlagSet) { - envName := fl.Arg(0) - devURLs := urlList(envName) - - w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.TabIndent) - for _, devURL := range devURLs { - fmt.Fprintf(w, "%s\t%s\t%s\n", devURL.URL, devURL.Port, devURL.Access) - } - w.Flush() -} - -func (cmd *urlsCmd) Subcommands() []cli.Command { - return []cli.Command{ - &createSubCmd{}, - &delSubCmd{}, - } -} diff --git a/cmd/coder/version.go b/cmd/coder/version.go deleted file mode 100644 index 9568bf75..00000000 --- a/cmd/coder/version.go +++ /dev/null @@ -1,28 +0,0 @@ -package main - -import ( - "fmt" - "runtime" - - "github.com/spf13/pflag" - - "go.coder.com/cli" -) - -type versionCmd struct{} - -func (versionCmd) Spec() cli.CommandSpec { - return cli.CommandSpec{ - Name: "version", - Usage: "", - Desc: "Print the currently installed CLI version", - } -} - -func (versionCmd) Run(fl *pflag.FlagSet) { - fmt.Println( - version, - runtime.Version(), - runtime.GOOS+"/"+runtime.GOARCH, - ) -} diff --git a/go.mod b/go.mod deleted file mode 100644 index 3fed388a..00000000 --- a/go.mod +++ /dev/null @@ -1,23 +0,0 @@ -module cdr.dev/coder-cli - -go 1.14 - -require ( - cdr.dev/wsep v0.0.0-20200616212001-0613cfe9a4ac - github.com/fatih/color v1.9.0 // indirect - github.com/gorilla/websocket v1.4.1 - github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f - github.com/klauspost/compress v1.10.8 // indirect - github.com/mattn/go-colorable v0.1.6 // indirect - github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 - github.com/rjeczalik/notify v0.9.2 - github.com/spf13/pflag v1.0.5 - go.coder.com/cli v0.4.0 - go.coder.com/flog v0.0.0-20190906214207-47dd47ea0512 - golang.org/x/crypto v0.0.0-20200422194213-44a606286825 - golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a - golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 - golang.org/x/time v0.0.0-20191024005414-555d28b269f0 - golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 - nhooyr.io/websocket v1.8.6 -) diff --git a/go.sum b/go.sum deleted file mode 100644 index e687d95d..00000000 --- a/go.sum +++ /dev/null @@ -1,314 +0,0 @@ -cdr.dev/slog v1.3.0 h1:MYN1BChIaVEGxdS7I5cpdyMC0+WfJfK8BETAfzfLUGQ= -cdr.dev/slog v1.3.0/go.mod h1:C5OL99WyuOK8YHZdYY57dAPN1jK2WJlCdq2VP6xeQns= -cdr.dev/wsep v0.0.0-20200615020153-e2b1c576fc40 h1:f369880iSAZ3cXwvbdc9WIyy3FZ4yanusYZjaVHeis4= -cdr.dev/wsep v0.0.0-20200615020153-e2b1c576fc40/go.mod h1:2VKClUml3gfmLez0gBxTJIjSKszpQotc2ZqPdApfK/Y= -cdr.dev/wsep v0.0.0-20200616212001-0613cfe9a4ac h1:rl4O0qfxgNRWBUe5gQu4of2cdsclcpjGYmLQhSCHX7c= -cdr.dev/wsep v0.0.0-20200616212001-0613cfe9a4ac/go.mod h1:2VKClUml3gfmLez0gBxTJIjSKszpQotc2ZqPdApfK/Y= -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.49.0 h1:CH+lkubJzcPYB1Ggupcq0+k8Ni2ILdG2lYjDIgavDBQ= -cloud.google.com/go v0.49.0/go.mod h1:hGvAdzcWNbyuxS3nWhD7H2cIJxjRRTRLQVB0bdputVY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= -github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= -github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= -github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= -github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= -github.com/alecthomas/chroma v0.7.0 h1:z+0HgTUmkpRDRz0SRSdMaqOLfJV4F+N1FPDZUZIDUzw= -github.com/alecthomas/chroma v0.7.0/go.mod h1:1U/PfCsTALWWYHDnsIQkxEBM0+6LLe0v8+RSVMOwxeY= -github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo= -github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= -github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI= -github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI= -github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA= -github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY= -github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= -github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= -github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= -github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= -github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= -github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= -github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= -github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 h1:uHTyIjqVhYRhLbJ8nIiOJHkEZZ+5YoOsAbD3sk82NiE= -github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.2-0.20191216170541-340f1ebe299e/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI= -github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f h1:dKccXx7xA56UNqOcFIbuqFjAWPVtP688j5QMgmo6OHU= -github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f/go.mod h1:4rEELDSfUAlBSyUjPG0JnaNGjf13JySHFeRdD/3dLP0= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8= -github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.10.8 h1:eLeJ3dr/Y9+XRfJT4l+8ZjmtB5RPJhucH2HeCV5+IZY= -github.com/klauspost/compress v1.10.8/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= -github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98= -github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8= -github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -go.coder.com/cli v0.4.0 h1:PruDGwm/CPFndyK/eMowZG3vzg5CgohRWeXWCTr3zi8= -go.coder.com/cli v0.4.0/go.mod h1:hRTOURCR3LJF1FRW9arecgrzX+AHG7mfYMwThPIgq+w= -go.coder.com/flog v0.0.0-20190906214207-47dd47ea0512 h1:DjCS6dRQh+1PlfiBmnabxfdrzenb0tAwJqFxDEH/s9g= -go.coder.com/flog v0.0.0-20190906214207-47dd47ea0512/go.mod h1:83JsYgXYv0EOaXjIMnaZ1Fl6ddNB3fJnDZ/8845mUJ8= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200422194213-44a606286825 h1:dSChiwOTvzwbHFTMq2l6uRardHH7/E6SqEkqccinS/o= -golang.org/x/crypto v0.0.0-20200422194213-44a606286825/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200610111108-226ff32320da h1:bGb80FudwxpeucJUjPYJXuJ8Hk91vNtfvrymzwiei38= -golang.org/x/sys v0.0.0-20200610111108-226ff32320da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1 h1:aQktFqmDE2yjveXJlVIfslDFmFnUXSqG0i6KRcJAeMc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k= -nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/internal/activity/pusher.go b/internal/activity/pusher.go deleted file mode 100644 index fb57068d..00000000 --- a/internal/activity/pusher.go +++ /dev/null @@ -1,41 +0,0 @@ -package activity - -import ( - "time" - - "cdr.dev/coder-cli/internal/entclient" - "go.coder.com/flog" - "golang.org/x/time/rate" -) - -const pushInterval = time.Minute - -// Pusher pushes activity metrics no more than once per pushInterval. Pushes -// within the same interval are a no-op. -type Pusher struct { - envID string - source string - - client *entclient.Client - rate *rate.Limiter -} - -func NewPusher(c *entclient.Client, envID, source string) *Pusher { - return &Pusher{ - envID: envID, - source: source, - client: c, - rate: rate.NewLimiter(rate.Every(pushInterval), 1), - } -} - -func (p *Pusher) Push() { - if !p.rate.Allow() { - return - } - - err := p.client.PushActivity(p.source, p.envID) - if err != nil { - flog.Error("push activity: %s", err.Error()) - } -} diff --git a/internal/activity/writer.go b/internal/activity/writer.go deleted file mode 100644 index 1e5c4f66..00000000 --- a/internal/activity/writer.go +++ /dev/null @@ -1,17 +0,0 @@ -package activity - -import "io" - -type activityWriter struct { - p *Pusher - wr io.Writer -} - -func (w *activityWriter) Write(p []byte) (n int, err error) { - w.p.Push() - return w.wr.Write(p) -} - -func (p *Pusher) Writer(wr io.Writer) io.Writer { - return &activityWriter{p: p, wr: wr} -} diff --git a/internal/config/dir.go b/internal/config/dir.go deleted file mode 100644 index 34cc7ab5..00000000 --- a/internal/config/dir.go +++ /dev/null @@ -1,49 +0,0 @@ -package config - -import ( - "io/ioutil" - "os" - "path/filepath" - - "github.com/kirsle/configdir" -) - -func dir() string { - return configdir.LocalConfig("coder") -} - -// open opens a file in the configuration directory, -// creating all intermediate directories. -func open(path string, flag int, mode os.FileMode) (*os.File, error) { - path = filepath.Join(dir(), path) - - err := os.MkdirAll(filepath.Dir(path), 0750) - if err != nil { - return nil, err - } - - return os.OpenFile(path, flag, mode) -} - -func write(path string, mode os.FileMode, dat []byte) error { - fi, err := open(path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, mode) - if err != nil { - return err - } - defer fi.Close() - _, err = fi.Write(dat) - return err -} - -func read(path string) ([]byte, error) { - fi, err := open(path, os.O_RDONLY, 0) - if err != nil { - return nil, err - } - defer fi.Close() - return ioutil.ReadAll(fi) -} - -func rm(path string) error { - return os.Remove(filepath.Join(dir(), path)) -} diff --git a/internal/config/doc.go b/internal/config/doc.go deleted file mode 100644 index 69ff5641..00000000 --- a/internal/config/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package config provides facilities for working with the local configuration -// directory. -package config diff --git a/internal/config/file.go b/internal/config/file.go deleted file mode 100644 index de254fc7..00000000 --- a/internal/config/file.go +++ /dev/null @@ -1,21 +0,0 @@ -package config - -type File string - -func (f File) Delete() error { - return rm(string(f)) -} - -func (f File) Write(s string) error { - return write(string(f), 0600, []byte(s)) -} - -func (f File) Read() (string, error) { - byt, err := read(string(f)) - return string(byt), err -} - -var ( - Session File = "session" - URL File = "url" -) diff --git a/internal/entclient/activity.go b/internal/entclient/activity.go deleted file mode 100644 index 92c0afed..00000000 --- a/internal/entclient/activity.go +++ /dev/null @@ -1,21 +0,0 @@ -package entclient - -import ( - "net/http" -) - -func (c Client) PushActivity(source string, envID string) error { - res, err := c.request("POST", "/api/metrics/usage/push", map[string]string{ - "source": source, - "environment_id": envID, - }) - if err != nil { - return err - } - - if res.StatusCode != http.StatusOK { - return bodyError(res) - } - - return nil -} diff --git a/internal/entclient/client.go b/internal/entclient/client.go deleted file mode 100644 index e0609d13..00000000 --- a/internal/entclient/client.go +++ /dev/null @@ -1,36 +0,0 @@ -package entclient - -import ( - "net/http" - "net/http/cookiejar" - "net/url" -) - -type Client struct { - BaseURL *url.URL - Token string -} - -func (c Client) copyURL() *url.URL { - swp := *c.BaseURL - return &swp -} - -func (c *Client) http() (*http.Client, error) { - jar, err := cookiejar.New(nil) - if err != nil { - return nil, err - } - - jar.SetCookies(c.BaseURL, []*http.Cookie{ - { - Name: "session_token", - Value: c.Token, - MaxAge: 86400, - Path: "/", - HttpOnly: true, - Secure: c.BaseURL.Scheme == "https", - }, - }) - return &http.Client{Jar: jar}, nil -} diff --git a/internal/entclient/devurl.go b/internal/entclient/devurl.go deleted file mode 100644 index 57925372..00000000 --- a/internal/entclient/devurl.go +++ /dev/null @@ -1,45 +0,0 @@ -package entclient - -import ( - "fmt" - "net/http" -) - -func (c Client) DelDevURL(envID, urlID string) error { - reqString := "/api/environments/%s/devurls/%s" - reqUrl := fmt.Sprintf(reqString, envID, urlID) - - res, err := c.request("DELETE", reqUrl, map[string]string{ - "environment_id": envID, - "url_id": urlID, - }) - if err != nil { - return err - } - - if res.StatusCode != http.StatusOK { - return bodyError(res) - } - - return nil -} - -func (c Client) UpsertDevURL(envID, port, access string) error { - reqString := "/api/environments/%s/devurls" - reqUrl := fmt.Sprintf(reqString, envID) - - res, err := c.request("POST", reqUrl, map[string]string{ - "environment_id": envID, - "port": port, - "access": access, - }) - if err != nil { - return err - } - - if res.StatusCode != http.StatusOK { - return bodyError(res) - } - - return nil -} diff --git a/internal/entclient/env.go b/internal/entclient/env.go deleted file mode 100644 index 45a7aa0e..00000000 --- a/internal/entclient/env.go +++ /dev/null @@ -1,51 +0,0 @@ -package entclient - -import ( - "context" - "time" - - "nhooyr.io/websocket" -) - -type Environment struct { - Name string `json:"name"` - ID string `json:"id"` -} - -func (c Client) Envs(user *User, org Org) ([]Environment, error) { - var envs []Environment - err := c.requestBody( - "GET", "/api/orgs/"+org.ID+"/members/"+user.ID+"/environments", - nil, - &envs, - ) - return envs, err -} - -func (c Client) DialWsep(ctx context.Context, env Environment) (*websocket.Conn, error) { - u := c.copyURL() - if c.BaseURL.Scheme == "https" { - u.Scheme = "wss" - } else { - u.Scheme = "ws" - } - u.Path = "/proxy/environments/" + env.ID + "/wsep" - - ctx, cancel := context.WithTimeout(ctx, time.Second*15) - defer cancel() - - conn, resp, err := websocket.Dial(ctx, u.String(), - &websocket.DialOptions{ - HTTPHeader: map[string][]string{ - "Cookie": {"session_token=" + c.Token}, - }, - }, - ) - if err != nil { - if resp != nil { - return nil, bodyError(resp) - } - return nil, err - } - return conn, nil -} diff --git a/internal/entclient/error.go b/internal/entclient/error.go deleted file mode 100644 index 49f58669..00000000 --- a/internal/entclient/error.go +++ /dev/null @@ -1,16 +0,0 @@ -package entclient - -import ( - "net/http" - "net/http/httputil" - - "golang.org/x/xerrors" -) - -func bodyError(resp *http.Response) error { - byt, err := httputil.DumpResponse(resp, false) - if err != nil { - return xerrors.Errorf("dump response: %w", err) - } - return xerrors.Errorf("%s\n%s", resp.Request.URL, byt) -} diff --git a/internal/entclient/me.go b/internal/entclient/me.go deleted file mode 100644 index 7c7c66f0..00000000 --- a/internal/entclient/me.go +++ /dev/null @@ -1,30 +0,0 @@ -package entclient - -type User struct { - ID string `json:"id"` - Email string `json:"email"` - Username string `json:"username"` -} - -func (c Client) Me() (*User, error) { - var u User - err := c.requestBody("GET", "/api/users/me", nil, &u) - if err != nil { - return nil, err - } - return &u, nil -} - -type SSHKey struct { - PublicKey string `json:"public_key"` - PrivateKey string `json:"private_key"` -} - -func (c Client) SSHKey() (*SSHKey, error) { - var key SSHKey - err := c.requestBody("GET", "/api/users/me/sshkey", nil, &key) - if err != nil { - return nil, err - } - return &key, nil -} diff --git a/internal/entclient/org.go b/internal/entclient/org.go deleted file mode 100644 index 24a8307b..00000000 --- a/internal/entclient/org.go +++ /dev/null @@ -1,13 +0,0 @@ -package entclient - -type Org struct { - ID string `json:"id"` - Name string `json:"name"` - Members []User `json:"members"` -} - -func (c Client) Orgs() ([]Org, error) { - var os []Org - err := c.requestBody("GET", "/api/orgs", nil, &os) - return os, err -} diff --git a/internal/entclient/request.go b/internal/entclient/request.go deleted file mode 100644 index b5873f81..00000000 --- a/internal/entclient/request.go +++ /dev/null @@ -1,51 +0,0 @@ -package entclient - -import ( - "bytes" - "encoding/json" - "net/http" - - "golang.org/x/xerrors" -) - -func (c Client) request( - method string, path string, - request interface{}, -) (*http.Response, error) { - client, err := c.http() - if err != nil { - return nil, err - } - if request == nil { - request = []byte{} - } - body, err := json.Marshal(request) - if err != nil { - return nil, xerrors.Errorf("marshal request: %w", err) - } - req, err := http.NewRequest(method, c.BaseURL.String()+path, bytes.NewReader(body)) - if err != nil { - return nil, xerrors.Errorf("create request: %w", err) - } - return client.Do(req) -} - -func (c Client) requestBody( - method string, path string, request interface{}, response interface{}, -) error { - resp, err := c.request(method, path, request) - if err != nil { - return err - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return bodyError(resp) - } - - err = json.NewDecoder(resp.Body).Decode(&response) - if err != nil { - return err - } - return nil -} diff --git a/internal/loginsrv/server.go b/internal/loginsrv/server.go deleted file mode 100644 index b062977a..00000000 --- a/internal/loginsrv/server.go +++ /dev/null @@ -1,29 +0,0 @@ -package loginsrv - -import ( - "fmt" - "net/http" - "sync" -) - -type Server struct { - TokenCond *sync.Cond - Token string -} - -func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { - token := r.URL.Query().Get("session_token") - if token == "" { - w.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(w, "No session_token found") - return - } - - s.TokenCond.L.Lock() - s.Token = token - s.TokenCond.L.Unlock() - s.TokenCond.Broadcast() - - w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, "You may close this window now") -} diff --git a/internal/sync/eventcache.go b/internal/sync/eventcache.go deleted file mode 100644 index 85a5e8ac..00000000 --- a/internal/sync/eventcache.go +++ /dev/null @@ -1,70 +0,0 @@ -package sync - -import ( - "os" - "time" - - "github.com/rjeczalik/notify" - - "go.coder.com/flog" -) - -type timedEvent struct { - CreatedAt time.Time - notify.EventInfo -} - -type eventCache map[string]timedEvent - -func (cache eventCache) Add(ev timedEvent) { - log := flog.New() - log.Prefix = ev.Path() + ": " - lastEvent, ok := cache[ev.Path()] - if ok { - switch { - // If the file was quickly created and then destroyed, pretend nothing ever happened. - case lastEvent.Event() == notify.Create && ev.Event() == notify.Remove: - delete(cache, ev.Path()) - log.Info("ignored Create then Remove") - return - } - log.Info("replaced %s with %s", lastEvent.Event(), ev.Event()) - } - // Only let the latest event for a path have action. - cache[ev.Path()] = ev -} - -// SequentialEvents returns the list of events that pertain to directories. -// The set of returned events is disjoint with ConcurrentEvents. -func (cache eventCache) SequentialEvents() []timedEvent { - var r []timedEvent - for _, ev := range cache { - info, err := os.Stat(ev.Path()) - if err == nil && !info.IsDir() { - continue - } - // Include files that have deleted here. - // It's unclear whether they're files or folders. - r = append(r, ev) - - } - return r -} - -// ConcurrentEvents returns the list of events that are safe to process after SequentialEvents. -// The set of returns events is disjoint with SequentialEvents. -func (cache eventCache) ConcurrentEvents() []timedEvent { - var r []timedEvent - for _, ev := range cache { - info, err := os.Stat(ev.Path()) - if err != nil { - continue - } - if info.IsDir() { - continue - } - r = append(r, ev) - - } - return r -} diff --git a/internal/sync/sync.go b/internal/sync/sync.go deleted file mode 100644 index 32086896..00000000 --- a/internal/sync/sync.go +++ /dev/null @@ -1,349 +0,0 @@ -package sync - -import ( - "context" - "errors" - "fmt" - "io" - "io/ioutil" - "os" - "os/exec" - "path" - "path/filepath" - "sync" - "sync/atomic" - "time" - - "github.com/gorilla/websocket" - "github.com/rjeczalik/notify" - "golang.org/x/sync/semaphore" - "golang.org/x/xerrors" - - "go.coder.com/flog" - - "cdr.dev/coder-cli/internal/activity" - "cdr.dev/coder-cli/internal/entclient" - "cdr.dev/wsep" -) - -// Sync runs a live sync daemon. -type Sync struct { - // Init sets whether the sync will do the initial init and then return fast. - Init bool - // LocalDir is an absolute path. - LocalDir string - // RemoteDir is an absolute path. - RemoteDir string - // DisableMetrics disables activity metric pushing. - DisableMetrics bool - - Env entclient.Environment - Client *entclient.Client -} - -// See https://lxadm.com/Rsync_exit_codes#List_of_standard_rsync_exit_codes. -const ( - rsyncExitCodeIncompat = 2 - rsyncExitCodeDataStream = 12 -) - -func (s Sync) syncPaths(delete bool, local, remote string) error { - self := os.Args[0] - - args := []string{"-zz", - "-a", - "--delete", - "-e", self + " sh", local, s.Env.Name + ":" + remote, - } - if delete { - args = append([]string{"--delete"}, args...) - } - if os.Getenv("DEBUG_RSYNC") != "" { - args = append([]string{"--progress"}, args...) - } - - // See https://unix.stackexchange.com/questions/188737/does-compression-option-z-with-rsync-speed-up-backup - // on compression level. - // (AB): compression sped up the initial sync of the enterprise repo by 30%, leading me to believe it's - // good in general for codebases. - cmd := exec.Command("rsync", args...) - cmd.Stdout = os.Stdout - cmd.Stderr = ioutil.Discard - cmd.Stdin = os.Stdin - err := cmd.Run() - if err != nil { - if exitError, ok := err.(*exec.ExitError); ok { - if exitError.ExitCode() == rsyncExitCodeIncompat { - return xerrors.Errorf("no compatible rsync on remote machine: rsync: %w", err) - } else if exitError.ExitCode() == rsyncExitCodeDataStream { - return xerrors.Errorf("protocol datastream error or no remote rsync found: %w", err) - } else { - return xerrors.Errorf("rsync: %w", err) - } - } - return xerrors.Errorf("rsync: %w", err) - } - return nil -} - -func (s Sync) remoteCmd(ctx context.Context, prog string, args ...string) error { - conn, err := s.Client.DialWsep(ctx, s.Env) - if err != nil { - return err - } - defer conn.Close(websocket.CloseNormalClosure, "") - - execer := wsep.RemoteExecer(conn) - process, err := execer.Start(ctx, wsep.Command{ - Command: prog, - Args: args, - }) - if err != nil { - return err - } - go io.Copy(os.Stdout, process.Stderr()) - go io.Copy(os.Stderr, process.Stdout()) - - err = process.Wait() - if code, ok := err.(wsep.ExitError); ok { - return fmt.Errorf("%s exit status: %v", prog, code) - } - if err != nil { - return xerrors.Errorf("execution failure: %w", err) - } - return nil -} - -// initSync performs the initial synchronization of the directory. -func (s Sync) initSync() error { - flog.Info("doing initial sync (%v -> %v)", s.LocalDir, s.RemoteDir) - - start := time.Now() - // Delete old files on initial sync (e.g git checkout). - // Add the "/." to the local directory so rsync doesn't try to place the directory - // into the remote dir. - err := s.syncPaths(true, s.LocalDir+"/.", s.RemoteDir) - if err == nil { - flog.Success("finished initial sync (%v)", time.Since(start).Truncate(time.Millisecond)) - } - return err -} - -func (s Sync) convertPath(local string) string { - relLocalPath, err := filepath.Rel(s.LocalDir, local) - if err != nil { - panic(err) - } - return filepath.Join( - s.RemoteDir, - relLocalPath, - ) -} - -func (s Sync) handleCreate(localPath string) error { - target := s.convertPath(localPath) - err := s.syncPaths(false, localPath, target) - if err != nil { - _, statErr := os.Stat(localPath) - // File was quickly deleted. - if os.IsNotExist(statErr) { - return nil - } - - return err - } - return nil -} - -func (s Sync) handleDelete(localPath string) error { - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - - return s.remoteCmd(ctx, "rm", "-rf", s.convertPath(localPath)) -} - -func (s Sync) handleRename(localPath string) error { - // The rename operation is sent in two events, one - // for the old (gone) file and one for the new file. - // Catching both would require complex state. - // Instead, we turn it into a Create or Delete based - // on file existence. - info, err := os.Stat(localPath) - if err != nil { - if os.IsNotExist(err) { - return s.handleDelete(localPath) - } - return err - } - if info.IsDir() { - // Without this, the directory will be created as a subdirectory. - localPath += "/." - } - return s.handleCreate(localPath) -} - -func (s Sync) work(ev timedEvent) { - var ( - localPath = ev.Path() - err error - ) - switch ev.Event() { - case notify.Write, notify.Create: - err = s.handleCreate(localPath) - case notify.Rename: - err = s.handleRename(localPath) - case notify.Remove: - err = s.handleDelete(localPath) - default: - flog.Info("unhandled event %v %+v", ev.Event(), ev.Path()) - } - - log := fmt.Sprintf("%v %v (%v)", - ev.Event(), filepath.Base(localPath), time.Since(ev.CreatedAt).Truncate(time.Millisecond*10), - ) - if err != nil { - flog.Error(log+": %v", err) - } else { - flog.Success(log) - } -} - -var ErrRestartSync = errors.New("the sync exited because it was overloaded, restart it") - -// workEventGroup converges a group of events to prevent duplicate work. -func (s Sync) workEventGroup(evs []timedEvent) { - cache := make(eventCache) - for _, ev := range evs { - cache.Add(ev) - } - - // We want to process events concurrently but safely for speed. - // Because the event cache prevents duplicate events for the same file, race conditions of that type - // are impossible. - // What is possible is a dependency on a previous Rename or Create. For example, if a directory is renamed - // and then a file is moved to it. AFAIK this dependecy only exists with Directories. - // So, we sequentially process the list of directory Renames and Creates, and then concurrently - // perform all Writes. - for _, ev := range cache.SequentialEvents() { - s.work(ev) - } - - sem := semaphore.NewWeighted(8) - - var wg sync.WaitGroup - for _, ev := range cache.ConcurrentEvents() { - setConsoleTitle(fmtUpdateTitle(ev.Path())) - - wg.Add(1) - sem.Acquire(context.Background(), 1) - ev := ev - go func() { - defer sem.Release(1) - defer wg.Done() - s.work(ev) - }() - } - - wg.Wait() -} - -const ( - // maxinflightInotify sets the maximum number of inotifies before the - // sync just restarts. Syncing a large amount of small files (e.g .git - // or node_modules) is impossible to do performantly with individual - // rsyncs. - maxInflightInotify = 8 - maxEventDelay = time.Second * 7 - // maxAcceptableDispatch is the maximum amount of time before an event - // should begin its journey to the server. This sets a lower bound for - // perceivable latency, but the higher it is, the better the - // optimization. - maxAcceptableDispatch = time.Millisecond * 50 -) - -// Run starts the sync synchronously. -// Use this command to debug what wasn't sync'd correctly: -// rsync -e "coder sh" -nicr ~/Projects/cdr/coder-cli/. ammar:/home/coder/coder-cli/ -func (s Sync) Run() error { - events := make(chan notify.EventInfo, maxInflightInotify) - // Set up a recursive watch. - // We do this before the initial sync so we can capture any changes - // that may have happened during sync. - err := notify.Watch(path.Join(s.LocalDir, "..."), events, notify.All) - if err != nil { - return xerrors.Errorf("create watch: %w", err) - } - defer notify.Stop(events) - - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - - s.remoteCmd(ctx, "mkdir", "-p", s.RemoteDir) - - ap := activity.NewPusher(s.Client, s.Env.ID, activityName) - ap.Push() - - setConsoleTitle("⏳ syncing project") - err = s.initSync() - if err != nil { - return err - } - - if s.Init { - return nil - } - - flog.Info("watching %s for changes", s.LocalDir) - - var droppedEvents uint64 - // Timed events lets us track how long each individual file takes to - // update. - timedEvents := make(chan timedEvent, cap(events)) - go func() { - defer close(timedEvents) - for event := range events { - select { - case timedEvents <- timedEvent{ - CreatedAt: time.Now(), - EventInfo: event, - }: - default: - if atomic.AddUint64(&droppedEvents, 1) == 1 { - flog.Info("dropped event, sync should restart soon") - } - } - } - }() - - var ( - eventGroup []timedEvent - dispatchEventGroup = time.NewTicker(maxAcceptableDispatch) - ) - defer dispatchEventGroup.Stop() - for { - const watchingFilesystemTitle = "🛰 watching filesystem" - setConsoleTitle(watchingFilesystemTitle) - - select { - case ev := <-timedEvents: - if atomic.LoadUint64(&droppedEvents) > 0 { - return ErrRestartSync - } - - eventGroup = append(eventGroup, ev) - case <-dispatchEventGroup.C: - if len(eventGroup) == 0 { - continue - } - // We're too backlogged and should restart the sync. - if time.Since(eventGroup[0].CreatedAt) > maxEventDelay { - return ErrRestartSync - } - s.workEventGroup(eventGroup) - eventGroup = eventGroup[:0] - ap.Push() - } - } -} - -const activityName = "sync" diff --git a/internal/sync/title.go b/internal/sync/title.go deleted file mode 100644 index c9a91c8b..00000000 --- a/internal/sync/title.go +++ /dev/null @@ -1,20 +0,0 @@ -package sync - -import ( - "fmt" - "os" - "path/filepath" - - "golang.org/x/crypto/ssh/terminal" -) - -func setConsoleTitle(title string) { - if !terminal.IsTerminal(int(os.Stdout.Fd())) { - return - } - fmt.Printf("\033]0;%s\007", title) -} - -func fmtUpdateTitle(path string) string { - return "🚀 updating " + filepath.Base(path) -}