diff --git a/enterprise/cli/licenses.go b/enterprise/cli/licenses.go index 72c7c3d67e819..e1cc4992387ea 100644 --- a/enterprise/cli/licenses.go +++ b/enterprise/cli/licenses.go @@ -6,6 +6,7 @@ import ( "io" "os" "regexp" + "strconv" "strings" "github.com/spf13/cobra" @@ -20,13 +21,14 @@ var jwtRegexp = regexp.MustCompile(`^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_ func licenses() *cobra.Command { cmd := &cobra.Command{ - Short: "Add, remove, and list licenses", + Short: "Add, delete, and list licenses", Use: "licenses", Aliases: []string{"license"}, } cmd.AddCommand( licenseAdd(), licensesList(), + licenseDelete(), ) return cmd } @@ -142,3 +144,29 @@ func licensesList() *cobra.Command { } return cmd } + +func licenseDelete() *cobra.Command { + cmd := &cobra.Command{ + Use: "delete ", + Short: "Delete license by ID", + Aliases: []string{"del", "rm"}, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := agpl.CreateClient(cmd) + if err != nil { + return err + } + id, err := strconv.ParseInt(args[0], 10, 32) + if err != nil { + return xerrors.Errorf("license ID must be an integer: %s", args[0]) + } + err = client.DeleteLicense(cmd.Context(), int32(id)) + if err != nil { + return err + } + _, _ = fmt.Fprintf(cmd.OutOrStdout(), "License with ID %d deleted\n", id) + return nil + }, + } + return cmd +} diff --git a/enterprise/cli/licenses_test.go b/enterprise/cli/licenses_test.go index 79cf1256997f6..92fa91da4a685 100644 --- a/enterprise/cli/licenses_test.go +++ b/enterprise/cli/licenses_test.go @@ -191,6 +191,48 @@ func TestLicensesListReal(t *testing.T) { }) } +func TestLicensesDeleteFake(t *testing.T) { + t.Parallel() + // We can't check a real license into the git repo, and can't patch out the keys from here, + // so instead we have to fake the HTTP interaction. + t.Run("Mainline", func(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + cmd := setupFakeLicenseServerTest(t, "licenses", "delete", "55") + pty := attachPty(t, cmd) + errC := make(chan error) + go func() { + errC <- cmd.ExecuteContext(ctx) + }() + require.NoError(t, <-errC) + pty.ExpectMatch("License with ID 55 deleted") + }) +} + +func TestLicensesDeleteReal(t *testing.T) { + t.Parallel() + t.Run("Empty", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, &coderdtest.Options{APIBuilder: coderd.NewEnterprise}) + coderdtest.CreateFirstUser(t, client) + cmd, root := clitest.NewWithSubcommands(t, cli.EnterpriseSubcommands(), + "licenses", "delete", "1") + clitest.SetupConfig(t, client, root) + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + errC := make(chan error) + go func() { + errC <- cmd.ExecuteContext(ctx) + }() + err := <-errC + var coderError *codersdk.Error + require.True(t, xerrors.As(err, &coderError)) + assert.Equal(t, 404, coderError.StatusCode()) + assert.Contains(t, "Unknown license ID", coderError.Message) + }) +} + func setupFakeLicenseServerTest(t *testing.T, args ...string) *cobra.Command { t.Helper() s := httptest.NewServer(newFakeLicenseAPI(t)) @@ -217,6 +259,7 @@ func newFakeLicenseAPI(t *testing.T) http.Handler { r.Post("/api/v2/licenses", a.postLicense) r.Get("/api/v2/licenses", a.licenses) r.Get("/api/v2/buildinfo", a.noop) + r.Delete("/api/v2/licenses/{id}", a.deleteLicense) return r } @@ -282,3 +325,8 @@ func (s *fakeLicenseAPI) licenses(rw http.ResponseWriter, _ *http.Request) { err := json.NewEncoder(rw).Encode(resp) assert.NoError(s.t, err) } + +func (s *fakeLicenseAPI) deleteLicense(rw http.ResponseWriter, r *http.Request) { + assert.Equal(s.t, "55", chi.URLParam(r, "id")) + rw.WriteHeader(200) +}