From f872b51617a6846f795ab2468ee35368bb7d9c60 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Fri, 13 May 2022 16:52:21 -0500 Subject: [PATCH 1/6] feat: add `templates delete` command --- cli/templatedelete.go | 86 ++++++++++++++++++++++++++++++++++ cli/templatedelete_test.go | 64 +++++++++++++++++++++++++ cli/templates.go | 1 + coderd/httpmw/templateparam.go | 6 +++ coderd/templates.go | 1 + 5 files changed, 158 insertions(+) create mode 100644 cli/templatedelete.go create mode 100644 cli/templatedelete_test.go diff --git a/cli/templatedelete.go b/cli/templatedelete.go new file mode 100644 index 0000000000000..aa08ee904bce4 --- /dev/null +++ b/cli/templatedelete.go @@ -0,0 +1,86 @@ +package cli + +import ( + "fmt" + + "github.com/spf13/cobra" + "golang.org/x/xerrors" + + "github.com/coder/coder/cli/cliui" + "github.com/coder/coder/codersdk" +) + +func templateDelete() *cobra.Command { + return &cobra.Command{ + Use: "delete [name]", + Args: cobra.MaximumNArgs(1), + Short: "Delete a template.", + RunE: func(cmd *cobra.Command, args []string) error { + var ( + ctx = cmd.Context() + templateNames = []string{} + templates = []codersdk.Template{} + ) + + client, err := createClient(cmd) + if err != nil { + return err + } + organization, err := currentOrganization(cmd, client) + if err != nil { + return err + } + + if len(args) > 0 { + templateNames = args + } else { + allTemplates, err := client.TemplatesByOrganization(ctx, organization.ID) + if err != nil { + return xerrors.Errorf("get templates by organization: %w", err) + } + + if len(allTemplates) == 0 { + return xerrors.Errorf("no templates exist in the current organization %q", organization.Name) + } + + opts := make([]string, 0, len(allTemplates)) + for _, template := range allTemplates { + opts = append(opts, template.Name) + } + + selection, err := cliui.Select(cmd, cliui.SelectOptions{ + Options: opts, + }) + if err != nil { + return xerrors.Errorf("select template: %w", err) + } + + for _, template := range allTemplates { + if template.Name == selection { + templates = append(templates, template) + } + } + } + + for _, templateName := range templateNames { + template, err := client.TemplateByName(ctx, organization.ID, templateName) + if err != nil { + return xerrors.Errorf("get template by name: %w", err) + } + + templates = append(templates, template) + } + + for _, template := range templates { + err := client.DeleteTemplate(ctx, template.ID) + if err != nil { + return xerrors.Errorf("delete template %q: %w", template.Name, err) + } + + _, _ = fmt.Fprintln(cmd.ErrOrStderr(), "Deleted template "+cliui.Styles.Code.Render(template.Name)+"!") + } + + return nil + }, + } +} diff --git a/cli/templatedelete_test.go b/cli/templatedelete_test.go new file mode 100644 index 0000000000000..49d71c8221a72 --- /dev/null +++ b/cli/templatedelete_test.go @@ -0,0 +1,64 @@ +package cli_test + +import ( + "context" + "testing" + + "github.com/coder/coder/cli/clitest" + "github.com/coder/coder/coderd/coderdtest" + "github.com/coder/coder/pty/ptytest" + "github.com/davecgh/go-spew/spew" + "github.com/stretchr/testify/require" +) + +func TestTemplateDelete(t *testing.T) { + t.Parallel() + + t.Run("Ok", func(t *testing.T) { + t.Parallel() + + client := coderdtest.New(t, nil) + user := coderdtest.CreateFirstUser(t, client) + _ = coderdtest.NewProvisionerDaemon(t, client) + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) + _ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + + cmd, root := clitest.New(t, "templates", "delete", template.Name) + clitest.SetupConfig(t, client, root) + require.NoError(t, cmd.Execute()) + + template, err := client.Template(context.Background(), template.ID) + spew.Dump(template, err) + require.Error(t, err, "template should not exist") + }) + + t.Run("Selector", func(t *testing.T) { + t.Parallel() + + client := coderdtest.New(t, nil) + user := coderdtest.CreateFirstUser(t, client) + _ = coderdtest.NewProvisionerDaemon(t, client) + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) + _ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + + cmd, root := clitest.New(t, "templates", "delete") + clitest.SetupConfig(t, client, root) + + pty := ptytest.New(t) + cmd.SetIn(pty.Input()) + cmd.SetOut(pty.Output()) + + execDone := make(chan error) + go func() { + execDone <- cmd.Execute() + }() + + pty.WriteLine("docker-local") + require.NoError(t, <-execDone) + + _, err := client.Template(context.Background(), template.ID) + require.Error(t, err, "template should not exist") + }) +} diff --git a/cli/templates.go b/cli/templates.go index de1a9bcc1478d..45f6224369ba8 100644 --- a/cli/templates.go +++ b/cli/templates.go @@ -30,6 +30,7 @@ func templates() *cobra.Command { templatePlan(), templateUpdate(), templateVersions(), + templateDelete(), ) return cmd diff --git a/coderd/httpmw/templateparam.go b/coderd/httpmw/templateparam.go index 48adb95ff2ae8..ad549254b37dd 100644 --- a/coderd/httpmw/templateparam.go +++ b/coderd/httpmw/templateparam.go @@ -45,6 +45,12 @@ func ExtractTemplateParam(db database.Store) func(http.Handler) http.Handler { return } + if template.Deleted { + httpapi.Write(rw, http.StatusNotFound, httpapi.Response{ + Message: fmt.Sprintf("template %q does not exist", templateID), + }) + } + ctx := context.WithValue(r.Context(), templateParamContextKey{}, template) chi.RouteContext(ctx).URLParams.Add("organization", template.OrganizationID.String()) next.ServeHTTP(rw, r.WithContext(ctx)) diff --git a/coderd/templates.go b/coderd/templates.go index 153c8aba9c122..12958c0860070 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -18,6 +18,7 @@ import ( // Returns a single template. func (api *api) template(rw http.ResponseWriter, r *http.Request) { template := httpmw.TemplateParam(r) + workspaceCounts, err := api.Database.GetWorkspaceOwnerCountsByTemplateIDs(r.Context(), []uuid.UUID{template.ID}) if errors.Is(err, sql.ErrNoRows) { err = nil From 3a4782a66581631245ce8e36e26fa7f719264cbf Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Fri, 13 May 2022 16:58:10 -0500 Subject: [PATCH 2/6] add return + remove max arg limit --- cli/templatedelete.go | 1 - coderd/httpmw/templateparam.go | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/templatedelete.go b/cli/templatedelete.go index aa08ee904bce4..e894032b01f9d 100644 --- a/cli/templatedelete.go +++ b/cli/templatedelete.go @@ -13,7 +13,6 @@ import ( func templateDelete() *cobra.Command { return &cobra.Command{ Use: "delete [name]", - Args: cobra.MaximumNArgs(1), Short: "Delete a template.", RunE: func(cmd *cobra.Command, args []string) error { var ( diff --git a/coderd/httpmw/templateparam.go b/coderd/httpmw/templateparam.go index ad549254b37dd..f5d1ffcef127a 100644 --- a/coderd/httpmw/templateparam.go +++ b/coderd/httpmw/templateparam.go @@ -49,6 +49,7 @@ func ExtractTemplateParam(db database.Store) func(http.Handler) http.Handler { httpapi.Write(rw, http.StatusNotFound, httpapi.Response{ Message: fmt.Sprintf("template %q does not exist", templateID), }) + return } ctx := context.WithValue(r.Context(), templateParamContextKey{}, template) From f7aca885b06a94bec67f27917daf96fa86aed168 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Fri, 13 May 2022 17:05:58 -0500 Subject: [PATCH 3/6] add test for deleting multiple templates --- cli/templatedelete.go | 2 +- cli/templatedelete_test.go | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/cli/templatedelete.go b/cli/templatedelete.go index e894032b01f9d..41bf9dae34ec3 100644 --- a/cli/templatedelete.go +++ b/cli/templatedelete.go @@ -13,7 +13,7 @@ import ( func templateDelete() *cobra.Command { return &cobra.Command{ Use: "delete [name]", - Short: "Delete a template.", + Short: "Delete templates.", RunE: func(cmd *cobra.Command, args []string) error { var ( ctx = cmd.Context() diff --git a/cli/templatedelete_test.go b/cli/templatedelete_test.go index 49d71c8221a72..d374585650afc 100644 --- a/cli/templatedelete_test.go +++ b/cli/templatedelete_test.go @@ -6,8 +6,8 @@ import ( "github.com/coder/coder/cli/clitest" "github.com/coder/coder/coderd/coderdtest" + "github.com/coder/coder/codersdk" "github.com/coder/coder/pty/ptytest" - "github.com/davecgh/go-spew/spew" "github.com/stretchr/testify/require" ) @@ -28,11 +28,38 @@ func TestTemplateDelete(t *testing.T) { clitest.SetupConfig(t, client, root) require.NoError(t, cmd.Execute()) - template, err := client.Template(context.Background(), template.ID) - spew.Dump(template, err) + _, err := client.Template(context.Background(), template.ID) require.Error(t, err, "template should not exist") }) + t.Run("Multiple", func(t *testing.T) { + t.Parallel() + + client := coderdtest.New(t, nil) + user := coderdtest.CreateFirstUser(t, client) + _ = coderdtest.NewProvisionerDaemon(t, client) + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) + _ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID) + templates := []codersdk.Template{ + coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID), + coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID), + coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID), + } + templateNames := []string{} + for _, template := range templates { + templateNames = append(templateNames, template.Name) + } + + cmd, root := clitest.New(t, append([]string{"templates", "delete"}, templateNames...)...) + clitest.SetupConfig(t, client, root) + require.NoError(t, cmd.Execute()) + + for _, template := range templates { + _, err := client.Template(context.Background(), template.ID) + require.Error(t, err, "template should not exist") + } + }) + t.Run("Selector", func(t *testing.T) { t.Parallel() From 5c6b745a7f8f3debbb83913aa8f5ad64337cd3b6 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Fri, 13 May 2022 17:08:34 -0500 Subject: [PATCH 4/6] fix use doc --- cli/templatedelete.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/templatedelete.go b/cli/templatedelete.go index 41bf9dae34ec3..ff8d02ddaa574 100644 --- a/cli/templatedelete.go +++ b/cli/templatedelete.go @@ -12,7 +12,7 @@ import ( func templateDelete() *cobra.Command { return &cobra.Command{ - Use: "delete [name]", + Use: "delete [name...]", Short: "Delete templates.", RunE: func(cmd *cobra.Command, args []string) error { var ( From 62797944e7c23032ecd6414975a2140d712d91dc Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Fri, 13 May 2022 17:32:11 -0500 Subject: [PATCH 5/6] sentence i guess --- cli/templatedelete.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/templatedelete.go b/cli/templatedelete.go index ff8d02ddaa574..372a17facf94d 100644 --- a/cli/templatedelete.go +++ b/cli/templatedelete.go @@ -13,7 +13,7 @@ import ( func templateDelete() *cobra.Command { return &cobra.Command{ Use: "delete [name...]", - Short: "Delete templates.", + Short: "Delete one or multiple templates.", RunE: func(cmd *cobra.Command, args []string) error { var ( ctx = cmd.Context() From 52b0f6d7d483a1b94b3342030bc64ab63c6621e9 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Fri, 13 May 2022 17:40:27 -0500 Subject: [PATCH 6/6] "Delete templates" --- cli/templatedelete.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/templatedelete.go b/cli/templatedelete.go index 372a17facf94d..26ba6ca2a01f1 100644 --- a/cli/templatedelete.go +++ b/cli/templatedelete.go @@ -13,7 +13,7 @@ import ( func templateDelete() *cobra.Command { return &cobra.Command{ Use: "delete [name...]", - Short: "Delete one or multiple templates.", + Short: "Delete templates", RunE: func(cmd *cobra.Command, args []string) error { var ( ctx = cmd.Context()