From 83e3868cba77b7a2b160c0e356a4a3bf936be09b Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Tue, 9 Jul 2024 20:02:11 +0000 Subject: [PATCH 1/9] add api routes --- coderd/coderd.go | 10 +++ coderd/notifications/manager_test.go | 2 + coderd/provisionerkeys.go | 92 ++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+) create mode 100644 coderd/provisionerkeys.go diff --git a/coderd/coderd.go b/coderd/coderd.go index 0a3414fdb984c..199c88945c856 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -915,6 +915,16 @@ func New(options *Options) *API { }) }) }) + r.Route("/provisionerkeys", func(r chi.Router) { + r.Get("/", api.provisionerKeys) + r.Post("/", api.postProvisionerKey) + r.Route("/{provisionerKey}", func(r chi.Router) { + r.Use( + httpmw.ExtractProvisionerKeyParam(options.Database), + ) + r.Delete("/", api.deleteProvisionerKey) + }) + }) }) }) r.Route("/templates", func(r chi.Router) { diff --git a/coderd/notifications/manager_test.go b/coderd/notifications/manager_test.go index 93ba158b48a65..3f68fb3cff6e1 100644 --- a/coderd/notifications/manager_test.go +++ b/coderd/notifications/manager_test.go @@ -14,6 +14,8 @@ import ( "github.com/coder/serpent" + "github.com/coder/serpent" + "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbgen" "github.com/coder/coder/v2/coderd/notifications" diff --git a/coderd/provisionerkeys.go b/coderd/provisionerkeys.go new file mode 100644 index 0000000000000..53b6f5f7a0700 --- /dev/null +++ b/coderd/provisionerkeys.go @@ -0,0 +1,92 @@ +package coderd + +import ( + "database/sql" + "errors" + "net/http" + + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/httpapi" + "github.com/coder/coder/v2/coderd/httpmw" + "github.com/coder/coder/v2/coderd/provisionerkey" + "github.com/coder/coder/v2/codersdk" +) + +func (api *API) postProvisionerKey(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() + organization := httpmw.OrganizationParam(r) + + var req codersdk.CreateProvisionerKeyRequest + if !httpapi.Read(ctx, rw, r, &req) { + return + } + + params, token, err := provisionerkey.New(organization.ID, req.Name) + if err != nil { + httpapi.InternalServerError(rw, err) + return + } + + _, err = api.Database.InsertProvisionerKey(ctx, params) + if err != nil { + httpapi.InternalServerError(rw, err) + return + } + + httpapi.Write(ctx, rw, http.StatusCreated, codersdk.CreateProvisionerKeyResponse{ + Key: token, + }) +} + +func (api *API) provisionerKeys(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() + organization := httpmw.OrganizationParam(r) + + pks, err := api.Database.ListProvisionerKeysByOrganization(ctx, organization.ID) + if err != nil { + httpapi.InternalServerError(rw, err) + return + } + + httpapi.Write(ctx, rw, http.StatusOK, convertProvisionerKeys(pks)) +} + +func (api *API) deleteProvisionerKey(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() + organization := httpmw.OrganizationParam(r) + provisionerKey := httpmw.ProvisionerKeyParam(r) + + pk, err := api.Database.GetProvisionerKeyByName(ctx, database.GetProvisionerKeyByNameParams{ + OrganizationID: organization.ID, + Name: provisionerKey.Name, + }) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + httpapi.ResourceNotFound(rw) + return + } + httpapi.InternalServerError(rw, err) + return + } + + err = api.Database.DeleteProvisionerKey(ctx, pk.ID) + if err != nil { + httpapi.InternalServerError(rw, err) + return + } + + httpapi.Write(ctx, rw, http.StatusNoContent, nil) +} + +func convertProvisionerKeys(dbKeys []database.ListProvisionerKeysByOrganizationRow) []codersdk.ProvisionerKey { + keys := make([]codersdk.ProvisionerKey, 0, len(dbKeys)) + for _, dbKey := range dbKeys { + keys = append(keys, codersdk.ProvisionerKey{ + ID: dbKey.ID, + CreatedAt: dbKey.CreatedAt, + OrganizationID: dbKey.OrganizationID, + Name: dbKey.Name, + }) + } + return keys +} From e4cf8131b7e88908d922fe9b72f73e1f6516f30d Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Thu, 11 Jul 2024 18:10:36 +0000 Subject: [PATCH 2/9] feat: add provisioner key cli commands --- enterprise/cli/provisionerdaemons.go | 2 + enterprise/cli/provisionerkeys.go | 184 +++++++++++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 enterprise/cli/provisionerkeys.go diff --git a/enterprise/cli/provisionerdaemons.go b/enterprise/cli/provisionerdaemons.go index 079b1891346eb..2ea8983adc69d 100644 --- a/enterprise/cli/provisionerdaemons.go +++ b/enterprise/cli/provisionerdaemons.go @@ -39,8 +39,10 @@ func (r *RootCmd) provisionerDaemons() *serpent.Command { Handler: func(inv *serpent.Invocation) error { return inv.Command.HelpHandler(inv) }, + Aliases: []string{"provisioner"}, Children: []*serpent.Command{ r.provisionerDaemonStart(), + r.provisionerKeys(), }, } diff --git a/enterprise/cli/provisionerkeys.go b/enterprise/cli/provisionerkeys.go new file mode 100644 index 0000000000000..025d635587932 --- /dev/null +++ b/enterprise/cli/provisionerkeys.go @@ -0,0 +1,184 @@ +package cli + +import ( + "fmt" + "time" + + "github.com/google/uuid" + "golang.org/x/xerrors" + + agpl "github.com/coder/coder/v2/cli" + "github.com/coder/coder/v2/cli/cliui" + "github.com/coder/coder/v2/codersdk" + "github.com/coder/pretty" + "github.com/coder/serpent" +) + +func (r *RootCmd) provisionerKeys() *serpent.Command { + cmd := &serpent.Command{ + Use: "keys", + Short: "Manage provisioner keys", + Handler: func(inv *serpent.Invocation) error { + return inv.Command.HelpHandler(inv) + }, + Aliases: []string{"key"}, + Children: []*serpent.Command{ + r.provisionerKeysCreate(), + r.provisionerKeysList(), + r.provisionerKeysDelete(), + }, + } + + return cmd +} + +func (r *RootCmd) provisionerKeysCreate() *serpent.Command { + var ( + orgContext = agpl.NewOrganizationContext() + ) + + client := new(codersdk.Client) + cmd := &serpent.Command{ + Use: "create ", + Short: "Create a new provisioner key", + Middleware: serpent.Chain( + serpent.RequireNArgs(1), + r.InitClient(client), + ), + Handler: func(inv *serpent.Invocation) error { + ctx := inv.Context() + + org, err := orgContext.Selected(inv, client) + if err != nil { + return xerrors.Errorf("current organization: %w", err) + } + + res, err := client.CreateProvisionerKey(ctx, org.ID, codersdk.CreateProvisionerKeyRequest{ + Name: inv.Args[0], + }) + if err != nil { + return xerrors.Errorf("create provisioner key: %w", err) + } + + _, _ = fmt.Fprintf(inv.Stdout, "Successfully created provisioner key %s!\n\n%s", pretty.Sprint(cliui.DefaultStyles.Keyword, inv.Args[0], res.Key)) + + return nil + }, + } + + cmd.Options = serpent.OptionSet{} + + return cmd +} + +type provisionerKeysTableRow struct { + // For json output: + Key codersdk.ProvisionerKey `table:"-"` + + // For table output: + Name string `json:"-" table:"name,default_sort"` + CreatedAt time.Time `json:"-" table:"created_at"` + OrganizationID uuid.UUID `json:"-" table:"organization_id"` +} + +func provisionerKeysToRows(keys ...codersdk.ProvisionerKey) []provisionerKeysTableRow { + rows := make([]provisionerKeysTableRow, 0, len(keys)) + for _, key := range keys { + rows = append(rows, provisionerKeysTableRow{ + Name: key.Name, + CreatedAt: key.CreatedAt, + OrganizationID: key.OrganizationID, + }) + } + + return rows +} + +func (r *RootCmd) provisionerKeysList() *serpent.Command { + var ( + orgContext = agpl.NewOrganizationContext() + formatter = cliui.NewOutputFormatter( + cliui.TableFormat([]provisionerKeysTableRow{}, nil), + cliui.JSONFormat(), + ) + ) + + client := new(codersdk.Client) + cmd := &serpent.Command{ + Use: "list", + Short: "List provisioner keys", + Middleware: serpent.Chain( + serpent.RequireNArgs(0), + r.InitClient(client), + ), + Handler: func(inv *serpent.Invocation) error { + ctx := inv.Context() + + org, err := orgContext.Selected(inv, client) + if err != nil { + return xerrors.Errorf("current organization: %w", err) + } + + keys, err := client.ListProvisionerKeys(ctx, org.ID) + if err != nil { + return xerrors.Errorf("list provisioner keys: %w", err) + } + + if len(keys) == 0 { + _, _ = fmt.Fprintln(inv.Stdout, "No provisioner keys found") + return nil + } + + rows := provisionerKeysToRows(keys...) + out, err := formatter.Format(inv.Context(), rows) + if err != nil { + return xerrors.Errorf("display provisioner keys: %w", err) + } + + _, _ = fmt.Fprintln(inv.Stdout, out) + + return nil + }, + } + + cmd.Options = serpent.OptionSet{} + + return cmd +} + +func (r *RootCmd) provisionerKeysDelete() *serpent.Command { + var ( + orgContext = agpl.NewOrganizationContext() + ) + + client := new(codersdk.Client) + cmd := &serpent.Command{ + Use: "delete ", + Short: "Delete a provisioner key", + Middleware: serpent.Chain( + serpent.RequireNArgs(1), + r.InitClient(client), + ), + Handler: func(inv *serpent.Invocation) error { + ctx := inv.Context() + + org, err := orgContext.Selected(inv, client) + if err != nil { + return xerrors.Errorf("current organization: %w", err) + } + + err = client.DeleteProvisionerKey(ctx, org.ID, inv.Args[0]) + if err != nil { + return xerrors.Errorf("delete provisioner key: %w", err) + } + + _, _ = fmt.Fprintf(inv.Stdout, "Successfully deleted provisioner key %s!", pretty.Sprint(cliui.DefaultStyles.Keyword, inv.Args[0])) + + return nil + }, + } + + cmd.Options = serpent.OptionSet{} + + return cmd +} From 9fd47c8a80696372eef6aba45c3ad64d7b79ce6d Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Tue, 16 Jul 2024 16:11:23 +0000 Subject: [PATCH 3/9] fix imports --- coderd/notifications/manager_test.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/coderd/notifications/manager_test.go b/coderd/notifications/manager_test.go index 3f68fb3cff6e1..2e264c534ccfa 100644 --- a/coderd/notifications/manager_test.go +++ b/coderd/notifications/manager_test.go @@ -12,16 +12,13 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/xerrors" - "github.com/coder/serpent" - - "github.com/coder/serpent" - "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbgen" "github.com/coder/coder/v2/coderd/notifications" "github.com/coder/coder/v2/coderd/notifications/dispatch" "github.com/coder/coder/v2/coderd/notifications/types" "github.com/coder/coder/v2/testutil" + "github.com/coder/serpent" ) func TestBufferedUpdates(t *testing.T) { From 3d1b0757e27091a48898f73f4e746c3ca211ce44 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Tue, 16 Jul 2024 16:40:37 +0000 Subject: [PATCH 4/9] fmt --- coderd/coderd.go | 10 ---- coderd/provisionerkeys.go | 92 ------------------------------- docs/cli/provisionerd.md | 5 ++ docs/manifest.json | 20 +++++++ enterprise/cli/provisionerkeys.go | 13 ++--- 5 files changed, 31 insertions(+), 109 deletions(-) delete mode 100644 coderd/provisionerkeys.go diff --git a/coderd/coderd.go b/coderd/coderd.go index 199c88945c856..0a3414fdb984c 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -915,16 +915,6 @@ func New(options *Options) *API { }) }) }) - r.Route("/provisionerkeys", func(r chi.Router) { - r.Get("/", api.provisionerKeys) - r.Post("/", api.postProvisionerKey) - r.Route("/{provisionerKey}", func(r chi.Router) { - r.Use( - httpmw.ExtractProvisionerKeyParam(options.Database), - ) - r.Delete("/", api.deleteProvisionerKey) - }) - }) }) }) r.Route("/templates", func(r chi.Router) { diff --git a/coderd/provisionerkeys.go b/coderd/provisionerkeys.go deleted file mode 100644 index 53b6f5f7a0700..0000000000000 --- a/coderd/provisionerkeys.go +++ /dev/null @@ -1,92 +0,0 @@ -package coderd - -import ( - "database/sql" - "errors" - "net/http" - - "github.com/coder/coder/v2/coderd/database" - "github.com/coder/coder/v2/coderd/httpapi" - "github.com/coder/coder/v2/coderd/httpmw" - "github.com/coder/coder/v2/coderd/provisionerkey" - "github.com/coder/coder/v2/codersdk" -) - -func (api *API) postProvisionerKey(rw http.ResponseWriter, r *http.Request) { - ctx := r.Context() - organization := httpmw.OrganizationParam(r) - - var req codersdk.CreateProvisionerKeyRequest - if !httpapi.Read(ctx, rw, r, &req) { - return - } - - params, token, err := provisionerkey.New(organization.ID, req.Name) - if err != nil { - httpapi.InternalServerError(rw, err) - return - } - - _, err = api.Database.InsertProvisionerKey(ctx, params) - if err != nil { - httpapi.InternalServerError(rw, err) - return - } - - httpapi.Write(ctx, rw, http.StatusCreated, codersdk.CreateProvisionerKeyResponse{ - Key: token, - }) -} - -func (api *API) provisionerKeys(rw http.ResponseWriter, r *http.Request) { - ctx := r.Context() - organization := httpmw.OrganizationParam(r) - - pks, err := api.Database.ListProvisionerKeysByOrganization(ctx, organization.ID) - if err != nil { - httpapi.InternalServerError(rw, err) - return - } - - httpapi.Write(ctx, rw, http.StatusOK, convertProvisionerKeys(pks)) -} - -func (api *API) deleteProvisionerKey(rw http.ResponseWriter, r *http.Request) { - ctx := r.Context() - organization := httpmw.OrganizationParam(r) - provisionerKey := httpmw.ProvisionerKeyParam(r) - - pk, err := api.Database.GetProvisionerKeyByName(ctx, database.GetProvisionerKeyByNameParams{ - OrganizationID: organization.ID, - Name: provisionerKey.Name, - }) - if err != nil { - if errors.Is(err, sql.ErrNoRows) { - httpapi.ResourceNotFound(rw) - return - } - httpapi.InternalServerError(rw, err) - return - } - - err = api.Database.DeleteProvisionerKey(ctx, pk.ID) - if err != nil { - httpapi.InternalServerError(rw, err) - return - } - - httpapi.Write(ctx, rw, http.StatusNoContent, nil) -} - -func convertProvisionerKeys(dbKeys []database.ListProvisionerKeysByOrganizationRow) []codersdk.ProvisionerKey { - keys := make([]codersdk.ProvisionerKey, 0, len(dbKeys)) - for _, dbKey := range dbKeys { - keys = append(keys, codersdk.ProvisionerKey{ - ID: dbKey.ID, - CreatedAt: dbKey.CreatedAt, - OrganizationID: dbKey.OrganizationID, - Name: dbKey.Name, - }) - } - return keys -} diff --git a/docs/cli/provisionerd.md b/docs/cli/provisionerd.md index 21af8ff547fcb..9d9e0daaa4bd4 100644 --- a/docs/cli/provisionerd.md +++ b/docs/cli/provisionerd.md @@ -4,6 +4,10 @@ Manage provisioner daemons +Aliases: + +- provisioner + ## Usage ```console @@ -15,3 +19,4 @@ coder provisionerd | Name | Purpose | | --------------------------------------------- | ------------------------ | | [start](./provisionerd_start.md) | Run a provisioner daemon | +| [keys](./provisionerd_keys.md) | Manage provisioner keys | diff --git a/docs/manifest.json b/docs/manifest.json index 82dd73ada47c8..89d74fa40974b 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -795,6 +795,26 @@ "description": "Manage provisioner daemons", "path": "cli/provisionerd.md" }, + { + "title": "provisionerd keys", + "description": "Manage provisioner keys", + "path": "cli/provisionerd_keys.md" + }, + { + "title": "provisionerd keys create", + "description": "Create a new provisioner key", + "path": "cli/provisionerd_keys_create.md" + }, + { + "title": "provisionerd keys delete", + "description": "Delete a provisioner key", + "path": "cli/provisionerd_keys_delete.md" + }, + { + "title": "provisionerd keys list", + "description": "List provisioner keys", + "path": "cli/provisionerd_keys_list.md" + }, { "title": "provisionerd start", "description": "Run a provisioner daemon", diff --git a/enterprise/cli/provisionerkeys.go b/enterprise/cli/provisionerkeys.go index 025d635587932..7c377a16a6704 100644 --- a/enterprise/cli/provisionerkeys.go +++ b/enterprise/cli/provisionerkeys.go @@ -33,9 +33,7 @@ func (r *RootCmd) provisionerKeys() *serpent.Command { } func (r *RootCmd) provisionerKeysCreate() *serpent.Command { - var ( - orgContext = agpl.NewOrganizationContext() - ) + orgContext := agpl.NewOrganizationContext() client := new(codersdk.Client) cmd := &serpent.Command{ @@ -60,13 +58,14 @@ func (r *RootCmd) provisionerKeysCreate() *serpent.Command { return xerrors.Errorf("create provisioner key: %w", err) } - _, _ = fmt.Fprintf(inv.Stdout, "Successfully created provisioner key %s!\n\n%s", pretty.Sprint(cliui.DefaultStyles.Keyword, inv.Args[0], res.Key)) + _, _ = fmt.Fprintf(inv.Stdout, "Successfully created provisioner key %s!\n\n%s", pretty.Sprint(cliui.DefaultStyles.Keyword, inv.Args[0]), pretty.Sprint(cliui.DefaultStyles.Keyword, res.Key)) return nil }, } cmd.Options = serpent.OptionSet{} + orgContext.AttachOptions(cmd) return cmd } @@ -142,14 +141,13 @@ func (r *RootCmd) provisionerKeysList() *serpent.Command { } cmd.Options = serpent.OptionSet{} + orgContext.AttachOptions(cmd) return cmd } func (r *RootCmd) provisionerKeysDelete() *serpent.Command { - var ( - orgContext = agpl.NewOrganizationContext() - ) + orgContext := agpl.NewOrganizationContext() client := new(codersdk.Client) cmd := &serpent.Command{ @@ -179,6 +177,7 @@ func (r *RootCmd) provisionerKeysDelete() *serpent.Command { } cmd.Options = serpent.OptionSet{} + orgContext.AttachOptions(cmd) return cmd } From c67ed19310dfd18a0077712e850c9a8bddf6aef3 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Tue, 16 Jul 2024 17:23:22 +0000 Subject: [PATCH 5/9] add tests --- docs/cli/provisionerd_keys.md | 23 +++++ docs/cli/provisionerd_keys_create.md | 22 +++++ docs/cli/provisionerd_keys_delete.md | 34 ++++++++ docs/cli/provisionerd_keys_list.md | 26 ++++++ enterprise/cli/provisionerkeys.go | 22 +++-- enterprise/cli/provisionerkeys_test.go | 111 +++++++++++++++++++++++++ 6 files changed, 233 insertions(+), 5 deletions(-) create mode 100644 docs/cli/provisionerd_keys.md create mode 100644 docs/cli/provisionerd_keys_create.md create mode 100644 docs/cli/provisionerd_keys_delete.md create mode 100644 docs/cli/provisionerd_keys_list.md create mode 100644 enterprise/cli/provisionerkeys_test.go diff --git a/docs/cli/provisionerd_keys.md b/docs/cli/provisionerd_keys.md new file mode 100644 index 0000000000000..87ecca367dd11 --- /dev/null +++ b/docs/cli/provisionerd_keys.md @@ -0,0 +1,23 @@ + + +# provisionerd keys + +Manage provisioner keys + +Aliases: + +- key + +## Usage + +```console +coder provisionerd keys +``` + +## Subcommands + +| Name | Purpose | +| ---------------------------------------------------- | ---------------------------- | +| [create](./provisionerd_keys_create.md) | Create a new provisioner key | +| [list](./provisionerd_keys_list.md) | List provisioner keys | +| [delete](./provisionerd_keys_delete.md) | Delete a provisioner key | diff --git a/docs/cli/provisionerd_keys_create.md b/docs/cli/provisionerd_keys_create.md new file mode 100644 index 0000000000000..44543b1c5b23c --- /dev/null +++ b/docs/cli/provisionerd_keys_create.md @@ -0,0 +1,22 @@ + + +# provisionerd keys create + +Create a new provisioner key + +## Usage + +```console +coder provisionerd keys create [flags] +``` + +## Options + +### -O, --org + +| | | +| ----------- | -------------------------------- | +| Type | string | +| Environment | $CODER_ORGANIZATION | + +Select which organization (uuid or name) to use. diff --git a/docs/cli/provisionerd_keys_delete.md b/docs/cli/provisionerd_keys_delete.md new file mode 100644 index 0000000000000..7921975ff6a9c --- /dev/null +++ b/docs/cli/provisionerd_keys_delete.md @@ -0,0 +1,34 @@ + + +# provisionerd keys delete + +Delete a provisioner key + +Aliases: + +- rm + +## Usage + +```console +coder provisionerd keys delete [flags] +``` + +## Options + +### -y, --yes + +| | | +| ---- | ----------------- | +| Type | bool | + +Bypass prompts. + +### -O, --org + +| | | +| ----------- | -------------------------------- | +| Type | string | +| Environment | $CODER_ORGANIZATION | + +Select which organization (uuid or name) to use. diff --git a/docs/cli/provisionerd_keys_list.md b/docs/cli/provisionerd_keys_list.md new file mode 100644 index 0000000000000..a6d583bfd6134 --- /dev/null +++ b/docs/cli/provisionerd_keys_list.md @@ -0,0 +1,26 @@ + + +# provisionerd keys list + +List provisioner keys + +Aliases: + +- ls + +## Usage + +```console +coder provisionerd keys list [flags] +``` + +## Options + +### -O, --org + +| | | +| ----------- | -------------------------------- | +| Type | string | +| Environment | $CODER_ORGANIZATION | + +Select which organization (uuid or name) to use. diff --git a/enterprise/cli/provisionerkeys.go b/enterprise/cli/provisionerkeys.go index 7c377a16a6704..1b7acbcbbf19f 100644 --- a/enterprise/cli/provisionerkeys.go +++ b/enterprise/cli/provisionerkeys.go @@ -2,6 +2,7 @@ package cli import ( "fmt" + "strings" "time" "github.com/google/uuid" @@ -58,7 +59,7 @@ func (r *RootCmd) provisionerKeysCreate() *serpent.Command { return xerrors.Errorf("create provisioner key: %w", err) } - _, _ = fmt.Fprintf(inv.Stdout, "Successfully created provisioner key %s!\n\n%s", pretty.Sprint(cliui.DefaultStyles.Keyword, inv.Args[0]), pretty.Sprint(cliui.DefaultStyles.Keyword, res.Key)) + _, _ = fmt.Fprintf(inv.Stdout, "Successfully created provisioner key %s!\n\n%s\n", pretty.Sprint(cliui.DefaultStyles.Keyword, strings.ToLower(inv.Args[0])), pretty.Sprint(cliui.DefaultStyles.Keyword, res.Key)) return nil }, @@ -104,8 +105,9 @@ func (r *RootCmd) provisionerKeysList() *serpent.Command { client := new(codersdk.Client) cmd := &serpent.Command{ - Use: "list", - Short: "List provisioner keys", + Use: "list", + Short: "List provisioner keys", + Aliases: []string{"ls"}, Middleware: serpent.Chain( serpent.RequireNArgs(0), r.InitClient(client), @@ -165,18 +167,28 @@ func (r *RootCmd) provisionerKeysDelete() *serpent.Command { return xerrors.Errorf("current organization: %w", err) } + _, err = cliui.Prompt(inv, cliui.PromptOptions{ + Text: fmt.Sprintf("Are you sure you want to delete provisioner key %s?", pretty.Sprint(cliui.DefaultStyles.Keyword, inv.Args[0])), + IsConfirm: true, + }) + if err != nil { + return err + } + err = client.DeleteProvisionerKey(ctx, org.ID, inv.Args[0]) if err != nil { return xerrors.Errorf("delete provisioner key: %w", err) } - _, _ = fmt.Fprintf(inv.Stdout, "Successfully deleted provisioner key %s!", pretty.Sprint(cliui.DefaultStyles.Keyword, inv.Args[0])) + _, _ = fmt.Fprintf(inv.Stdout, "Successfully deleted provisioner key %s!\n", pretty.Sprint(cliui.DefaultStyles.Keyword, strings.ToLower(inv.Args[0]))) return nil }, } - cmd.Options = serpent.OptionSet{} + cmd.Options = serpent.OptionSet{ + cliui.SkipPromptOption(), + } orgContext.AttachOptions(cmd) return cmd diff --git a/enterprise/cli/provisionerkeys_test.go b/enterprise/cli/provisionerkeys_test.go new file mode 100644 index 0000000000000..dac764da616b9 --- /dev/null +++ b/enterprise/cli/provisionerkeys_test.go @@ -0,0 +1,111 @@ +package cli_test + +import ( + "strings" + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/require" + + "github.com/coder/coder/v2/cli/clitest" + "github.com/coder/coder/v2/coderd/coderdtest" + "github.com/coder/coder/v2/coderd/rbac" + "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/enterprise/coderd/coderdenttest" + "github.com/coder/coder/v2/enterprise/coderd/license" + "github.com/coder/coder/v2/pty/ptytest" + "github.com/coder/coder/v2/testutil" +) + +func TestProvisionerKeys(t *testing.T) { + t.Parallel() + + t.Run("CRUD", func(t *testing.T) { + t.Parallel() + + dv := coderdtest.DeploymentValues(t) + dv.Experiments = []string{string(codersdk.ExperimentMultiOrganization)} + client, owner := coderdenttest.New(t, &coderdenttest.Options{ + Options: &coderdtest.Options{ + DeploymentValues: dv, + }, + LicenseOptions: &coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureMultipleOrganizations: 1, + }, + }, + }) + orgAdminClient, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.ScopedRoleOrgAdmin(owner.OrganizationID)) + + name := "dont-TEST-me" + ctx := testutil.Context(t, testutil.WaitMedium) + inv, conf := newCLI( + t, + "provisioner", "keys", "create", name, + ) + + pty := ptytest.New(t) + inv.Stdout = pty.Output() + clitest.SetupConfig(t, orgAdminClient, conf) + + err := inv.WithContext(ctx).Run() + require.NoError(t, err) + + line := pty.ReadLine(ctx) + require.Contains(t, line, "Successfully created provisioner key") + require.Contains(t, line, strings.ToLower(name)) + // empty line + _ = pty.ReadLine(ctx) + key := pty.ReadLine(ctx) + require.NotEmpty(t, key) + parts := strings.Split(key, ":") + require.Len(t, parts, 2, "expected 2 parts") + _, err = uuid.Parse(parts[0]) + require.NoError(t, err, "expected token to be a uuid") + + inv, conf = newCLI( + t, + "provisioner", "keys", "ls", + ) + pty = ptytest.New(t) + inv.Stdout = pty.Output() + clitest.SetupConfig(t, orgAdminClient, conf) + + err = inv.WithContext(ctx).Run() + require.NoError(t, err) + line = pty.ReadLine(ctx) + require.Contains(t, line, "NAME") + require.Contains(t, line, "CREATED AT") + require.Contains(t, line, "ORGANIZATION ID") + line = pty.ReadLine(ctx) + require.Contains(t, line, strings.ToLower(name)) + + inv, conf = newCLI( + t, + "provisioner", "keys", "delete", "-y", name, + ) + + pty = ptytest.New(t) + inv.Stdout = pty.Output() + clitest.SetupConfig(t, orgAdminClient, conf) + + err = inv.WithContext(ctx).Run() + require.NoError(t, err) + line = pty.ReadLine(ctx) + require.Contains(t, line, "Successfully deleted provisioner key") + require.Contains(t, line, strings.ToLower(name)) + + inv, conf = newCLI( + t, + "provisioner", "keys", "ls", + ) + pty = ptytest.New(t) + inv.Stdout = pty.Output() + clitest.SetupConfig(t, orgAdminClient, conf) + + err = inv.WithContext(ctx).Run() + require.NoError(t, err) + line = pty.ReadLine(ctx) + require.Contains(t, line, "No provisioner keys found") + }) +} From adffefdacb4f8bdf21df497fdee4858f553b517c Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Tue, 16 Jul 2024 17:29:38 +0000 Subject: [PATCH 6/9] make golden --- .../testdata/coder_provisionerd_--help.golden | 3 +++ .../coder_provisionerd_keys_--help.golden | 16 ++++++++++++++++ ...oder_provisionerd_keys_create_--help.golden | 13 +++++++++++++ ...oder_provisionerd_keys_delete_--help.golden | 18 ++++++++++++++++++ .../coder_provisionerd_keys_list_--help.golden | 15 +++++++++++++++ 5 files changed, 65 insertions(+) create mode 100644 enterprise/cli/testdata/coder_provisionerd_keys_--help.golden create mode 100644 enterprise/cli/testdata/coder_provisionerd_keys_create_--help.golden create mode 100644 enterprise/cli/testdata/coder_provisionerd_keys_delete_--help.golden create mode 100644 enterprise/cli/testdata/coder_provisionerd_keys_list_--help.golden diff --git a/enterprise/cli/testdata/coder_provisionerd_--help.golden b/enterprise/cli/testdata/coder_provisionerd_--help.golden index bfa9ec147e03d..7209d4962abac 100644 --- a/enterprise/cli/testdata/coder_provisionerd_--help.golden +++ b/enterprise/cli/testdata/coder_provisionerd_--help.golden @@ -5,7 +5,10 @@ USAGE: Manage provisioner daemons + Aliases: provisioner + SUBCOMMANDS: + keys Manage provisioner keys start Run a provisioner daemon ——— diff --git a/enterprise/cli/testdata/coder_provisionerd_keys_--help.golden b/enterprise/cli/testdata/coder_provisionerd_keys_--help.golden new file mode 100644 index 0000000000000..68b7b5223a3e0 --- /dev/null +++ b/enterprise/cli/testdata/coder_provisionerd_keys_--help.golden @@ -0,0 +1,16 @@ +coder v0.0.0-devel + +USAGE: + coder provisionerd keys + + Manage provisioner keys + + Aliases: key + +SUBCOMMANDS: + create Create a new provisioner key + delete Delete a provisioner key + list List provisioner keys + +——— +Run `coder --help` for a list of global options. diff --git a/enterprise/cli/testdata/coder_provisionerd_keys_create_--help.golden b/enterprise/cli/testdata/coder_provisionerd_keys_create_--help.golden new file mode 100644 index 0000000000000..a1e7cd1aa9404 --- /dev/null +++ b/enterprise/cli/testdata/coder_provisionerd_keys_create_--help.golden @@ -0,0 +1,13 @@ +coder v0.0.0-devel + +USAGE: + coder provisionerd keys create [flags] + + Create a new provisioner key + +OPTIONS: + -O, --org string, $CODER_ORGANIZATION + Select which organization (uuid or name) to use. + +——— +Run `coder --help` for a list of global options. diff --git a/enterprise/cli/testdata/coder_provisionerd_keys_delete_--help.golden b/enterprise/cli/testdata/coder_provisionerd_keys_delete_--help.golden new file mode 100644 index 0000000000000..0ab277f6609e7 --- /dev/null +++ b/enterprise/cli/testdata/coder_provisionerd_keys_delete_--help.golden @@ -0,0 +1,18 @@ +coder v0.0.0-devel + +USAGE: + coder provisionerd keys delete [flags] + + Delete a provisioner key + + Aliases: rm + +OPTIONS: + -O, --org string, $CODER_ORGANIZATION + Select which organization (uuid or name) to use. + + -y, --yes bool + Bypass prompts. + +——— +Run `coder --help` for a list of global options. diff --git a/enterprise/cli/testdata/coder_provisionerd_keys_list_--help.golden b/enterprise/cli/testdata/coder_provisionerd_keys_list_--help.golden new file mode 100644 index 0000000000000..0bdb43afff4e8 --- /dev/null +++ b/enterprise/cli/testdata/coder_provisionerd_keys_list_--help.golden @@ -0,0 +1,15 @@ +coder v0.0.0-devel + +USAGE: + coder provisionerd keys list [flags] + + List provisioner keys + + Aliases: ls + +OPTIONS: + -O, --org string, $CODER_ORGANIZATION + Select which organization (uuid or name) to use. + +——— +Run `coder --help` for a list of global options. From da95666a6a27040f5b52eb00b009fa6626dfc6ec Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Tue, 16 Jul 2024 17:51:17 +0000 Subject: [PATCH 7/9] hide commands --- docs/cli/provisionerd.md | 1 - docs/cli/provisionerd_keys.md | 23 ------------- docs/cli/provisionerd_keys_create.md | 22 ------------ docs/cli/provisionerd_keys_delete.md | 34 ------------------- docs/cli/provisionerd_keys_list.md | 26 -------------- docs/manifest.json | 20 ----------- enterprise/cli/provisionerkeys.go | 1 + .../testdata/coder_provisionerd_--help.golden | 1 - 8 files changed, 1 insertion(+), 127 deletions(-) delete mode 100644 docs/cli/provisionerd_keys.md delete mode 100644 docs/cli/provisionerd_keys_create.md delete mode 100644 docs/cli/provisionerd_keys_delete.md delete mode 100644 docs/cli/provisionerd_keys_list.md diff --git a/docs/cli/provisionerd.md b/docs/cli/provisionerd.md index 9d9e0daaa4bd4..44168c53a602d 100644 --- a/docs/cli/provisionerd.md +++ b/docs/cli/provisionerd.md @@ -19,4 +19,3 @@ coder provisionerd | Name | Purpose | | --------------------------------------------- | ------------------------ | | [start](./provisionerd_start.md) | Run a provisioner daemon | -| [keys](./provisionerd_keys.md) | Manage provisioner keys | diff --git a/docs/cli/provisionerd_keys.md b/docs/cli/provisionerd_keys.md deleted file mode 100644 index 87ecca367dd11..0000000000000 --- a/docs/cli/provisionerd_keys.md +++ /dev/null @@ -1,23 +0,0 @@ - - -# provisionerd keys - -Manage provisioner keys - -Aliases: - -- key - -## Usage - -```console -coder provisionerd keys -``` - -## Subcommands - -| Name | Purpose | -| ---------------------------------------------------- | ---------------------------- | -| [create](./provisionerd_keys_create.md) | Create a new provisioner key | -| [list](./provisionerd_keys_list.md) | List provisioner keys | -| [delete](./provisionerd_keys_delete.md) | Delete a provisioner key | diff --git a/docs/cli/provisionerd_keys_create.md b/docs/cli/provisionerd_keys_create.md deleted file mode 100644 index 44543b1c5b23c..0000000000000 --- a/docs/cli/provisionerd_keys_create.md +++ /dev/null @@ -1,22 +0,0 @@ - - -# provisionerd keys create - -Create a new provisioner key - -## Usage - -```console -coder provisionerd keys create [flags] -``` - -## Options - -### -O, --org - -| | | -| ----------- | -------------------------------- | -| Type | string | -| Environment | $CODER_ORGANIZATION | - -Select which organization (uuid or name) to use. diff --git a/docs/cli/provisionerd_keys_delete.md b/docs/cli/provisionerd_keys_delete.md deleted file mode 100644 index 7921975ff6a9c..0000000000000 --- a/docs/cli/provisionerd_keys_delete.md +++ /dev/null @@ -1,34 +0,0 @@ - - -# provisionerd keys delete - -Delete a provisioner key - -Aliases: - -- rm - -## Usage - -```console -coder provisionerd keys delete [flags] -``` - -## Options - -### -y, --yes - -| | | -| ---- | ----------------- | -| Type | bool | - -Bypass prompts. - -### -O, --org - -| | | -| ----------- | -------------------------------- | -| Type | string | -| Environment | $CODER_ORGANIZATION | - -Select which organization (uuid or name) to use. diff --git a/docs/cli/provisionerd_keys_list.md b/docs/cli/provisionerd_keys_list.md deleted file mode 100644 index a6d583bfd6134..0000000000000 --- a/docs/cli/provisionerd_keys_list.md +++ /dev/null @@ -1,26 +0,0 @@ - - -# provisionerd keys list - -List provisioner keys - -Aliases: - -- ls - -## Usage - -```console -coder provisionerd keys list [flags] -``` - -## Options - -### -O, --org - -| | | -| ----------- | -------------------------------- | -| Type | string | -| Environment | $CODER_ORGANIZATION | - -Select which organization (uuid or name) to use. diff --git a/docs/manifest.json b/docs/manifest.json index 89d74fa40974b..82dd73ada47c8 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -795,26 +795,6 @@ "description": "Manage provisioner daemons", "path": "cli/provisionerd.md" }, - { - "title": "provisionerd keys", - "description": "Manage provisioner keys", - "path": "cli/provisionerd_keys.md" - }, - { - "title": "provisionerd keys create", - "description": "Create a new provisioner key", - "path": "cli/provisionerd_keys_create.md" - }, - { - "title": "provisionerd keys delete", - "description": "Delete a provisioner key", - "path": "cli/provisionerd_keys_delete.md" - }, - { - "title": "provisionerd keys list", - "description": "List provisioner keys", - "path": "cli/provisionerd_keys_list.md" - }, { "title": "provisionerd start", "description": "Run a provisioner daemon", diff --git a/enterprise/cli/provisionerkeys.go b/enterprise/cli/provisionerkeys.go index 1b7acbcbbf19f..a39fec607edb9 100644 --- a/enterprise/cli/provisionerkeys.go +++ b/enterprise/cli/provisionerkeys.go @@ -22,6 +22,7 @@ func (r *RootCmd) provisionerKeys() *serpent.Command { Handler: func(inv *serpent.Invocation) error { return inv.Command.HelpHandler(inv) }, + Hidden: true, Aliases: []string{"key"}, Children: []*serpent.Command{ r.provisionerKeysCreate(), diff --git a/enterprise/cli/testdata/coder_provisionerd_--help.golden b/enterprise/cli/testdata/coder_provisionerd_--help.golden index 7209d4962abac..175c33e02f973 100644 --- a/enterprise/cli/testdata/coder_provisionerd_--help.golden +++ b/enterprise/cli/testdata/coder_provisionerd_--help.golden @@ -8,7 +8,6 @@ USAGE: Aliases: provisioner SUBCOMMANDS: - keys Manage provisioner keys start Run a provisioner daemon ——— From 90412c7aa2c37c49e7028448259d12d6a31f2fa8 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Thu, 18 Jul 2024 15:18:29 +0000 Subject: [PATCH 8/9] pr comments --- codersdk/provisionerdaemons.go | 8 ++++---- enterprise/cli/provisionerkeys.go | 9 +++++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/codersdk/provisionerdaemons.go b/codersdk/provisionerdaemons.go index 605d44d88c071..d6a8ba1e6f2fe 100644 --- a/codersdk/provisionerdaemons.go +++ b/codersdk/provisionerdaemons.go @@ -267,10 +267,10 @@ func (c *Client) ServeProvisionerDaemon(ctx context.Context, req ServeProvisione } type ProvisionerKey struct { - ID uuid.UUID `json:"id" format:"uuid"` - CreatedAt time.Time `json:"created_at" format:"date-time"` - OrganizationID uuid.UUID `json:"organization" format:"uuid"` - Name string `json:"name"` + ID uuid.UUID `json:"id" table:"-" format:"uuid"` + CreatedAt time.Time `json:"created_at" table:"created_at" format:"date-time"` + OrganizationID uuid.UUID `json:"organization" table:"organization_id" format:"uuid"` + Name string `json:"name" table:"name,default_sort"` // HashedSecret - never include the access token in the API response } diff --git a/enterprise/cli/provisionerkeys.go b/enterprise/cli/provisionerkeys.go index a39fec607edb9..8485378d6af2d 100644 --- a/enterprise/cli/provisionerkeys.go +++ b/enterprise/cli/provisionerkeys.go @@ -60,7 +60,12 @@ func (r *RootCmd) provisionerKeysCreate() *serpent.Command { return xerrors.Errorf("create provisioner key: %w", err) } - _, _ = fmt.Fprintf(inv.Stdout, "Successfully created provisioner key %s!\n\n%s\n", pretty.Sprint(cliui.DefaultStyles.Keyword, strings.ToLower(inv.Args[0])), pretty.Sprint(cliui.DefaultStyles.Keyword, res.Key)) + _, _ = fmt.Fprintf( + inv.Stdout, + "Successfully created provisioner key %s! Save this authentication token, it will not be shown again.\n\n%s\n", + pretty.Sprint(cliui.DefaultStyles.Keyword, strings.ToLower(inv.Args[0])), + pretty.Sprint(cliui.DefaultStyles.Keyword, res.Key), + ) return nil }, @@ -107,7 +112,7 @@ func (r *RootCmd) provisionerKeysList() *serpent.Command { client := new(codersdk.Client) cmd := &serpent.Command{ Use: "list", - Short: "List provisioner keys", + Short: "List provisioner keys in an organization", Aliases: []string{"ls"}, Middleware: serpent.Chain( serpent.RequireNArgs(0), From 86c0d02bb2c3f7d1d821b41ceb752077d18f9640 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Thu, 18 Jul 2024 17:32:07 +0000 Subject: [PATCH 9/9] remove rows type --- enterprise/cli/provisionerkeys.go | 30 ++---------------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/enterprise/cli/provisionerkeys.go b/enterprise/cli/provisionerkeys.go index 8485378d6af2d..8253d4826e164 100644 --- a/enterprise/cli/provisionerkeys.go +++ b/enterprise/cli/provisionerkeys.go @@ -3,9 +3,7 @@ package cli import ( "fmt" "strings" - "time" - "github.com/google/uuid" "golang.org/x/xerrors" agpl "github.com/coder/coder/v2/cli" @@ -77,34 +75,11 @@ func (r *RootCmd) provisionerKeysCreate() *serpent.Command { return cmd } -type provisionerKeysTableRow struct { - // For json output: - Key codersdk.ProvisionerKey `table:"-"` - - // For table output: - Name string `json:"-" table:"name,default_sort"` - CreatedAt time.Time `json:"-" table:"created_at"` - OrganizationID uuid.UUID `json:"-" table:"organization_id"` -} - -func provisionerKeysToRows(keys ...codersdk.ProvisionerKey) []provisionerKeysTableRow { - rows := make([]provisionerKeysTableRow, 0, len(keys)) - for _, key := range keys { - rows = append(rows, provisionerKeysTableRow{ - Name: key.Name, - CreatedAt: key.CreatedAt, - OrganizationID: key.OrganizationID, - }) - } - - return rows -} - func (r *RootCmd) provisionerKeysList() *serpent.Command { var ( orgContext = agpl.NewOrganizationContext() formatter = cliui.NewOutputFormatter( - cliui.TableFormat([]provisionerKeysTableRow{}, nil), + cliui.TableFormat([]codersdk.ProvisionerKey{}, nil), cliui.JSONFormat(), ) ) @@ -136,8 +111,7 @@ func (r *RootCmd) provisionerKeysList() *serpent.Command { return nil } - rows := provisionerKeysToRows(keys...) - out, err := formatter.Format(inv.Context(), rows) + out, err := formatter.Format(inv.Context(), keys) if err != nil { return xerrors.Errorf("display provisioner keys: %w", err) }