diff --git a/ci/integration/users_test.go b/ci/integration/users_test.go index e3c7e6f3..05d81c6a 100644 --- a/ci/integration/users_test.go +++ b/ci/integration/users_test.go @@ -5,7 +5,7 @@ import ( "testing" "cdr.dev/coder-cli/ci/tcli" - "cdr.dev/coder-cli/internal/entclient" + "cdr.dev/coder-cli/coder-sdk" "cdr.dev/slog/sloggers/slogtest/assert" ) @@ -20,7 +20,7 @@ func TestUsers(t *testing.T) { headlessLogin(ctx, t, c) - var user entclient.User + var user coder.User c.Run(ctx, `coder users ls --output json | jq -c '.[] | select( .username == "charlie")'`).Assert(t, tcli.Success(), tcli.StdoutJSONUnmarshal(&user), diff --git a/internal/entclient/activity.go b/coder-sdk/activity.go similarity index 66% rename from internal/entclient/activity.go rename to coder-sdk/activity.go index 8aba99d3..f5001507 100644 --- a/internal/entclient/activity.go +++ b/coder-sdk/activity.go @@ -1,13 +1,13 @@ -package entclient +package coder import ( "context" "net/http" ) -// PushActivity pushes CLI activity to Coder +// PushActivity pushes CLI activity to Coder. 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{ + res, err := c.request(ctx, http.MethodPost, "/api/metrics/usage/push", map[string]string{ "source": source, "environment_id": envID, }) @@ -18,6 +18,5 @@ func (c Client) PushActivity(ctx context.Context, source string, envID string) e if res.StatusCode != http.StatusOK { return bodyError(res) } - return nil } diff --git a/internal/entclient/client.go b/coder-sdk/client.go similarity index 97% rename from internal/entclient/client.go rename to coder-sdk/client.go index 703fcbb1..a4bcb3f2 100644 --- a/internal/entclient/client.go +++ b/coder-sdk/client.go @@ -1,4 +1,4 @@ -package entclient +package coder import ( "net/http" diff --git a/internal/entclient/devurl.go b/coder-sdk/devurl.go similarity index 88% rename from internal/entclient/devurl.go rename to coder-sdk/devurl.go index 3ed8187c..701ec004 100644 --- a/internal/entclient/devurl.go +++ b/coder-sdk/devurl.go @@ -1,4 +1,4 @@ -package entclient +package coder import ( "context" @@ -24,7 +24,7 @@ type delDevURLRequest struct { 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(ctx, "DELETE", reqURL, delDevURLRequest{ + res, err := c.request(ctx, http.MethodDelete, reqURL, delDevURLRequest{ EnvID: envID, DevURLID: urlID, }) @@ -51,7 +51,7 @@ type createDevURLRequest struct { 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(ctx, "POST", reqURL, createDevURLRequest{ + res, err := c.request(ctx, http.MethodPost, reqURL, createDevURLRequest{ EnvID: envID, Port: port, Access: access, @@ -75,7 +75,7 @@ type updateDevURLRequest createDevURLRequest 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(ctx, "PUT", reqURL, updateDevURLRequest{ + res, err := c.request(ctx, http.MethodPut, reqURL, updateDevURLRequest{ EnvID: envID, Port: port, Access: access, diff --git a/internal/entclient/env.go b/coder-sdk/env.go similarity index 83% rename from internal/entclient/env.go rename to coder-sdk/env.go index 93773f43..7f0a6573 100644 --- a/internal/entclient/env.go +++ b/coder-sdk/env.go @@ -1,7 +1,8 @@ -package entclient +package coder import ( "context" + "net/http" "time" "cdr.dev/coder-cli/internal/x/xjson" @@ -33,12 +34,12 @@ type Environment struct { AutoOffThreshold xjson.Duration `json:"auto_off_threshold" tab:"-"` } -// Envs gets the list of environments owned by the authenticated user -func (c Client) Envs(ctx context.Context, user *User, org Org) ([]Environment, error) { +// EnvironmentsByOrganization gets the list of environments owned by the given user. +func (c Client) EnvironmentsByOrganization(ctx context.Context, userID, orgID string) ([]Environment, error) { var envs []Environment err := c.requestBody( ctx, - "GET", "/api/orgs/"+org.ID+"/members/"+user.ID+"/environments", + http.MethodGet, "/api/orgs/"+orgID+"/members/"+userID+"/environments", nil, &envs, ) @@ -47,7 +48,7 @@ func (c Client) Envs(ctx context.Context, user *User, org Org) ([]Environment, e // DialWsep dials an environments command execution interface // See github.com/cdr/wsep for details -func (c Client) DialWsep(ctx context.Context, env Environment) (*websocket.Conn, error) { +func (c Client) DialWsep(ctx context.Context, env *Environment) (*websocket.Conn, error) { u := c.copyURL() if c.BaseURL.Scheme == "https" { u.Scheme = "wss" diff --git a/internal/entclient/error.go b/coder-sdk/error.go similarity index 97% rename from internal/entclient/error.go rename to coder-sdk/error.go index 9170efa9..aa1adbd2 100644 --- a/internal/entclient/error.go +++ b/coder-sdk/error.go @@ -1,4 +1,4 @@ -package entclient +package coder import ( "encoding/json" diff --git a/internal/entclient/org.go b/coder-sdk/org.go similarity index 70% rename from internal/entclient/org.go rename to coder-sdk/org.go index 868eeba8..260e6321 100644 --- a/internal/entclient/org.go +++ b/coder-sdk/org.go @@ -1,6 +1,9 @@ -package entclient +package coder -import "context" +import ( + "context" + "net/http" +) // Org describes an Organization in Coder type Org struct { @@ -12,6 +15,6 @@ type Org struct { // Orgs gets all Organizations func (c Client) Orgs(ctx context.Context) ([]Org, error) { var os []Org - err := c.requestBody(ctx, "GET", "/api/orgs", nil, &os) + err := c.requestBody(ctx, http.MethodGet, "/api/orgs", nil, &os) return os, err } diff --git a/internal/entclient/request.go b/coder-sdk/request.go similarity index 98% rename from internal/entclient/request.go rename to coder-sdk/request.go index 507df424..fba5f8ab 100644 --- a/internal/entclient/request.go +++ b/coder-sdk/request.go @@ -1,4 +1,4 @@ -package entclient +package coder import ( "bytes" diff --git a/coder-sdk/secrets.go b/coder-sdk/secrets.go new file mode 100644 index 00000000..b13a3060 --- /dev/null +++ b/coder-sdk/secrets.go @@ -0,0 +1,79 @@ +package coder + +import ( + "context" + "net/http" + "time" +) + +// Secret describes a Coder secret +type Secret struct { + ID string `json:"id" tab:"-"` + Name string `json:"name"` + Value string `json:"value,omitempty"` + Description string `json:"description"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at" tab:"-"` +} + +// Secrets gets all secrets for the given user +func (c *Client) Secrets(ctx context.Context, userID string) ([]Secret, error) { + var secrets []Secret + err := c.requestBody(ctx, http.MethodGet, "/api/users/"+userID+"/secrets", nil, &secrets) + return secrets, err +} + +// SecretWithValueByName gets the Coder secret with its value by its name. +func (c *Client) SecretWithValueByName(ctx context.Context, name, userID string) (*Secret, error) { + s, err := c.SecretByName(ctx, name, userID) + if err != nil { + return nil, err + } + var secret Secret + err = c.requestBody(ctx, http.MethodGet, "/api/users/"+userID+"/secrets/"+s.ID, nil, &secret) + return &secret, err +} + +// SecretWithValueByID gets the Coder secret with its value by the secret_id. +func (c *Client) SecretWithValueByID(ctx context.Context, id, userID string) (*Secret, error) { + var secret Secret + err := c.requestBody(ctx, http.MethodGet, "/api/users/"+userID+"/secrets/"+id, nil, &secret) + return &secret, err +} + +// SecretByName gets a secret object by name +func (c *Client) SecretByName(ctx context.Context, name, userID string) (*Secret, error) { + secrets, err := c.Secrets(ctx, userID) + if err != nil { + return nil, err + } + for _, s := range secrets { + if s.Name == name { + return &s, nil + } + } + return nil, ErrNotFound +} + +// InsertSecretReq describes the request body for creating a new secret +type InsertSecretReq struct { + Name string `json:"name"` + Value string `json:"value"` + Description string `json:"description"` +} + +// InsertSecret adds a new secret for the authed user +func (c *Client) InsertSecret(ctx context.Context, user *User, req InsertSecretReq) error { + var resp interface{} + return c.requestBody(ctx, http.MethodPost, "/api/users/"+user.ID+"/secrets", req, &resp) +} + +// DeleteSecretByName deletes the authenticated users secret with the given name +func (c *Client) DeleteSecretByName(ctx context.Context, name, userID string) error { + secret, err := c.SecretByName(ctx, name, userID) + if err != nil { + return err + } + _, err = c.request(ctx, http.MethodDelete, "/api/users/"+userID+"/secrets/"+secret.ID, nil) + return err +} diff --git a/coder-sdk/users.go b/coder-sdk/users.go new file mode 100644 index 00000000..48aa0502 --- /dev/null +++ b/coder-sdk/users.go @@ -0,0 +1,75 @@ +package coder + +import ( + "context" + "net/http" + "time" +) + +// User describes a Coder user account. +type User struct { + ID string `json:"id" tab:"-"` + Email string `json:"email"` + Username string `json:"username"` + Name string `json:"name"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at" tab:"-"` +} + +// Me gets the details of the authenticated user. +func (c Client) Me(ctx context.Context) (*User, error) { + return c.UserByID(ctx, Me) +} + +// UserByID get the details of a user by their id. +func (c Client) UserByID(ctx context.Context, id string) (*User, error) { + var u User + err := c.requestBody(ctx, http.MethodGet, "/api/users/"+id, nil, &u) + if err != nil { + return nil, err + } + return &u, nil +} + +// SSHKey describes an SSH keypair +type SSHKey struct { + PublicKey string `json:"public_key"` + PrivateKey string `json:"private_key"` +} + +// SSHKey gets the current SSH kepair of the authenticated user. +func (c Client) SSHKey(ctx context.Context) (*SSHKey, error) { + var key SSHKey + err := c.requestBody(ctx, http.MethodGet, "/api/users/me/sshkey", nil, &key) + if err != nil { + return nil, err + } + return &key, nil +} + +// Users gets the list of user accounts. +func (c Client) Users(ctx context.Context) ([]User, error) { + var u []User + err := c.requestBody(ctx, http.MethodGet, "/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, email string) (*User, error) { + if email == Me { + return c.Me(ctx) + } + users, err := c.Users(ctx) + if err != nil { + return nil, err + } + for _, u := range users { + if u.Email == email { + return &u, nil + } + } + return nil, ErrNotFound +} diff --git a/internal/activity/pusher.go b/internal/activity/pusher.go index d0f5954c..5efa15d1 100644 --- a/internal/activity/pusher.go +++ b/internal/activity/pusher.go @@ -4,7 +4,7 @@ import ( "context" "time" - "cdr.dev/coder-cli/internal/entclient" + "cdr.dev/coder-cli/coder-sdk" "golang.org/x/time/rate" "go.coder.com/flog" @@ -18,12 +18,12 @@ type Pusher struct { envID string source string - client *entclient.Client + client *coder.Client rate *rate.Limiter } // NewPusher instantiates a new instance of Pusher -func NewPusher(c *entclient.Client, envID, source string) *Pusher { +func NewPusher(c *coder.Client, envID, source string) *Pusher { return &Pusher{ envID: envID, source: source, diff --git a/internal/cmd/auth.go b/internal/cmd/auth.go index 64bcfb23..bfe00a3e 100644 --- a/internal/cmd/auth.go +++ b/internal/cmd/auth.go @@ -3,15 +3,15 @@ package cmd import ( "net/url" + "cdr.dev/coder-cli/coder-sdk" "cdr.dev/coder-cli/internal/config" - "cdr.dev/coder-cli/internal/entclient" "golang.org/x/xerrors" "go.coder.com/flog" ) // requireAuth exits the process with a nonzero exit code if the user is not authenticated to make requests -func requireAuth() *entclient.Client { +func requireAuth() *coder.Client { client, err := newClient() if err != nil { flog.Fatal("%v", err) @@ -19,7 +19,7 @@ func requireAuth() *entclient.Client { return client } -func newClient() (*entclient.Client, error) { +func newClient() (*coder.Client, error) { sessionToken, err := config.Session.Read() if err != nil { return nil, xerrors.Errorf("read session: %v (did you run coder login?)", err) @@ -35,7 +35,7 @@ func newClient() (*entclient.Client, error) { return nil, xerrors.Errorf("url misformatted: %v (try runing coder login)", err) } - client := &entclient.Client{ + client := &coder.Client{ BaseURL: u, Token: sessionToken, } diff --git a/internal/cmd/ceapi.go b/internal/cmd/ceapi.go index b69788fc..d9d8eb7e 100644 --- a/internal/cmd/ceapi.go +++ b/internal/cmd/ceapi.go @@ -3,18 +3,17 @@ package cmd import ( "context" + "cdr.dev/coder-cli/coder-sdk" "golang.org/x/xerrors" "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 +func userOrgs(user *coder.User, orgs []coder.Org) []coder.Org { + var uo []coder.Org outer: for _, org := range orgs { for _, member := range org.Members { @@ -29,7 +28,7 @@ outer: } // getEnvs returns all environments for the user. -func getEnvs(ctx context.Context, client *entclient.Client, email string) ([]entclient.Environment, error) { +func getEnvs(ctx context.Context, client *coder.Client, email string) ([]coder.Environment, error) { user, err := client.UserByEmail(ctx, email) if err != nil { return nil, xerrors.Errorf("get user: %+v", err) @@ -42,10 +41,10 @@ func getEnvs(ctx context.Context, client *entclient.Client, email string) ([]ent orgs = userOrgs(user, orgs) - var allEnvs []entclient.Environment + var allEnvs []coder.Environment for _, org := range orgs { - envs, err := client.Envs(ctx, user, org) + envs, err := client.EnvironmentsByOrganization(ctx, user.ID, org.ID) if err != nil { return nil, xerrors.Errorf("get envs for %v: %+v", org.Name, err) } @@ -58,7 +57,7 @@ func getEnvs(ctx context.Context, client *entclient.Client, email string) ([]ent } // findEnv returns a single environment by name (if it exists.) -func findEnv(ctx context.Context, client *entclient.Client, envName, userEmail string) (*entclient.Environment, error) { +func findEnv(ctx context.Context, client *coder.Client, envName, userEmail string) (*coder.Environment, error) { envs, err := getEnvs(ctx, client, userEmail) if err != nil { return nil, xerrors.Errorf("get environments: %w", err) diff --git a/internal/cmd/configssh.go b/internal/cmd/configssh.go index 1d1c3c7b..5023ea7e 100644 --- a/internal/cmd/configssh.go +++ b/internal/cmd/configssh.go @@ -12,8 +12,8 @@ import ( "strings" "time" + "cdr.dev/coder-cli/coder-sdk" "cdr.dev/coder-cli/internal/config" - "cdr.dev/coder-cli/internal/entclient" "github.com/spf13/cobra" "golang.org/x/xerrors" ) @@ -85,19 +85,19 @@ func configSSH(configpath *string, remove *bool) func(cmd *cobra.Command, _ []st return nil } - entClient := requireAuth() + client := requireAuth() sshAvailable := isSSHAvailable(ctx) if !sshAvailable { return xerrors.New("SSH is disabled or not available for your Coder Enterprise deployment.") } - user, err := entClient.Me(cmd.Context()) + user, err := client.Me(cmd.Context()) if err != nil { return xerrors.Errorf("fetch username: %w", err) } - envs, err := getEnvs(cmd.Context(), entClient, entclient.Me) + envs, err := getEnvs(cmd.Context(), client, coder.Me) if err != nil { return err } @@ -122,7 +122,7 @@ func configSSH(configpath *string, remove *bool) func(cmd *cobra.Command, _ []st if err != nil { return xerrors.Errorf("write new configurations to ssh config file %q: %w", *configpath, err) } - err = writeSSHKey(ctx, entClient) + err = writeSSHKey(ctx, client) if err != nil { return xerrors.Errorf("fetch and write ssh key: %w", err) } @@ -139,7 +139,7 @@ var ( privateKeyFilepath = filepath.Join(os.Getenv("HOME"), ".ssh", "coder_enterprise") ) -func writeSSHKey(ctx context.Context, client *entclient.Client) error { +func writeSSHKey(ctx context.Context, client *coder.Client) error { key, err := client.SSHKey(ctx) if err != nil { return err @@ -147,7 +147,7 @@ func writeSSHKey(ctx context.Context, client *entclient.Client) error { return ioutil.WriteFile(privateKeyFilepath, []byte(key.PrivateKey), 0400) } -func makeNewConfigs(userName string, envs []entclient.Environment, startToken, startMsg, endToken string) (string, error) { +func makeNewConfigs(userName string, envs []coder.Environment, startToken, startMsg, endToken string) (string, error) { hostname, err := configuredHostname() if err != nil { return "", nil diff --git a/internal/cmd/envs.go b/internal/cmd/envs.go index 4e3219b7..b5f3eb6d 100644 --- a/internal/cmd/envs.go +++ b/internal/cmd/envs.go @@ -4,7 +4,7 @@ import ( "encoding/json" "os" - "cdr.dev/coder-cli/internal/entclient" + "cdr.dev/coder-cli/coder-sdk" "cdr.dev/coder-cli/internal/x/xtabwriter" "github.com/spf13/cobra" "golang.org/x/xerrors" @@ -20,7 +20,7 @@ func makeEnvsCommand() *cobra.Command { 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") + cmd.PersistentFlags().StringVar(&user, "user", coder.Me, "Specify the user whose resources to target") lsCmd := &cobra.Command{ Use: "ls", diff --git a/internal/cmd/secrets.go b/internal/cmd/secrets.go index 7303d8fa..9dd660e5 100644 --- a/internal/cmd/secrets.go +++ b/internal/cmd/secrets.go @@ -5,7 +5,7 @@ import ( "io/ioutil" "os" - "cdr.dev/coder-cli/internal/entclient" + "cdr.dev/coder-cli/coder-sdk" "cdr.dev/coder-cli/internal/x/xtabwriter" "github.com/manifoldco/promptui" "github.com/spf13/cobra" @@ -21,7 +21,7 @@ func makeSecretsCmd() *cobra.Command { 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.PersistentFlags().StringVar(&user, "user", coder.Me, "Specify the user whose resources to target") cmd.AddCommand( &cobra.Command{ Use: "ls", @@ -47,7 +47,7 @@ func makeSecretsCmd() *cobra.Command { return cmd } -func makeCreateSecret(user *string) *cobra.Command { +func makeCreateSecret(userEmail *string) *cobra.Command { var ( fromFile string fromLiteral string @@ -109,12 +109,14 @@ coder secrets create aws-credentials --from-file ./credentials.json`, } } - err = client.InsertSecret(cmd.Context(), entclient.InsertSecretReq{ + user, err := client.UserByEmail(cmd.Context(), *userEmail) + if err != nil { + return xerrors.Errorf("get user %q by email: %w", *userEmail, err) + } + err = client.InsertSecret(cmd.Context(), user, coder.InsertSecretReq{ Name: name, Value: value, Description: description, - }, &entclient.ReqOptions{ - User: *user, }) if err != nil { return xerrors.Errorf("insert secret: %w", err) @@ -131,13 +133,15 @@ coder secrets create aws-credentials --from-file ./credentials.json`, return cmd } -func listSecrets(user *string) func(cmd *cobra.Command, _ []string) error { +func listSecrets(userEmail *string) func(cmd *cobra.Command, _ []string) error { return func(cmd *cobra.Command, _ []string) error { client := requireAuth() + user, err := client.UserByEmail(cmd.Context(), *userEmail) + if err != nil { + return xerrors.Errorf("get user %q by email: %w", *userEmail, err) + } - secrets, err := client.Secrets(cmd.Context(), &entclient.ReqOptions{ - User: *user, - }) + secrets, err := client.Secrets(cmd.Context(), user.ID) if err != nil { return xerrors.Errorf("get secrets: %w", err) } @@ -159,19 +163,18 @@ func listSecrets(user *string) func(cmd *cobra.Command, _ []string) error { } } -func makeViewSecret(user *string) func(cmd *cobra.Command, args []string) error { +func makeViewSecret(userEmail *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") + user, err := client.UserByEmail(cmd.Context(), *userEmail) + if err != nil { + return xerrors.Errorf("get user %q by email: %w", *userEmail, err) } - secret, err := client.SecretByName(cmd.Context(), name, &entclient.ReqOptions{ - User: *user, - }) + secret, err := client.SecretWithValueByName(cmd.Context(), name, user.ID) if err != nil { return xerrors.Errorf("get secret by name: %w", err) } @@ -184,20 +187,19 @@ func makeViewSecret(user *string) func(cmd *cobra.Command, args []string) error } } -func makeRemoveSecrets(user *string) func(c *cobra.Command, args []string) error { +func makeRemoveSecrets(userEmail *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") + user, err := client.UserByEmail(cmd.Context(), *userEmail) + if err != nil { + return xerrors.Errorf("get user %q by email: %w", *userEmail, err) } errorSeen := false for _, n := range args { - err := client.DeleteSecretByName(cmd.Context(), n, &entclient.ReqOptions{ - User: *user, - }) + err := client.DeleteSecretByName(cmd.Context(), n, user.ID) if err != nil { flog.Error("failed to delete secret %q: %v", n, err) errorSeen = true diff --git a/internal/cmd/shell.go b/internal/cmd/shell.go index 7cfee8b0..62accce2 100644 --- a/internal/cmd/shell.go +++ b/internal/cmd/shell.go @@ -7,8 +7,8 @@ import ( "strings" "time" + "cdr.dev/coder-cli/coder-sdk" "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" @@ -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, - ValidArgsFunction: getEnvsForCompletion(entclient.Me), + ValidArgsFunction: getEnvsForCompletion(coder.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(ctx, entClient, envName, entclient.Me) + env, err := findEnv(ctx, entClient, envName, coder.Me) if err != nil { return err } @@ -118,7 +118,7 @@ func runCommand(ctx context.Context, envName string, command string, args []stri ctx, cancel := context.WithCancel(ctx) defer cancel() - conn, err := entClient.DialWsep(ctx, *env) + conn, err := entClient.DialWsep(ctx, env) if err != nil { return err } diff --git a/internal/cmd/sync.go b/internal/cmd/sync.go index c41d06fd..7507d76a 100644 --- a/internal/cmd/sync.go +++ b/internal/cmd/sync.go @@ -8,7 +8,7 @@ import ( "path/filepath" "strings" - "cdr.dev/coder-cli/internal/entclient" + "cdr.dev/coder-cli/coder-sdk" "cdr.dev/coder-cli/internal/sync" "github.com/spf13/cobra" "golang.org/x/xerrors" @@ -71,7 +71,7 @@ func makeRunSync(init *bool) func(cmd *cobra.Command, args []string) error { remoteDir = remoteTokens[1] ) - env, err := findEnv(cmd.Context(), entClient, envName, entclient.Me) + env, err := findEnv(cmd.Context(), entClient, envName, coder.Me) if err != nil { return err } diff --git a/internal/cmd/urls.go b/internal/cmd/urls.go index ba6e5494..b406c947 100644 --- a/internal/cmd/urls.go +++ b/internal/cmd/urls.go @@ -10,7 +10,7 @@ import ( "strconv" "strings" - "cdr.dev/coder-cli/internal/entclient" + "cdr.dev/coder-cli/coder-sdk" "cdr.dev/coder-cli/internal/x/xtabwriter" "github.com/spf13/cobra" "golang.org/x/xerrors" @@ -28,7 +28,7 @@ func makeURLCmd() *cobra.Command { Use: "ls [environment_name]", Short: "List all DevURLs for an environment", Args: cobra.ExactArgs(1), - ValidArgsFunction: getEnvsForCompletion(entclient.Me), + ValidArgsFunction: getEnvsForCompletion(coder.Me), RunE: makeListDevURLs(&outputFmt), } lsCmd.Flags().StringVarP(&outputFmt, "output", "o", "human", "human|json") @@ -155,7 +155,7 @@ func makeCreateDevURL() *cobra.Command { } entClient := requireAuth() - env, err := findEnv(cmd.Context(), entClient, envName, entclient.Me) + env, err := findEnv(cmd.Context(), entClient, envName, coder.Me) if err != nil { return err } @@ -220,7 +220,7 @@ func removeDevURL(cmd *cobra.Command, args []string) error { } entClient := requireAuth() - env, err := findEnv(cmd.Context(), entClient, envName, entclient.Me) + env, err := findEnv(cmd.Context(), entClient, envName, coder.Me) if err != nil { return err } @@ -247,7 +247,7 @@ func removeDevURL(cmd *cobra.Command, args []string) error { // urlList returns the list of active devURLs from the cemanager. func urlList(ctx context.Context, envName string) ([]DevURL, error) { entClient := requireAuth() - env, err := findEnv(ctx, entClient, envName, entclient.Me) + env, err := findEnv(ctx, entClient, envName, coder.Me) if err != nil { return nil, err } diff --git a/internal/entclient/me.go b/internal/entclient/me.go deleted file mode 100644 index 5a70bd2f..00000000 --- a/internal/entclient/me.go +++ /dev/null @@ -1,42 +0,0 @@ -package entclient - -import ( - "context" - "time" -) - -// User describes a Coder user account -type User struct { - ID string `json:"id" tab:"-"` - Email string `json:"email"` - Username string `json:"username"` - Name string `json:"name"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at" tab:"-"` -} - -// Me gets the details of the authenticated user. -func (c Client) Me(ctx context.Context) (*User, error) { - var u User - err := c.requestBody(ctx, "GET", "/api/users/me", nil, &u) - if err != nil { - return nil, err - } - return &u, nil -} - -// SSHKey describes an SSH keypair -type SSHKey struct { - PublicKey string `json:"public_key"` - PrivateKey string `json:"private_key"` -} - -// SSHKey gets the current SSH kepair of the authenticated user -func (c Client) SSHKey(ctx context.Context) (*SSHKey, error) { - var key SSHKey - err := c.requestBody(ctx, "GET", "/api/users/me/sshkey", nil, &key) - if err != nil { - return nil, err - } - return &key, nil -} diff --git a/internal/entclient/secrets.go b/internal/entclient/secrets.go deleted file mode 100644 index d1ad25bf..00000000 --- a/internal/entclient/secrets.go +++ /dev/null @@ -1,116 +0,0 @@ -package entclient - -import ( - "context" - "net/http" - "time" -) - -// Secret describes a Coder secret -type Secret struct { - ID string `json:"id" tab:"-"` - Name string `json:"name"` - Value string `json:"value,omitempty"` - Description string `json:"description"` - CreatedAt time.Time `json:"created_at"` - 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(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(ctx, http.MethodGet, "/api/users/"+user.ID+"/secrets", nil, &secrets) - return secrets, err -} - -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(ctx, http.MethodGet, "/api/users/"+user.ID+"/secrets/"+id, nil, &secret) - return &secret, err -} - -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 - } - for _, s := range secrets { - if s.Name == name { - return s.ID, nil - } - } - return "", ErrNotFound -} - -// SecretByName gets a secret object by 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(ctx, id, opts) -} - -// InsertSecretReq describes the request body for creating a new secret -type InsertSecretReq struct { - Name string `json:"name"` - Value string `json:"value"` - Description string `json:"description"` -} - -// InsertSecret adds a new secret for the authed user -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(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(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(ctx, http.MethodDelete, "/api/users/"+user.ID+"/secrets/"+id, nil) - return err -} diff --git a/internal/entclient/users.go b/internal/entclient/users.go deleted file mode 100644 index a721d3fe..00000000 --- a/internal/entclient/users.go +++ /dev/null @@ -1,30 +0,0 @@ -package entclient - -import "context" - -// Users gets the list of user accounts -func (c Client) Users(ctx context.Context) ([]User, error) { - var u []User - 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 70b4d724..6e5b6c49 100644 --- a/internal/sync/sync.go +++ b/internal/sync/sync.go @@ -16,6 +16,7 @@ import ( "sync/atomic" "time" + "cdr.dev/coder-cli/coder-sdk" "github.com/gorilla/websocket" "github.com/rjeczalik/notify" "golang.org/x/sync/semaphore" @@ -24,7 +25,6 @@ import ( "go.coder.com/flog" "cdr.dev/coder-cli/internal/activity" - "cdr.dev/coder-cli/internal/entclient" "cdr.dev/wsep" ) @@ -39,8 +39,8 @@ type Sync struct { // DisableMetrics disables activity metric pushing. DisableMetrics bool - Env entclient.Environment - Client *entclient.Client + Env coder.Environment + Client *coder.Client } // See https://lxadm.com/Rsync_exit_codes#List_of_standard_rsync_exit_codes. @@ -89,7 +89,7 @@ func (s Sync) syncPaths(delete bool, local, remote string) error { } func (s Sync) remoteCmd(ctx context.Context, prog string, args ...string) error { - conn, err := s.Client.DialWsep(ctx, s.Env) + conn, err := s.Client.DialWsep(ctx, &s.Env) if err != nil { return err } @@ -270,7 +270,7 @@ func (s Sync) Version() (string, error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() - conn, err := s.Client.DialWsep(ctx, s.Env) + conn, err := s.Client.DialWsep(ctx, &s.Env) if err != nil { return "", err }