diff --git a/ci/integration/secrets_test.go b/ci/integration/secrets_test.go deleted file mode 100644 index ffba0b25..00000000 --- a/ci/integration/secrets_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package integration - -import ( - "context" - "fmt" - "regexp" - "testing" - - "cdr.dev/coder-cli/pkg/tcli" -) - -func TestSecrets(t *testing.T) { - t.Parallel() - run(t, "secrets-cli-tests", func(t *testing.T, ctx context.Context, c *tcli.ContainerRunner) { - t.Skip() - headlessLogin(ctx, t, c) - - c.Run(ctx, "coder secrets ls").Assert(t, - tcli.Success(), - ) - - name, value := randString(8), randString(8) - - c.Run(ctx, "coder secrets create").Assert(t, - tcli.Error(), - ) - - // this tests the "Value:" prompt fallback - c.Run(ctx, fmt.Sprintf("echo %s | coder secrets create %s --from-prompt", value, name)).Assert(t, - tcli.Success(), - tcli.StderrEmpty(), - ) - - c.Run(ctx, "coder secrets ls").Assert(t, - tcli.Success(), - tcli.StderrEmpty(), - tcli.StdoutMatches("Value"), - tcli.StdoutMatches(regexp.QuoteMeta(name)), - ) - - c.Run(ctx, "coder secrets view "+name).Assert(t, - tcli.Success(), - tcli.StderrEmpty(), - tcli.StdoutMatches(regexp.QuoteMeta(value)), - ) - - c.Run(ctx, "coder secrets rm").Assert(t, - tcli.Error(), - ) - c.Run(ctx, "coder secrets rm "+name).Assert(t, - tcli.Success(), - ) - c.Run(ctx, "coder secrets view "+name).Assert(t, - tcli.Error(), - tcli.StdoutEmpty(), - ) - - name, value = randString(8), randString(8) - - c.Run(ctx, fmt.Sprintf("coder secrets create %s --from-literal %s", name, value)).Assert(t, - tcli.Success(), - tcli.StderrEmpty(), - ) - - c.Run(ctx, "coder secrets view "+name).Assert(t, - tcli.Success(), - tcli.StdoutMatches(regexp.QuoteMeta(value)), - ) - - name, value = randString(8), randString(8) - c.Run(ctx, fmt.Sprintf("echo %s > ~/secret.json", value)).Assert(t, - tcli.Success(), - ) - c.Run(ctx, fmt.Sprintf("coder secrets create %s --from-file ~/secret.json", name)).Assert(t, - tcli.Success(), - ) - c.Run(ctx, "coder secrets view "+name).Assert(t, - tcli.Success(), - tcli.StdoutMatches(regexp.QuoteMeta(value)), - ) - }) -} diff --git a/coder-sdk/secrets.go b/coder-sdk/secrets.go deleted file mode 100644 index 55124bc0..00000000 --- a/coder-sdk/secrets.go +++ /dev/null @@ -1,112 +0,0 @@ -package coder - -import ( - "context" - "net/http" - "time" -) - -// Secret describes a Coder secret. -// -// Deprecated: Coder Secrets will be removed from Coder Enterprise in a future release. -type Secret struct { - ID string `json:"id" table:"-"` - Name string `json:"name" table:"Name"` - Value string `json:"value,omitempty" table:"Value"` - Description string `json:"description" table:"Description"` - CreatedAt time.Time `json:"created_at" table:"CreatedAt"` - UpdatedAt time.Time `json:"updated_at" table:"-"` -} - -// Secrets gets all secrets for the given user. -// -// Deprecated: Coder Secrets will be removed from Coder Enterprise in a future release. -func (c Client) Secrets(ctx context.Context, userID string) ([]Secret, error) { - var secrets []Secret - if err := c.requestBody(ctx, http.MethodGet, "/api/private/users/"+userID+"/secrets", nil, &secrets); err != nil { - return nil, err - } - return secrets, nil -} - -// SecretWithValueByName gets the Coder secret with its value by its name. -// -// Deprecated: Coder Secrets will be removed from Coder Enterprise in a future release. -func (c Client) SecretWithValueByName(ctx context.Context, name, userID string) (*Secret, error) { - // Lookup the secret from the name. - s, err := c.SecretByName(ctx, name, userID) - if err != nil { - return nil, err - } - // Pull the secret value. - // NOTE: This is racy, but acceptable. If the secret is gone or the permission changed since we looked up the id, - // the call will simply fail and surface the error to the user. - var secret Secret - if err := c.requestBody(ctx, http.MethodGet, "/api/private/users/"+userID+"/secrets/"+s.ID, nil, &secret); err != nil { - return nil, err - } - return &secret, nil -} - -// SecretWithValueByID gets the Coder secret with its value by the secret_id. -// -// Deprecated: Coder Secrets will be removed from Coder Enterprise in a future release. -func (c Client) SecretWithValueByID(ctx context.Context, id, userID string) (*Secret, error) { - var secret Secret - if err := c.requestBody(ctx, http.MethodGet, "/api/private/users/"+userID+"/secrets/"+id, nil, &secret); err != nil { - return nil, err - } - return &secret, nil -} - -// SecretByName gets a secret object by name. -// -// Deprecated: Coder Secrets will be removed from Coder Enterprise in a future release. -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. -// -// Deprecated: Coder Secrets will be removed from Coder Enterprise in a future release. -type InsertSecretReq struct { - Name string `json:"name"` - Value string `json:"value"` - Description string `json:"description"` -} - -// InsertSecret adds a new secret for the authed user. -// -// Deprecated: Coder Secrets will be removed from Coder Enterprise in a future release. -func (c Client) InsertSecret(ctx context.Context, userID string, req InsertSecretReq) error { - return c.requestBody(ctx, http.MethodPost, "/api/private/users/"+userID+"/secrets", req, nil) -} - -// DeleteSecretByName deletes the authenticated users secret with the given name. -// -// Deprecated: Coder Secrets will be removed from Coder Enterprise in a future release. -func (c Client) DeleteSecretByName(ctx context.Context, name, userID string) error { - // Lookup the secret by name to get the ID. - secret, err := c.SecretByName(ctx, name, userID) - if err != nil { - return err - } - // Delete the secret. - // NOTE: This is racy, but acceptable. If the secret is gone or the permission changed since we looked up the id, - // the call will simply fail and surface the error to the user. - resp, err := c.request(ctx, http.MethodDelete, "/api/private/users/"+userID+"/secrets/"+secret.ID, nil) - if err != nil { - return err - } - defer resp.Body.Close() - return nil -} diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index 433266f8..4e78c477 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -28,7 +28,6 @@ func Make() *cobra.Command { usersCmd(), tagsCmd(), configSSHCmd(), - secretsCmd(), envsCmd(), syncCmd(), urlCmd(), diff --git a/internal/cmd/secrets.go b/internal/cmd/secrets.go deleted file mode 100644 index 4a8d7468..00000000 --- a/internal/cmd/secrets.go +++ /dev/null @@ -1,246 +0,0 @@ -package cmd - -import ( - "fmt" - "io/ioutil" - "os" - - "github.com/manifoldco/promptui" - "github.com/spf13/cobra" - "golang.org/x/xerrors" - - "cdr.dev/coder-cli/coder-sdk" - "cdr.dev/coder-cli/pkg/clog" - "cdr.dev/coder-cli/pkg/tablewriter" -) - -func secretsCmd() *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.", - Hidden: true, - PersistentPreRun: func(cmd *cobra.Command, args []string) { - clog.LogWarn( - "The 'secrets' command is now deprecated", - "It will be removed in the next minor release", - ) - }, - Run: func(cmd *cobra.Command, args []string) { - _ = cmd.Help() - }, - } - cmd.PersistentFlags().StringVar(&user, "user", coder.Me, "Specify the user whose resources to target") - cmd.AddCommand( - &cobra.Command{ - Use: "ls", - Short: "List all secrets owned by the active user", - RunE: listSecretsCmd(&user), - }, - createSecretCmd(&user), - &cobra.Command{ - Use: "rm [...secret_name]", - Short: "Remove one or more secrets by name", - Args: cobra.MinimumNArgs(1), - RunE: removeSecretsCmd(&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: viewSecretCmd(&user), - Example: "coder secrets view mysql-password", - }, - ) - return cmd -} - -func createSecretCmd(userEmail *string) *cobra.Command { - var ( - fromFile string - fromLiteral string - fromPrompt bool - description string - ) - - cmd := &cobra.Command{ - Use: "create [secret_name]", - Short: "Create a new secret", - Long: "Create a new secret object to store application secrets and access them securely from within your environments.", - Example: `coder secrets create mysql-password --from-literal 123password -coder secrets create mysql-password --from-prompt -coder secrets create aws-credentials --from-file ./credentials.json`, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return xerrors.Errorf("[secret_name] is a required argument") - } - if fromPrompt && (fromLiteral != "" || fromFile != "") { - return xerrors.Errorf("--from-prompt cannot be set along with --from-file or --from-literal") - } - if fromLiteral != "" && fromFile != "" { - return xerrors.Errorf("--from-literal and --from-file cannot both be set") - } - if !fromPrompt && fromFile == "" && fromLiteral == "" { - return xerrors.Errorf("one of [--from-literal, --from-file, --from-prompt] is required") - } - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - var ( - name = args[0] - value string - err error - ctx = cmd.Context() - ) - client, err := newClient(ctx) - if err != nil { - return err - } - switch { - case fromLiteral != "": - value = fromLiteral - case fromFile != "": - contents, err := ioutil.ReadFile(fromFile) - if err != nil { - return xerrors.Errorf("read file: %w", err) - } - value = string(contents) - default: - prompt := promptui.Prompt{ - Label: "value", - Mask: '*', - Validate: func(s string) error { - if len(s) < 1 { - return xerrors.Errorf("a length > 0 is required") - } - return nil - }, - } - value, err = prompt.Run() - if err != nil { - return xerrors.Errorf("prompt for value: %w", err) - } - } - - user, err := client.UserByEmail(ctx, *userEmail) - if err != nil { - return xerrors.Errorf("get user %q by email: %w", *userEmail, err) - } - //nolint:staticcheck - err = client.InsertSecret(ctx, user.ID, coder.InsertSecretReq{ - Name: name, - Value: value, - Description: description, - }) - if err != nil { - return xerrors.Errorf("insert secret: %w", err) - } - return nil - }, - } - - cmd.Flags().StringVar(&fromFile, "from-file", "", "a file from which to read the value of the secret") - cmd.Flags().StringVar(&fromLiteral, "from-literal", "", "the value of the secret") - cmd.Flags().BoolVar(&fromPrompt, "from-prompt", false, "enter the secret value through a terminal prompt") - cmd.Flags().StringVar(&description, "description", "", "a description of the secret") - - return cmd -} - -func listSecretsCmd(userEmail *string) func(cmd *cobra.Command, _ []string) error { - return func(cmd *cobra.Command, _ []string) error { - ctx := cmd.Context() - client, err := newClient(ctx) - if err != nil { - return err - } - user, err := client.UserByEmail(ctx, *userEmail) - if err != nil { - return xerrors.Errorf("get user %q by email: %w", *userEmail, err) - } - - //nolint:staticcheck - secrets, err := client.Secrets(ctx, user.ID) - if err != nil { - return xerrors.Errorf("get secrets: %w", err) - } - - if len(secrets) < 1 { - clog.LogInfo("no secrets found") - return nil - } - - err = tablewriter.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 - } -} - -func viewSecretCmd(userEmail *string) func(cmd *cobra.Command, args []string) error { - return func(cmd *cobra.Command, args []string) error { - var ( - name = args[0] - ctx = cmd.Context() - ) - client, err := newClient(ctx) - if err != nil { - return err - } - user, err := client.UserByEmail(ctx, *userEmail) - if err != nil { - return xerrors.Errorf("get user %q by email: %w", *userEmail, err) - } - - //nolint:staticcheck - secret, err := client.SecretWithValueByName(ctx, name, user.ID) - 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) - } - return nil - } -} - -func removeSecretsCmd(userEmail *string) func(c *cobra.Command, args []string) error { - return func(cmd *cobra.Command, args []string) error { - ctx := cmd.Context() - client, err := newClient(ctx) - if err != nil { - return err - } - user, err := client.UserByEmail(ctx, *userEmail) - if err != nil { - return xerrors.Errorf("get user %q by email: %w", *userEmail, err) - } - - errorSeen := false - for _, n := range args { - //nolint:staticcheck - err := client.DeleteSecretByName(ctx, n, user.ID) - if err != nil { - clog.Log(clog.Error( - fmt.Sprintf("failed to delete secret %q: %v", n, err), - )) - errorSeen = true - } else { - clog.LogSuccess("successfully deleted secret: %q", n) - } - } - if errorSeen { - os.Exit(1) - } - return nil - } -}