From 70123ac96b297a2618f56e76d0dd4a5dd81ef5f9 Mon Sep 17 00:00:00 2001 From: Michael Christenson II Date: Thu, 30 Nov 2023 09:16:28 -0500 Subject: [PATCH] Add batch deletions Added batch deletion to actions command Added batch deletion to apis command Added batch deletion to apps command Added batch deletion to custom domains command Added batch deletion to log streams command Added batch deletion for organizations command Added batch deletion for roles command Added batch deletion for rules command Added batch deletion for users non-interactive command Added batch deletion for user blocks non-interactive command Update required golang minimum version --- go.mod | 2 +- internal/cli/actions.go | 2 +- internal/cli/apis.go | 28 +++++++++++++++------------- internal/cli/apps.go | 28 ++++++++++++++++------------ internal/cli/custom_domains.go | 29 +++++++++++++++++------------ internal/cli/log_streams.go | 27 +++++++++++++++------------ internal/cli/organizations.go | 27 +++++++++++++++------------ internal/cli/roles.go | 27 +++++++++++++++------------ internal/cli/rules.go | 31 +++++++++++++++++-------------- internal/cli/users.go | 29 +++++++++++++++++------------ internal/cli/users_blocks.go | 31 +++++++++++++++++-------------- 11 files changed, 146 insertions(+), 115 deletions(-) diff --git a/go.mod b/go.mod index 3bdffe579..2847f6e0b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/auth0/auth0-cli -go 1.20 +go 1.21 require ( github.com/AlecAivazis/survey/v2 v2.3.7 diff --git a/internal/cli/actions.go b/internal/cli/actions.go index 20999caad..12994ded4 100644 --- a/internal/cli/actions.go +++ b/internal/cli/actions.go @@ -371,7 +371,7 @@ func deleteActionCmd(cli *cli) *cobra.Command { auth0 actions delete auth0 actions delete --force`, RunE: func(cmd *cobra.Command, args []string) error { - var ids []string + ids := make([]string, len(args)) if len(args) == 0 { if err := actionID.PickMany(cmd, &ids, cli.actionPickerOptions); err != nil { return err diff --git a/internal/cli/apis.go b/internal/cli/apis.go index ffb91f9a4..f68cc9e67 100644 --- a/internal/cli/apis.go +++ b/internal/cli/apis.go @@ -390,14 +390,10 @@ func updateAPICmd(cli *cli) *cobra.Command { } func deleteAPICmd(cli *cli) *cobra.Command { - var inputs struct { - ID string - } - cmd := &cobra.Command{ Use: "delete", Aliases: []string{"rm"}, - Args: cobra.MaximumNArgs(1), + Args: cobra.MinimumNArgs(0), Short: "Delete an API", Long: "Delete an API.\n\n" + "To delete interactively, use `auth0 apis delete` with no arguments.\n\n" + @@ -407,13 +403,15 @@ func deleteAPICmd(cli *cli) *cobra.Command { auth0 apis delete auth0 apis delete --force`, RunE: func(cmd *cobra.Command, args []string) error { + var ids []string if len(args) == 0 { - err := apiID.Pick(cmd, &inputs.ID, cli.apiPickerOptions) - if err != nil { + if err := apiID.PickMany(cmd, &ids, cli.apiPickerOptions); err != nil { return err } } else { - inputs.ID = args[0] + for _, id := range args[0:] { + ids = append(ids, id) + } } if !cli.force && canPrompt(cmd) { @@ -423,13 +421,17 @@ func deleteAPICmd(cli *cli) *cobra.Command { } return ansi.Spinner("Deleting API", func() error { - _, err := cli.api.ResourceServer.Read(cmd.Context(), url.PathEscape(inputs.ID)) + var errs []error + for _, id := range ids { + if _, err := cli.api.ResourceServer.Read(cmd.Context(), url.PathEscape(id)); err != nil { + errs = append(errs, fmt.Errorf("Unable to read API for deletion: %w", err)) + } - if err != nil { - return fmt.Errorf("Unable to delete API: %w", err) + if err := cli.api.ResourceServer.Delete(cmd.Context(), url.PathEscape(id)); err != nil { + errs = append(errs, fmt.Errorf("Unable to delete API: %w", err)) + } } - - return cli.api.ResourceServer.Delete(cmd.Context(), url.PathEscape(inputs.ID)) + return errors.Join(errs...) }) }, } diff --git a/internal/cli/apps.go b/internal/cli/apps.go index 6eb76c7ea..d39bd5e9e 100644 --- a/internal/cli/apps.go +++ b/internal/cli/apps.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "net/url" "strings" "github.com/auth0/go-auth0/management" @@ -302,14 +303,10 @@ func showAppCmd(cli *cli) *cobra.Command { } func deleteAppCmd(cli *cli) *cobra.Command { - var inputs struct { - ID string - } - cmd := &cobra.Command{ Use: "delete", Aliases: []string{"rm"}, - Args: cobra.MaximumNArgs(1), + Args: cobra.MinimumNArgs(0), Short: "Delete an application", Long: "Delete an application.\n\n" + "To delete interactively, use `auth0 apps delete` with no arguments.\n\n" + @@ -320,13 +317,16 @@ func deleteAppCmd(cli *cli) *cobra.Command { auth0 apps delete auth0 apps delete --force`, RunE: func(cmd *cobra.Command, args []string) error { + ids := make([]string, len(args)) if len(args) == 0 { - err := appID.Pick(cmd, &inputs.ID, cli.appPickerOptions()) + err := appID.PickMany(cmd, &ids, cli.appPickerOptions()) if err != nil { return err } } else { - inputs.ID = args[0] + for _, id := range args[0:] { + ids = append(ids, id) + } } if !cli.force && canPrompt(cmd) { @@ -336,13 +336,17 @@ func deleteAppCmd(cli *cli) *cobra.Command { } return ansi.Spinner("Deleting Application", func() error { - _, err := cli.api.Client.Read(cmd.Context(), inputs.ID) + var errs []error + for _, id := range ids { + if _, err := cli.api.Client.Read(cmd.Context(), url.PathEscape(id)); err != nil { + errs = append(errs, fmt.Errorf("Unable to read application for deletion: %w", err)) + } - if err != nil { - return fmt.Errorf("Unable to delete application: %w", err) + if err := cli.api.Client.Delete(cmd.Context(), url.PathEscape(id)); err != nil { + errs = append(errs, fmt.Errorf("Unable to delete application: %w", err)) + } } - - return cli.api.Client.Delete(cmd.Context(), inputs.ID) + return errors.Join(errs...) }) }, } diff --git a/internal/cli/custom_domains.go b/internal/cli/custom_domains.go index f651204c6..d5e348e0c 100644 --- a/internal/cli/custom_domains.go +++ b/internal/cli/custom_domains.go @@ -2,6 +2,7 @@ package cli import ( "context" + "errors" "fmt" "net/url" @@ -323,14 +324,10 @@ func updateCustomDomainCmd(cli *cli) *cobra.Command { } func deleteCustomDomainCmd(cli *cli) *cobra.Command { - var inputs struct { - ID string - } - cmd := &cobra.Command{ Use: "delete", Aliases: []string{"rm"}, - Args: cobra.MaximumNArgs(1), + Args: cobra.MinimumNArgs(0), Short: "Delete a custom domain", Long: "Delete a custom domain.\n\n" + "To delete interactively, use `auth0 domains delete` with no arguments.\n\n" + @@ -341,13 +338,16 @@ func deleteCustomDomainCmd(cli *cli) *cobra.Command { auth0 domains delete auth0 domains delete --force`, RunE: func(cmd *cobra.Command, args []string) error { + ids := make([]string, len(args)) if len(args) == 0 { - err := customDomainID.Pick(cmd, &inputs.ID, cli.customDomainsPickerOptions) + err := customDomainID.PickMany(cmd, &ids, cli.customDomainsPickerOptions) if err != nil { return err } } else { - inputs.ID = args[0] + for _, id := range args[0:] { + ids = append(ids, id) + } } if !cli.force && canPrompt(cmd) { @@ -357,13 +357,18 @@ func deleteCustomDomainCmd(cli *cli) *cobra.Command { } return ansi.Spinner("Deleting custom domain", func() error { - _, err := cli.api.CustomDomain.Read(cmd.Context(), url.PathEscape(inputs.ID)) - - if err != nil { - return fmt.Errorf("Unable to delete custom domain: %w", err) + var errs []error + for _, id := range ids { + if _, err := cli.api.CustomDomain.Read(cmd.Context(), url.PathEscape(id)); err != nil { + return fmt.Errorf("Unable to read custom domain for deletion: %w", err) + } + + if err := cli.api.CustomDomain.Delete(cmd.Context(), url.PathEscape(id)); err != nil { + return fmt.Errorf("Unable to delete custom domain: %w", err) + } } - return cli.api.CustomDomain.Delete(cmd.Context(), url.PathEscape(inputs.ID)) + return errors.Join(errs...) }) }, } diff --git a/internal/cli/log_streams.go b/internal/cli/log_streams.go index 64d671016..6d4a497e1 100644 --- a/internal/cli/log_streams.go +++ b/internal/cli/log_streams.go @@ -168,14 +168,10 @@ func updateLogStreamCmd(cli *cli) *cobra.Command { } func deleteLogStreamCmd(cli *cli) *cobra.Command { - var inputs struct { - ID string - } - cmd := &cobra.Command{ Use: "delete", Aliases: []string{"rm"}, - Args: cobra.MaximumNArgs(1), + Args: cobra.MinimumNArgs(0), Short: "Delete a log stream", Long: "Delete a log stream.\n\n" + "To delete interactively, use `auth0 logs streams delete` with no arguments.\n\n" + @@ -186,13 +182,16 @@ func deleteLogStreamCmd(cli *cli) *cobra.Command { auth0 logs streams delete auth0 logs streams delete --force`, RunE: func(cmd *cobra.Command, args []string) error { + ids := make([]string, len(args)) if len(args) == 0 { - err := logStreamID.Pick(cmd, &inputs.ID, cli.allLogStreamsPickerOptions) + err := logStreamID.PickMany(cmd, &ids, cli.allLogStreamsPickerOptions) if err != nil { return err } } else { - inputs.ID = args[0] + for _, id := range args[0:] { + ids = append(ids, id) + } } if !cli.force && canPrompt(cmd) { @@ -202,13 +201,17 @@ func deleteLogStreamCmd(cli *cli) *cobra.Command { } return ansi.Spinner("Deleting Log Stream", func() error { - _, err := cli.api.LogStream.Read(cmd.Context(), inputs.ID) - - if err != nil { - return fmt.Errorf("Unable to delete log stream: %w", err) + var errs []error + for _, id := range ids { + if _, err := cli.api.LogStream.Read(cmd.Context(), id); err != nil { + errs = append(errs, fmt.Errorf("Unable to read log stream for deletion: %w", err)) + } + if err := cli.api.LogStream.Delete(cmd.Context(), id); err != nil { + errs = append(errs, fmt.Errorf("Unable to delete log stream: %w", err)) + } } - return cli.api.LogStream.Delete(cmd.Context(), inputs.ID) + return errors.Join(errs...) }) }, } diff --git a/internal/cli/organizations.go b/internal/cli/organizations.go index ba5192eb3..71795c99c 100644 --- a/internal/cli/organizations.go +++ b/internal/cli/organizations.go @@ -408,14 +408,10 @@ func updateOrganizationCmd(cli *cli) *cobra.Command { } func deleteOrganizationCmd(cli *cli) *cobra.Command { - var inputs struct { - ID string - } - cmd := &cobra.Command{ Use: "delete", Aliases: []string{"rm"}, - Args: cobra.MaximumNArgs(1), + Args: cobra.MinimumNArgs(0), Short: "Delete an organization", Long: "Delete an organization.\n\n" + "To delete interactively, use `auth0 orgs delete` with no arguments.\n\n" + @@ -426,13 +422,16 @@ func deleteOrganizationCmd(cli *cli) *cobra.Command { auth0 orgs delete auth0 orgs delete --force`, RunE: func(cmd *cobra.Command, args []string) error { + ids := make([]string, len(args)) if len(args) == 0 { - err := organizationID.Pick(cmd, &inputs.ID, cli.organizationPickerOptions) + err := organizationID.PickMany(cmd, &ids, cli.organizationPickerOptions) if err != nil { return err } } else { - inputs.ID = args[0] + for _, id := range args { + ids = append(ids, id) + } } if !cli.force && canPrompt(cmd) { @@ -442,13 +441,17 @@ func deleteOrganizationCmd(cli *cli) *cobra.Command { } return ansi.Spinner("Deleting organization", func() error { - _, err := cli.api.Organization.Read(cmd.Context(), url.PathEscape(inputs.ID)) + var errs []error + for _, id := range ids { + if _, err := cli.api.Organization.Read(cmd.Context(), url.PathEscape(id)); err != nil { + errs = append(errs, fmt.Errorf("Unable to read organization for deletion: %w", err)) + } - if err != nil { - return fmt.Errorf("Unable to delete organization: %w", err) + if err := cli.api.Organization.Delete(cmd.Context(), url.PathEscape(id)); err != nil { + errs = append(errs, fmt.Errorf("Unable to delete organization: %w", err)) + } } - - return cli.api.Organization.Delete(cmd.Context(), url.PathEscape(inputs.ID)) + return errors.Join(errs...) }) }, } diff --git a/internal/cli/roles.go b/internal/cli/roles.go index 96e25e420..c4789a6ef 100644 --- a/internal/cli/roles.go +++ b/internal/cli/roles.go @@ -282,14 +282,10 @@ func updateRoleCmd(cli *cli) *cobra.Command { } func deleteRoleCmd(cli *cli) *cobra.Command { - var inputs struct { - ID string - } - cmd := &cobra.Command{ Use: "delete", Aliases: []string{"rm"}, - Args: cobra.MaximumNArgs(1), + Args: cobra.MinimumNArgs(0), Short: "Delete a role", Long: "Delete a role.\n\n" + "To delete interactively, use `auth0 roles delete`.\n\n" + @@ -299,13 +295,16 @@ func deleteRoleCmd(cli *cli) *cobra.Command { auth0 roles delete auth0 roles delete --force`, RunE: func(cmd *cobra.Command, args []string) error { + ids := make([]string, len(args)) if len(args) == 0 { - err := roleID.Pick(cmd, &inputs.ID, cli.rolePickerOptions) + err := roleID.PickMany(cmd, &ids, cli.rolePickerOptions) if err != nil { return err } } else { - inputs.ID = args[0] + for _, id := range args { + ids = append(ids, id) + } } if !cli.force && canPrompt(cmd) { @@ -315,13 +314,17 @@ func deleteRoleCmd(cli *cli) *cobra.Command { } return ansi.Spinner("Deleting Role", func() error { - _, err := cli.api.Role.Read(cmd.Context(), inputs.ID) + var errs []error + for _, id := range ids { + if _, err := cli.api.Role.Read(cmd.Context(), id); err != nil { + errs = append(errs, fmt.Errorf("Unable to read role for deletion: %w", err)) + } - if err != nil { - return fmt.Errorf("Unable to delete role: %w", err) + if err := cli.api.Role.Delete(cmd.Context(), id); err != nil { + errs = append(errs, fmt.Errorf("Unable to delete role: %w", err)) + } } - - return cli.api.Role.Delete(cmd.Context(), inputs.ID) + return errors.Join(errs...) }) }, } diff --git a/internal/cli/rules.go b/internal/cli/rules.go index ac2f9d20b..a357416be 100644 --- a/internal/cli/rules.go +++ b/internal/cli/rules.go @@ -249,14 +249,10 @@ func showRuleCmd(cli *cli) *cobra.Command { } func deleteRuleCmd(cli *cli) *cobra.Command { - var inputs struct { - ID string - } - cmd := &cobra.Command{ Use: "delete", Aliases: []string{"rm"}, - Args: cobra.MaximumNArgs(1), + Args: cobra.MinimumNArgs(0), Short: "Delete a rule", Long: rulesDeprecationDocumentationText + "Delete a rule.\n\n" + "To delete interactively, use `auth0 rules delete` with no arguments.\n\n" + @@ -266,13 +262,16 @@ func deleteRuleCmd(cli *cli) *cobra.Command { auth0 rules delete auth0 rules delete --force`, RunE: func(cmd *cobra.Command, args []string) error { - if len(args) > 0 { - inputs.ID = args[0] - } else { - err := ruleID.Pick(cmd, &inputs.ID, cli.rulePickerOptions) + ids := make([]string, len(args)) + if len(args) == 0 { + err := ruleID.PickMany(cmd, &ids, cli.rulePickerOptions) if err != nil { return err } + } else { + for _, id := range args { + ids = append(ids, id) + } } if !cli.force && canPrompt(cmd) { @@ -282,13 +281,17 @@ func deleteRuleCmd(cli *cli) *cobra.Command { } return ansi.Spinner("Deleting Rule", func() error { - _, err := cli.api.Rule.Read(cmd.Context(), inputs.ID) + var errs []error + for _, id := range ids { + if _, err := cli.api.Rule.Read(cmd.Context(), id); err != nil { + errs = append(errs, fmt.Errorf("Unable to read rule for deletion: %w", err)) + } - if err != nil { - return fmt.Errorf("Unable to delete rule: %w", err) + if err := cli.api.Rule.Delete(cmd.Context(), id); err != nil { + errs = append(errs, fmt.Errorf("Unable to delete rule: %w", err)) + } } - - return cli.api.Rule.Delete(cmd.Context(), inputs.ID) + return errors.Join(errs...) }) }, } diff --git a/internal/cli/users.go b/internal/cli/users.go index 1e2fad6b6..9d95e6831 100644 --- a/internal/cli/users.go +++ b/internal/cli/users.go @@ -357,14 +357,10 @@ func showUserCmd(cli *cli) *cobra.Command { } func deleteUserCmd(cli *cli) *cobra.Command { - var inputs struct { - ID string - } - cmd := &cobra.Command{ Use: "delete", Aliases: []string{"rm"}, - Args: cobra.MaximumNArgs(1), + Args: cobra.MinimumNArgs(0), Short: "Delete a user", Long: "Delete a user.\n\n" + "To delete interactively, use `auth0 users delete` with no arguments.\n\n" + @@ -374,12 +370,17 @@ func deleteUserCmd(cli *cli) *cobra.Command { auth0 users delete auth0 users delete --force`, RunE: func(cmd *cobra.Command, args []string) error { + ids := make([]string, len(args)) if len(args) == 0 { - if err := userID.Ask(cmd, &inputs.ID); err != nil { + var id string + if err := userID.Ask(cmd, &id); err != nil { return err } + ids = append(ids, id) } else { - inputs.ID = args[0] + for _, id := range args { + ids = append(ids, id) + } } if !cli.force && canPrompt(cmd) { @@ -389,13 +390,17 @@ func deleteUserCmd(cli *cli) *cobra.Command { } return ansi.Spinner("Deleting user", func() error { - _, err := cli.api.User.Read(cmd.Context(), inputs.ID) + var errs []error + for _, id := range ids { + if _, err := cli.api.User.Read(cmd.Context(), id); err != nil { + errs = append(errs, fmt.Errorf("Unable to read user for deletion: %w", err)) + } - if err != nil { - return fmt.Errorf("Unable to delete user: %w", err) + if err := cli.api.User.Delete(cmd.Context(), id); err != nil { + errs = append(errs, fmt.Errorf("Unable to delete user: %w", err)) + } } - - return cli.api.User.Delete(cmd.Context(), inputs.ID) + return errors.Join(errs...) }) }, } diff --git a/internal/cli/users_blocks.go b/internal/cli/users_blocks.go index 657a930cf..8eb75a83f 100644 --- a/internal/cli/users_blocks.go +++ b/internal/cli/users_blocks.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "github.com/auth0/go-auth0/management" @@ -64,33 +65,35 @@ func listUserBlocksCmd(cli *cli) *cobra.Command { } func deleteUserBlocksCmd(cli *cli) *cobra.Command { - var inputs struct { - userID string - } - cmd := &cobra.Command{ Use: "unblock", - Args: cobra.MaximumNArgs(1), + Args: cobra.MinimumNArgs(0), Short: "Remove brute-force protection blocks for a given user", Long: "Remove brute-force protection blocks for a given user.", Example: ` auth0 users blocks unblock `, RunE: func(cmd *cobra.Command, args []string) error { + ids := make([]string, len(args)) if len(args) == 0 { - if err := userID.Ask(cmd, &inputs.userID); err != nil { + var id string + if err := userID.Ask(cmd, &id); err != nil { return err } + ids = append(ids, id) } else { - inputs.userID = args[0] + for _, id := range args { + ids = append(ids, id) + } } - err := ansi.Spinner("Unblocking user...", func() error { - return cli.api.User.Unblock(cmd.Context(), inputs.userID) + return ansi.Spinner("Unblocking user...", func() error { + var errs []error + for _, id := range ids { + if err := cli.api.User.Unblock(cmd.Context(), id); err != nil { + errs = append(errs, fmt.Errorf("failed to unblock user with ID %s: %w", id, err)) + } + } + return errors.Join(errs...) }) - if err != nil { - return fmt.Errorf("failed to unblock user with ID %s: %w", inputs.userID, err) - } - - return nil }, }