Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 101 additions & 41 deletions cmd/harbor/root/user/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
}

Expand Down
4 changes: 2 additions & 2 deletions doc/cli-docs/harbor-user-delete.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion doc/cli-docs/harbor-user.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions doc/man-docs/man1/harbor-user-delete.1
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading