From 8f41aa3c274f69855009e7b6f49ba39fe8bb3156 Mon Sep 17 00:00:00 2001 From: Charlie Moog Date: Tue, 4 Aug 2020 18:30:19 -0500 Subject: [PATCH] Add --user flag to "secrets" and "envs" --- cmd/coder/auth.go | 6 +- cmd/coder/ceapi.go | 22 +++--- cmd/coder/configssh.go | 8 +- cmd/coder/envs.go | 11 ++- cmd/coder/main.go | 6 +- cmd/coder/secrets.go | 134 ++++++++++++++++++--------------- cmd/coder/shell.go | 36 ++++----- cmd/coder/sync.go | 3 +- cmd/coder/urls.go | 36 +++++---- cmd/coder/users.go | 2 +- internal/activity/pusher.go | 5 +- internal/activity/writer.go | 7 +- internal/entclient/activity.go | 5 +- internal/entclient/client.go | 3 + internal/entclient/devurl.go | 22 +++--- internal/entclient/env.go | 3 +- internal/entclient/me.go | 11 +-- internal/entclient/org.go | 6 +- internal/entclient/request.go | 7 +- internal/entclient/secrets.go | 69 +++++++++++++---- internal/entclient/users.go | 23 +++++- internal/sync/sync.go | 4 +- 22 files changed, 271 insertions(+), 158 deletions(-) diff --git a/cmd/coder/auth.go b/cmd/coder/auth.go index d15cf914..abd0e8d0 100644 --- a/cmd/coder/auth.go +++ b/cmd/coder/auth.go @@ -35,8 +35,10 @@ func newClient() (*entclient.Client, error) { return nil, xerrors.Errorf("url misformatted: %v (try runing coder login)", err) } - return &entclient.Client{ + client := &entclient.Client{ BaseURL: u, Token: sessionToken, - }, nil + } + + return client, nil } diff --git a/cmd/coder/ceapi.go b/cmd/coder/ceapi.go index aff2bf5e..823f36ff 100644 --- a/cmd/coder/ceapi.go +++ b/cmd/coder/ceapi.go @@ -1,6 +1,8 @@ package main import ( + "context" + "golang.org/x/xerrors" "go.coder.com/flog" @@ -27,23 +29,23 @@ outer: } // getEnvs returns all environments for the user. -func getEnvs(client *entclient.Client) ([]entclient.Environment, error) { - me, err := client.Me() +func getEnvs(ctx context.Context, client *entclient.Client, email string) ([]entclient.Environment, error) { + user, err := client.UserByEmail(ctx, email) if err != nil { - return nil, xerrors.Errorf("get self: %+v", err) + return nil, xerrors.Errorf("get user: %+v", err) } - orgs, err := client.Orgs() + orgs, err := client.Orgs(ctx) if err != nil { return nil, xerrors.Errorf("get orgs: %+v", err) } - orgs = userOrgs(me, orgs) + orgs = userOrgs(user, orgs) var allEnvs []entclient.Environment for _, org := range orgs { - envs, err := client.Envs(me, org) + envs, err := client.Envs(ctx, user, org) if err != nil { return nil, xerrors.Errorf("get envs for %v: %+v", org.Name, err) } @@ -56,8 +58,8 @@ func getEnvs(client *entclient.Client) ([]entclient.Environment, error) { } // findEnv returns a single environment by name (if it exists.) -func findEnv(client *entclient.Client, name string) (*entclient.Environment, error) { - envs, err := getEnvs(client) +func findEnv(ctx context.Context, client *entclient.Client, envName, userEmail string) (*entclient.Environment, error) { + envs, err := getEnvs(ctx, client, userEmail) if err != nil { return nil, xerrors.Errorf("get environments: %w", err) } @@ -66,11 +68,11 @@ func findEnv(client *entclient.Client, name string) (*entclient.Environment, err for _, env := range envs { found = append(found, env.Name) - if env.Name == name { + if env.Name == envName { return &env, nil } } flog.Error("found %q", found) - flog.Error("%q not found", name) + flog.Error("%q not found", envName) return nil, xerrors.New("environment not found") } diff --git a/cmd/coder/configssh.go b/cmd/coder/configssh.go index 135e7c58..50b991d4 100644 --- a/cmd/coder/configssh.go +++ b/cmd/coder/configssh.go @@ -83,19 +83,19 @@ func configSSH(filepath *string, remove *bool) func(cmd *cobra.Command, _ []stri return xerrors.New("SSH is disabled or not available for your Coder Enterprise deployment.") } - me, err := entClient.Me() + user, err := entClient.Me(cmd.Context()) if err != nil { return xerrors.Errorf("fetch username: %w", err) } - envs, err := getEnvs(entClient) + envs, err := getEnvs(cmd.Context(), entClient, entclient.Me) if err != nil { return err } if len(envs) < 1 { return xerrors.New("no environments found") } - newConfig, err := makeNewConfigs(me.Username, envs, startToken, startMessage, endToken) + newConfig, err := makeNewConfigs(user.Username, envs, startToken, startMessage, endToken) if err != nil { return xerrors.Errorf("make new ssh configurations: %w", err) } @@ -127,7 +127,7 @@ var ( ) func writeSSHKey(ctx context.Context, client *entclient.Client) error { - key, err := client.SSHKey() + key, err := client.SSHKey(ctx) if err != nil { return err } diff --git a/cmd/coder/envs.go b/cmd/coder/envs.go index 3442bdea..32d73245 100644 --- a/cmd/coder/envs.go +++ b/cmd/coder/envs.go @@ -4,18 +4,23 @@ import ( "encoding/json" "os" + "cdr.dev/coder-cli/internal/entclient" "cdr.dev/coder-cli/internal/x/xtabwriter" "github.com/spf13/cobra" "golang.org/x/xerrors" + + "go.coder.com/flog" ) func makeEnvsCommand() *cobra.Command { var outputFmt string + var user string cmd := &cobra.Command{ Use: "envs", Short: "Interact with Coder environments", Long: "Perform operations on the Coder environments owned by the active user.", } + cmd.PersistentFlags().StringVar(&user, "user", entclient.Me, "Specify the user whose resources to target") lsCmd := &cobra.Command{ Use: "ls", @@ -23,10 +28,14 @@ func makeEnvsCommand() *cobra.Command { Long: "List all Coder environments owned by the active user.", RunE: func(cmd *cobra.Command, args []string) error { entClient := requireAuth() - envs, err := getEnvs(entClient) + envs, err := getEnvs(cmd.Context(), entClient, user) if err != nil { return err } + if len(envs) < 1 { + flog.Info("no environments found") + return nil + } switch outputFmt { case "human": diff --git a/cmd/coder/main.go b/cmd/coder/main.go index 7346212b..5cf3454b 100644 --- a/cmd/coder/main.go +++ b/cmd/coder/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "log" "net/http" @@ -19,6 +20,9 @@ var ( ) func main() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + if os.Getenv("PPROF") != "" { go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) @@ -49,7 +53,7 @@ func main() { makeURLCmd(), completionCmd, ) - err = app.Execute() + err = app.ExecuteContext(ctx) if err != nil { os.Exit(1) } diff --git a/cmd/coder/secrets.go b/cmd/coder/secrets.go index 27bade21..9433588c 100644 --- a/cmd/coder/secrets.go +++ b/cmd/coder/secrets.go @@ -15,37 +15,39 @@ import ( ) func makeSecretsCmd() *cobra.Command { + var user string cmd := &cobra.Command{ Use: "secrets", Short: "Interact with Coder Secrets", Long: "Interact with secrets objects owned by the active user.", } + cmd.PersistentFlags().StringVar(&user, "user", entclient.Me, "Specify the user whose resources to target") cmd.AddCommand( &cobra.Command{ Use: "ls", Short: "List all secrets owned by the active user", - RunE: listSecrets, + RunE: listSecrets(&user), }, - makeCreateSecret(), + makeCreateSecret(&user), &cobra.Command{ Use: "rm [...secret_name]", Short: "Remove one or more secrets by name", Args: cobra.MinimumNArgs(1), - RunE: removeSecrets, + RunE: makeRemoveSecrets(&user), Example: "coder secrets rm mysql-password mysql-user", }, &cobra.Command{ Use: "view [secret_name]", Short: "View a secret by name", Args: cobra.ExactArgs(1), - RunE: viewSecret, + RunE: makeViewSecret(&user), Example: "coder secrets view mysql-password", }, ) return cmd } -func makeCreateSecret() *cobra.Command { +func makeCreateSecret(user *string) *cobra.Command { var ( fromFile string fromLiteral string @@ -107,10 +109,12 @@ coder secrets create aws-credentials --from-file ./credentials.json`, } } - err = client.InsertSecret(entclient.InsertSecretReq{ + err = client.InsertSecret(cmd.Context(), entclient.InsertSecretReq{ Name: name, Value: value, Description: description, + }, &entclient.ReqOptions{ + User: *user, }) if err != nil { return xerrors.Errorf("insert secret: %w", err) @@ -127,71 +131,83 @@ coder secrets create aws-credentials --from-file ./credentials.json`, return cmd } -func listSecrets(cmd *cobra.Command, _ []string) error { - client := requireAuth() +func listSecrets(user *string) func(cmd *cobra.Command, _ []string) error { + return func(cmd *cobra.Command, _ []string) error { + client := requireAuth() - secrets, err := client.Secrets() - if err != nil { - return xerrors.Errorf("get secrets: %w", err) - } + secrets, err := client.Secrets(cmd.Context(), &entclient.ReqOptions{ + User: *user, + }) + if err != nil { + return xerrors.Errorf("get secrets: %w", err) + } - if len(secrets) < 1 { - flog.Info("No secrets found") - return nil - } + if len(secrets) < 1 { + flog.Info("No secrets found") + return nil + } - err = xtabwriter.WriteTable(len(secrets), func(i int) interface{} { - s := secrets[i] - s.Value = "******" // value is omitted from bulk responses - return s - }) - if err != nil { - return xerrors.Errorf("write table of secrets: %w", err) + err = xtabwriter.WriteTable(len(secrets), func(i int) interface{} { + s := secrets[i] + s.Value = "******" // value is omitted from bulk responses + return s + }) + if err != nil { + return xerrors.Errorf("write table of secrets: %w", err) + } + return nil } - return nil } -func viewSecret(_ *cobra.Command, args []string) error { - var ( - client = requireAuth() - name = args[0] - ) - if name == "" { - return xerrors.New("[name] is a required argument") - } +func makeViewSecret(user *string) func(cmd *cobra.Command, args []string) error { + return func(cmd *cobra.Command, args []string) error { + var ( + client = requireAuth() + name = args[0] + ) + if name == "" { + return xerrors.New("[name] is a required argument") + } - secret, err := client.SecretByName(name) - if err != nil { - return xerrors.Errorf("get secret by name: %w", err) - } + secret, err := client.SecretByName(cmd.Context(), name, &entclient.ReqOptions{ + User: *user, + }) + if err != nil { + return xerrors.Errorf("get secret by name: %w", err) + } - _, err = fmt.Fprintln(os.Stdout, secret.Value) - if err != nil { - return xerrors.Errorf("write secret value: %w", err) + _, err = fmt.Fprintln(os.Stdout, secret.Value) + if err != nil { + return xerrors.Errorf("write secret value: %w", err) + } + return nil } - return nil } -func removeSecrets(_ *cobra.Command, args []string) error { - var ( - client = requireAuth() - ) - if len(args) < 1 { - return xerrors.New("[...secret_name] is a required argument") - } +func makeRemoveSecrets(user *string) func(c *cobra.Command, args []string) error { + return func(cmd *cobra.Command, args []string) error { + var ( + client = requireAuth() + ) + if len(args) < 1 { + return xerrors.New("[...secret_name] is a required argument") + } - errorSeen := false - for _, n := range args { - err := client.DeleteSecretByName(n) - if err != nil { - flog.Error("failed to delete secret %q: %v", n, err) - errorSeen = true - } else { - flog.Success("successfully deleted secret %q", n) + errorSeen := false + for _, n := range args { + err := client.DeleteSecretByName(cmd.Context(), n, &entclient.ReqOptions{ + User: *user, + }) + if err != nil { + flog.Error("failed to delete secret %q: %v", n, err) + errorSeen = true + } else { + flog.Success("successfully deleted secret %q", n) + } } + if errorSeen { + os.Exit(1) + } + return nil } - if errorSeen { - os.Exit(1) - } - return nil } diff --git a/cmd/coder/shell.go b/cmd/coder/shell.go index 0012c338..eca157e0 100644 --- a/cmd/coder/shell.go +++ b/cmd/coder/shell.go @@ -8,6 +8,7 @@ import ( "time" "cdr.dev/coder-cli/internal/activity" + "cdr.dev/coder-cli/internal/entclient" "cdr.dev/coder-cli/internal/x/xterminal" "cdr.dev/wsep" "github.com/spf13/cobra" @@ -19,23 +20,22 @@ import ( "go.coder.com/flog" ) -func getEnvsForCompletion() []string { - // TODO(@cmoog): Enable this if speed issue can be resolved. Otherwise, all commands will take > 1 second. - return nil - - var envNames []string - client, err := newClient() - if err != nil { - return envNames - } - envs, err := getEnvs(client) - if err != nil { - return envNames - } - for _, e := range envs { - envNames = append(envNames, e.Name) +func getEnvsForCompletion(user string) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + var envNames []string + client, err := newClient() + if err != nil { + return envNames, cobra.ShellCompDirectiveDefault + } + envs, err := getEnvs(context.TODO(), client, user) + if err != nil { + return envNames, cobra.ShellCompDirectiveDefault + } + for _, e := range envs { + envNames = append(envNames, e.Name) + } + return envNames, cobra.ShellCompDirectiveDefault } - return envNames } func makeShellCmd() *cobra.Command { @@ -45,7 +45,7 @@ func makeShellCmd() *cobra.Command { Long: "Execute a remote command on the environment\\nIf no command is specified, the default shell is opened.", Args: cobra.MinimumNArgs(1), DisableFlagParsing: true, - ValidArgs: getEnvsForCompletion(), + ValidArgsFunction: getEnvsForCompletion(entclient.Me), RunE: shell, Example: "coder sh backend-env", } @@ -99,7 +99,7 @@ func runCommand(ctx context.Context, envName string, command string, args []stri var ( entClient = requireAuth() ) - env, err := findEnv(entClient, envName) + env, err := findEnv(ctx, entClient, envName, entclient.Me) if err != nil { return err } diff --git a/cmd/coder/sync.go b/cmd/coder/sync.go index 20aed14c..6cab1bc2 100644 --- a/cmd/coder/sync.go +++ b/cmd/coder/sync.go @@ -8,6 +8,7 @@ import ( "path/filepath" "strings" + "cdr.dev/coder-cli/internal/entclient" "cdr.dev/coder-cli/internal/sync" "github.com/spf13/cobra" "golang.org/x/xerrors" @@ -70,7 +71,7 @@ func makeRunSync(init *bool) func(cmd *cobra.Command, args []string) error { remoteDir = remoteTokens[1] ) - env, err := findEnv(entClient, envName) + env, err := findEnv(cmd.Context(), entClient, envName, entclient.Me) if err != nil { return err } diff --git a/cmd/coder/urls.go b/cmd/coder/urls.go index 01a3bd7b..7f29cdb8 100644 --- a/cmd/coder/urls.go +++ b/cmd/coder/urls.go @@ -1,6 +1,7 @@ package main import ( + "context" "encoding/json" "fmt" "net/http" @@ -9,6 +10,7 @@ import ( "strconv" "strings" + "cdr.dev/coder-cli/internal/entclient" "cdr.dev/coder-cli/internal/x/xtabwriter" "github.com/spf13/cobra" "golang.org/x/xerrors" @@ -23,11 +25,11 @@ func makeURLCmd() *cobra.Command { Short: "Interact with environment DevURLs", } lsCmd := &cobra.Command{ - Use: "ls [environment_name]", - Short: "List all DevURLs for an environment", - Args: cobra.ExactArgs(1), - ValidArgs: getEnvsForCompletion(), - RunE: makeListDevURLs(&outputFmt), + Use: "ls [environment_name]", + Short: "List all DevURLs for an environment", + Args: cobra.ExactArgs(1), + ValidArgsFunction: getEnvsForCompletion(entclient.Me), + RunE: makeListDevURLs(&outputFmt), } lsCmd.Flags().StringVarP(&outputFmt, "output", "o", "human", "human|json") @@ -92,13 +94,17 @@ func accessLevelIsValid(level string) bool { func makeListDevURLs(outputFmt *string) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { envName := args[0] - devURLs, err := urlList(envName) + devURLs, err := urlList(cmd.Context(), envName) if err != nil { return err } switch *outputFmt { case "human": + if len(devURLs) < 1 { + flog.Info("No devURLs found for environment %q", envName) + return nil + } err := xtabwriter.WriteTable(len(devURLs), func(i int) interface{} { return devURLs[i] }) @@ -149,12 +155,12 @@ func makeCreateDevURL() *cobra.Command { } entClient := requireAuth() - env, err := findEnv(entClient, envName) + env, err := findEnv(cmd.Context(), entClient, envName, entclient.Me) if err != nil { return err } - urls, err := urlList(envName) + urls, err := urlList(cmd.Context(), envName) if err != nil { return err } @@ -162,13 +168,13 @@ func makeCreateDevURL() *cobra.Command { urlID, found := devURLID(portNum, urls) if found { flog.Info("Updating devurl for port %v", port) - err := entClient.UpdateDevURL(env.ID, urlID, portNum, urlname, access) + err := entClient.UpdateDevURL(cmd.Context(), env.ID, urlID, portNum, urlname, access) if err != nil { return xerrors.Errorf("update DevURL: %w", err) } } else { flog.Info("Adding devurl for port %v", port) - err := entClient.InsertDevURL(env.ID, portNum, urlname, access) + err := entClient.InsertDevURL(cmd.Context(), env.ID, portNum, urlname, access) if err != nil { return xerrors.Errorf("insert DevURL: %w", err) } @@ -214,12 +220,12 @@ func removeDevURL(cmd *cobra.Command, args []string) error { } entClient := requireAuth() - env, err := findEnv(entClient, envName) + env, err := findEnv(cmd.Context(), entClient, envName, entclient.Me) if err != nil { return err } - urls, err := urlList(envName) + urls, err := urlList(cmd.Context(), envName) if err != nil { return err } @@ -231,7 +237,7 @@ func removeDevURL(cmd *cobra.Command, args []string) error { return xerrors.Errorf("No devurl found for port %v", port) } - err = entClient.DelDevURL(env.ID, urlID) + err = entClient.DelDevURL(cmd.Context(), env.ID, urlID) if err != nil { return xerrors.Errorf("delete DevURL: %w", err) } @@ -239,9 +245,9 @@ func removeDevURL(cmd *cobra.Command, args []string) error { } // urlList returns the list of active devURLs from the cemanager. -func urlList(envName string) ([]DevURL, error) { +func urlList(ctx context.Context, envName string) ([]DevURL, error) { entClient := requireAuth() - env, err := findEnv(entClient, envName) + env, err := findEnv(ctx, entClient, envName, entclient.Me) if err != nil { return nil, err } diff --git a/cmd/coder/users.go b/cmd/coder/users.go index bbfb08b8..d43d2f4b 100644 --- a/cmd/coder/users.go +++ b/cmd/coder/users.go @@ -33,7 +33,7 @@ func listUsers(outputFmt *string) func(cmd *cobra.Command, args []string) error return func(cmd *cobra.Command, args []string) error { entClient := requireAuth() - users, err := entClient.Users() + users, err := entClient.Users(cmd.Context()) if err != nil { return xerrors.Errorf("get users: %w", err) } diff --git a/internal/activity/pusher.go b/internal/activity/pusher.go index c2102fc1..d0f5954c 100644 --- a/internal/activity/pusher.go +++ b/internal/activity/pusher.go @@ -1,6 +1,7 @@ package activity import ( + "context" "time" "cdr.dev/coder-cli/internal/entclient" @@ -32,12 +33,12 @@ func NewPusher(c *entclient.Client, envID, source string) *Pusher { } // Push pushes activity, abiding by a rate limit -func (p *Pusher) Push() { +func (p *Pusher) Push(ctx context.Context) { if !p.rate.Allow() { return } - err := p.client.PushActivity(p.source, p.envID) + err := p.client.PushActivity(ctx, 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 index a10c4341..f7358924 100644 --- a/internal/activity/writer.go +++ b/internal/activity/writer.go @@ -1,6 +1,9 @@ package activity -import "io" +import ( + "context" + "io" +) type activityWriter struct { p *Pusher @@ -9,7 +12,7 @@ type activityWriter struct { // Write writes to the underlying writer and tracks activity func (w *activityWriter) Write(p []byte) (n int, err error) { - w.p.Push() + w.p.Push(context.Background()) return w.wr.Write(p) } diff --git a/internal/entclient/activity.go b/internal/entclient/activity.go index 362d7da0..8aba99d3 100644 --- a/internal/entclient/activity.go +++ b/internal/entclient/activity.go @@ -1,12 +1,13 @@ package entclient import ( + "context" "net/http" ) // PushActivity pushes CLI activity to Coder -func (c Client) PushActivity(source string, envID string) error { - res, err := c.request("POST", "/api/metrics/usage/push", map[string]string{ +func (c Client) PushActivity(ctx context.Context, source string, envID string) error { + res, err := c.request(ctx, "POST", "/api/metrics/usage/push", map[string]string{ "source": source, "environment_id": envID, }) diff --git a/internal/entclient/client.go b/internal/entclient/client.go index a306c707..703fcbb1 100644 --- a/internal/entclient/client.go +++ b/internal/entclient/client.go @@ -6,6 +6,9 @@ import ( "net/url" ) +// Me is the route param to access resources of the authenticated user +const Me = "me" + // Client wraps the Coder HTTP API type Client struct { BaseURL *url.URL diff --git a/internal/entclient/devurl.go b/internal/entclient/devurl.go index b2d34025..3ed8187c 100644 --- a/internal/entclient/devurl.go +++ b/internal/entclient/devurl.go @@ -1,6 +1,7 @@ package entclient import ( + "context" "fmt" "net/http" ) @@ -20,11 +21,10 @@ type delDevURLRequest struct { } // DelDevURL deletes the specified devurl -func (c Client) DelDevURL(envID, urlID string) error { - reqString := "/api/environments/%s/devurls/%s" - reqURL := fmt.Sprintf(reqString, envID, urlID) +func (c Client) DelDevURL(ctx context.Context, envID, urlID string) error { + reqURL := fmt.Sprintf("/api/environments/%s/devurls/%s", envID, urlID) - res, err := c.request("DELETE", reqURL, delDevURLRequest{ + res, err := c.request(ctx, "DELETE", reqURL, delDevURLRequest{ EnvID: envID, DevURLID: urlID, }) @@ -48,11 +48,10 @@ type createDevURLRequest struct { } // InsertDevURL inserts a new devurl for the authenticated user -func (c Client) InsertDevURL(envID string, port int, name, access string) error { - reqString := "/api/environments/%s/devurls" - reqURL := fmt.Sprintf(reqString, envID) +func (c Client) InsertDevURL(ctx context.Context, envID string, port int, name, access string) error { + reqURL := fmt.Sprintf("/api/environments/%s/devurls", envID) - res, err := c.request("POST", reqURL, createDevURLRequest{ + res, err := c.request(ctx, "POST", reqURL, createDevURLRequest{ EnvID: envID, Port: port, Access: access, @@ -73,11 +72,10 @@ func (c Client) InsertDevURL(envID string, port int, name, access string) error type updateDevURLRequest createDevURLRequest // UpdateDevURL updates an existing devurl for the authenticated user -func (c Client) UpdateDevURL(envID, urlID string, port int, name, access string) error { - reqString := "/api/environments/%s/devurls/%s" - reqURL := fmt.Sprintf(reqString, envID, urlID) +func (c Client) UpdateDevURL(ctx context.Context, envID, urlID string, port int, name, access string) error { + reqURL := fmt.Sprintf("/api/environments/%s/devurls/%s", envID, urlID) - res, err := c.request("PUT", reqURL, updateDevURLRequest{ + res, err := c.request(ctx, "PUT", reqURL, updateDevURLRequest{ EnvID: envID, Port: port, Access: access, diff --git a/internal/entclient/env.go b/internal/entclient/env.go index 508f438f..93773f43 100644 --- a/internal/entclient/env.go +++ b/internal/entclient/env.go @@ -34,9 +34,10 @@ type Environment struct { } // Envs gets the list of environments owned by the authenticated user -func (c Client) Envs(user *User, org Org) ([]Environment, error) { +func (c Client) Envs(ctx context.Context, user *User, org Org) ([]Environment, error) { var envs []Environment err := c.requestBody( + ctx, "GET", "/api/orgs/"+org.ID+"/members/"+user.ID+"/environments", nil, &envs, diff --git a/internal/entclient/me.go b/internal/entclient/me.go index 2d57d8cc..5a70bd2f 100644 --- a/internal/entclient/me.go +++ b/internal/entclient/me.go @@ -1,6 +1,7 @@ package entclient import ( + "context" "time" ) @@ -14,10 +15,10 @@ type User struct { UpdatedAt time.Time `json:"updated_at" tab:"-"` } -// Me gets the details of the authenticated user -func (c Client) Me() (*User, error) { +// Me gets the details of the authenticated user. +func (c Client) Me(ctx context.Context) (*User, error) { var u User - err := c.requestBody("GET", "/api/users/me", nil, &u) + err := c.requestBody(ctx, "GET", "/api/users/me", nil, &u) if err != nil { return nil, err } @@ -31,9 +32,9 @@ type SSHKey struct { } // SSHKey gets the current SSH kepair of the authenticated user -func (c Client) SSHKey() (*SSHKey, error) { +func (c Client) SSHKey(ctx context.Context) (*SSHKey, error) { var key SSHKey - err := c.requestBody("GET", "/api/users/me/sshkey", nil, &key) + err := c.requestBody(ctx, "GET", "/api/users/me/sshkey", nil, &key) if err != nil { return nil, err } diff --git a/internal/entclient/org.go b/internal/entclient/org.go index 6ac9cdc5..868eeba8 100644 --- a/internal/entclient/org.go +++ b/internal/entclient/org.go @@ -1,5 +1,7 @@ package entclient +import "context" + // Org describes an Organization in Coder type Org struct { ID string `json:"id"` @@ -8,8 +10,8 @@ type Org struct { } // Orgs gets all Organizations -func (c Client) Orgs() ([]Org, error) { +func (c Client) Orgs(ctx context.Context) ([]Org, error) { var os []Org - err := c.requestBody("GET", "/api/orgs", nil, &os) + err := c.requestBody(ctx, "GET", "/api/orgs", nil, &os) return os, err } diff --git a/internal/entclient/request.go b/internal/entclient/request.go index dfd0d6fe..507df424 100644 --- a/internal/entclient/request.go +++ b/internal/entclient/request.go @@ -2,6 +2,7 @@ package entclient import ( "bytes" + "context" "encoding/json" "net/http" @@ -9,6 +10,7 @@ import ( ) func (c Client) request( + ctx context.Context, method string, path string, request interface{}, ) (*http.Response, error) { @@ -23,7 +25,7 @@ func (c Client) request( if err != nil { return nil, xerrors.Errorf("marshal request: %w", err) } - req, err := http.NewRequest(method, c.BaseURL.String()+path, bytes.NewReader(body)) + req, err := http.NewRequestWithContext(ctx, method, c.BaseURL.String()+path, bytes.NewReader(body)) if err != nil { return nil, xerrors.Errorf("create request: %w", err) } @@ -31,9 +33,10 @@ func (c Client) request( } func (c Client) requestBody( + ctx context.Context, method string, path string, request interface{}, response interface{}, ) error { - resp, err := c.request(method, path, request) + resp, err := c.request(ctx, method, path, request) if err != nil { return err } diff --git a/internal/entclient/secrets.go b/internal/entclient/secrets.go index 72a62105..d1ad25bf 100644 --- a/internal/entclient/secrets.go +++ b/internal/entclient/secrets.go @@ -1,6 +1,7 @@ package entclient import ( + "context" "net/http" "time" ) @@ -15,21 +16,47 @@ type Secret struct { UpdatedAt time.Time `json:"updated_at" tab:"-"` } +// ReqOptions define api request configuration options +type ReqOptions struct { + // User defines the users whose resources should be targeted + User string +} + +// DefaultReqOptions define reasonable defaults for an api request +var DefaultReqOptions = &ReqOptions{ + User: Me, +} + // Secrets gets all secrets owned by the authed user -func (c *Client) Secrets() ([]Secret, error) { +func (c *Client) Secrets(ctx context.Context, opts *ReqOptions) ([]Secret, error) { + if opts == nil { + opts = DefaultReqOptions + } + user, err := c.UserByEmail(ctx, opts.User) + if err != nil { + return nil, err + } var secrets []Secret - err := c.requestBody(http.MethodGet, "/api/users/me/secrets", nil, &secrets) + err = c.requestBody(ctx, http.MethodGet, "/api/users/"+user.ID+"/secrets", nil, &secrets) return secrets, err } -func (c *Client) secretByID(id string) (*Secret, error) { +func (c *Client) secretByID(ctx context.Context, id string, opts *ReqOptions) (*Secret, error) { + if opts == nil { + opts = DefaultReqOptions + } + user, err := c.UserByEmail(ctx, opts.User) + if err != nil { + return nil, err + } + var secret Secret - err := c.requestBody(http.MethodGet, "/api/users/me/secrets/"+id, nil, &secret) + err = c.requestBody(ctx, http.MethodGet, "/api/users/"+user.ID+"/secrets/"+id, nil, &secret) return &secret, err } -func (c *Client) secretNameToID(name string) (id string, _ error) { - secrets, err := c.Secrets() +func (c *Client) secretNameToID(ctx context.Context, name string, opts *ReqOptions) (id string, _ error) { + secrets, err := c.Secrets(ctx, opts) if err != nil { return "", err } @@ -42,12 +69,12 @@ func (c *Client) secretNameToID(name string) (id string, _ error) { } // SecretByName gets a secret object by name -func (c *Client) SecretByName(name string) (*Secret, error) { - id, err := c.secretNameToID(name) +func (c *Client) SecretByName(ctx context.Context, name string, opts *ReqOptions) (*Secret, error) { + id, err := c.secretNameToID(ctx, name, opts) if err != nil { return nil, err } - return c.secretByID(id) + return c.secretByID(ctx, id, opts) } // InsertSecretReq describes the request body for creating a new secret @@ -58,18 +85,32 @@ type InsertSecretReq struct { } // InsertSecret adds a new secret for the authed user -func (c *Client) InsertSecret(req InsertSecretReq) error { +func (c *Client) InsertSecret(ctx context.Context, req InsertSecretReq, opts *ReqOptions) error { + if opts == nil { + opts = DefaultReqOptions + } + user, err := c.UserByEmail(ctx, opts.User) + if err != nil { + return err + } var resp interface{} - err := c.requestBody(http.MethodPost, "/api/users/me/secrets", req, &resp) + err = c.requestBody(ctx, http.MethodPost, "/api/users/"+user.ID+"/secrets", req, &resp) return err } // DeleteSecretByName deletes the authenticated users secret with the given name -func (c *Client) DeleteSecretByName(name string) error { - id, err := c.secretNameToID(name) +func (c *Client) DeleteSecretByName(ctx context.Context, name string, opts *ReqOptions) error { + if opts == nil { + opts = DefaultReqOptions + } + user, err := c.UserByEmail(ctx, opts.User) + if err != nil { + return err + } + id, err := c.secretNameToID(ctx, name, opts) if err != nil { return err } - _, err = c.request(http.MethodDelete, "/api/users/me/secrets/"+id, nil) + _, err = c.request(ctx, http.MethodDelete, "/api/users/"+user.ID+"/secrets/"+id, nil) return err } diff --git a/internal/entclient/users.go b/internal/entclient/users.go index 2354e568..a721d3fe 100644 --- a/internal/entclient/users.go +++ b/internal/entclient/users.go @@ -1,11 +1,30 @@ package entclient +import "context" + // Users gets the list of user accounts -func (c Client) Users() ([]User, error) { +func (c Client) Users(ctx context.Context) ([]User, error) { var u []User - err := c.requestBody("GET", "/api/users", nil, &u) + err := c.requestBody(ctx, "GET", "/api/users", nil, &u) if err != nil { return nil, err } return u, nil } + +// UserByEmail gets a user by email +func (c Client) UserByEmail(ctx context.Context, target string) (*User, error) { + if target == Me { + return c.Me(ctx) + } + users, err := c.Users(ctx) + if err != nil { + return nil, err + } + for _, u := range users { + if u.Email == target { + return &u, nil + } + } + return nil, ErrNotFound +} diff --git a/internal/sync/sync.go b/internal/sync/sync.go index b58382a1..70b4d724 100644 --- a/internal/sync/sync.go +++ b/internal/sync/sync.go @@ -322,7 +322,7 @@ func (s Sync) Run() error { s.remoteCmd(ctx, "mkdir", "-p", s.RemoteDir) ap := activity.NewPusher(s.Client, s.Env.ID, activityName) - ap.Push() + ap.Push(ctx) setConsoleTitle("⏳ syncing project") err = s.initSync() @@ -382,7 +382,7 @@ func (s Sync) Run() error { } s.workEventGroup(eventGroup) eventGroup = eventGroup[:0] - ap.Push() + ap.Push(context.TODO()) } } }