diff --git a/cmd/harbor/root/user/delete.go b/cmd/harbor/root/user/delete.go index eaa2b1f4c..329858e04 100644 --- a/cmd/harbor/root/user/delete.go +++ b/cmd/harbor/root/user/delete.go @@ -14,67 +14,127 @@ package user import ( + "fmt" "sync" + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" "github.com/goharbor/harbor-cli/pkg/api" "github.com/goharbor/harbor-cli/pkg/prompt" + "github.com/goharbor/harbor-cli/pkg/utils" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) +func fetchAllUsers() ([]*models.UserResp, error) { + opts := api.ListFlags{Page: 1, PageSize: 100} + var allUsers []*models.UserResp + for { + response, err := api.ListUsers(opts) + if err != nil { + return nil, err + } + + allUsers = append(allUsers, response.Payload...) + + if len(response.Payload) < int(opts.PageSize) { + break + } + opts.Page++ + } + return allUsers, nil +} + // UserDeleteCmd defines the "delete" command for user deletion. func UserDeleteCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "delete", - Short: "delete user by name or id", + Use: "delete [username...]", + Short: "Delete users by name or interactive prompt", Args: cobra.MinimumNArgs(0), - Run: func(cmd *cobra.Command, args []string) { - // If there are command line arguments, process them concurrently. - if len(args) > 0 { - var wg sync.WaitGroup - errChan := make(chan error, len(args)) // Channel to collect errors - - for _, arg := range args { - // Retrieve user ID by name. - userID, err := api.GetUsersIdByName(arg) - if err != nil { - log.Errorf("failed to get user id for '%s': %v", arg, err) - continue - } - wg.Add(1) - go func(userID int64) { - defer wg.Done() - if err := api.DeleteUser(userID); err != nil { - errChan <- err - } - }(userID) + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + userID := prompt.GetUserIdFromUser() + if err := api.DeleteUser(userID); err != nil { + return fmt.Errorf("failed to delete user: %s", utils.ParseHarborErrorMsg(err)) } + fmt.Println("User deleted successfully") + return nil + } - // Wait for all goroutines to finish and then close the error channel. - go func() { - wg.Wait() - close(errChan) - }() + allUsers, err := fetchAllUsers() + if err != nil { + return fmt.Errorf("failed to list users: %v", utils.ParseHarborErrorMsg(err)) + } + userMap := make(map[string]int64) + for _, u := range allUsers { + userMap[u.Username] = u.UserID + } - // Process errors from the goroutines. - for err := range errChan { - if isUnauthorizedError(err) { - log.Error("Permission denied: Admin privileges are required to execute this command.") - } else { - log.Errorf("failed to delete user: %v", err) - } + type user struct { + name string + id int64 + } + var resolved []user + failedResolves := map[string]string{} + + for _, name := range args { + id, found := userMap[name] + if !found { + failedResolves[name] = "user not found" + continue } - } else { - // Interactive mode: get the user ID from the prompt. - userID := prompt.GetUserIdFromUser() - if err := api.DeleteUser(userID); err != nil { - if isUnauthorizedError(err) { - log.Error("Permission denied: Admin privileges are required to execute this command.") + resolved = append(resolved, user{name: name, id: id}) + } + + var wg sync.WaitGroup + var mu sync.Mutex + successfulDeletes := []string{} + failedDeletes := map[string]string{} + + for _, u := range resolved { + wg.Add(1) + go func(u user) { + defer wg.Done() + if err := api.DeleteUser(u.id); err != nil { + mu.Lock() + failedDeletes[u.name] = utils.ParseHarborErrorMsg(err) + mu.Unlock() } else { - log.Errorf("failed to delete user: %v", err) + mu.Lock() + successfulDeletes = append(successfulDeletes, u.name) + mu.Unlock() } + }(u) + } + wg.Wait() + + if len(successfulDeletes) > 0 { + fmt.Println("Successfully deleted users:") + for _, name := range successfulDeletes { + fmt.Printf(" - %s\n", name) } } + + if len(failedResolves) > 0 { + fmt.Println("Failed to resolve users:") + for name, reason := range failedResolves { + fmt.Printf(" - %s: %s\n", name, reason) + } + } + + if len(failedDeletes) > 0 { + fmt.Println("Failed to delete users:") + for name, reason := range failedDeletes { + fmt.Printf(" - %s: %s\n", name, reason) + } + } + + totFailed := len(failedResolves) + len(failedDeletes) + if totFailed > 0 { + return fmt.Errorf("failed to delete %d user(s)", totFailed) + } + + log.Debug("All requested users processed for deletion successfully.") + return nil }, } diff --git a/doc/cli-docs/harbor-user-delete.md b/doc/cli-docs/harbor-user-delete.md index b542c9485..19e883a7b 100644 --- a/doc/cli-docs/harbor-user-delete.md +++ b/doc/cli-docs/harbor-user-delete.md @@ -6,10 +6,10 @@ weight: 185 ### Description -##### delete user by name or id +##### Delete users by name or interactive prompt ```sh -harbor user delete [flags] +harbor user delete [username...] [flags] ``` ### Options diff --git a/doc/cli-docs/harbor-user.md b/doc/cli-docs/harbor-user.md index e2767358c..a8772e448 100644 --- a/doc/cli-docs/harbor-user.md +++ b/doc/cli-docs/harbor-user.md @@ -36,7 +36,7 @@ Manage users in Harbor * [harbor](harbor.md) - Official Harbor CLI * [harbor user create](harbor-user-create.md) - create user -* [harbor user delete](harbor-user-delete.md) - delete user by name or id +* [harbor user delete](harbor-user-delete.md) - Delete users by name or interactive prompt * [harbor user elevate](harbor-user-elevate.md) - elevate user * [harbor user list](harbor-user-list.md) - List users * [harbor user password](harbor-user-password.md) - Reset user password by name or id diff --git a/doc/man-docs/man1/harbor-user-delete.1 b/doc/man-docs/man1/harbor-user-delete.1 index eb20078cf..b3b45b21f 100644 --- a/doc/man-docs/man1/harbor-user-delete.1 +++ b/doc/man-docs/man1/harbor-user-delete.1 @@ -2,15 +2,15 @@ .TH "HARBOR" "1" "Harbor Community" "Harbor User Manuals" .SH NAME -harbor-user-delete - delete user by name or id +harbor-user-delete - Delete users by name or interactive prompt .SH SYNOPSIS -\fBharbor user delete [flags]\fP +\fBharbor user delete [username...] [flags]\fP .SH DESCRIPTION -delete user by name or id +Delete users by name or interactive prompt .SH OPTIONS