From c8c305c25c82aa55f1fdde5f45a0594e1181755d Mon Sep 17 00:00:00 2001 From: Patrick Eschenbach Date: Mon, 26 Jan 2026 20:13:49 +0100 Subject: [PATCH 1/5] fix(utils,prompt): enhance error parsing and goroutine error handling ## Fix goroutine deadlocks and improve error handling in prompt and utils ### Problem - Goroutines were deadlocking when API calls failed because channels were not properly handled on error paths - Error parsing for HTTP status codes (e.g., 403) was incomplete - Missing error returns in functions that only returned values ### Changes - Enhanced `ParseHarborErrorCode()` to properly extract status codes from error messages - Fixed `GetRobotIDFromUser()` to close channels and return errors on failure - Updated `GetRobotPermissionsFromUser()` to handle errors through result channel instead of fataling - All prompt functions now properly propagate errors to callers Signed-off-by: Patrick Eschenbach --- cmd/harbor/root/project/robot/create.go | 5 ++- cmd/harbor/root/project/robot/delete.go | 10 ++++-- cmd/harbor/root/project/robot/refresh.go | 5 ++- cmd/harbor/root/project/robot/update.go | 15 +++++++-- cmd/harbor/root/project/robot/view.go | 10 ++++-- cmd/harbor/root/robot/create.go | 43 ++++++++++++++++++++---- cmd/harbor/root/robot/delete.go | 19 +++++++++-- cmd/harbor/root/robot/list.go | 12 +++++-- cmd/harbor/root/robot/refresh.go | 12 +++++-- cmd/harbor/root/robot/update.go | 25 +++++++++++--- cmd/harbor/root/robot/view.go | 25 +++++++++++--- cmd/harbor/root/user/create.go | 2 +- cmd/harbor/root/user/delete.go | 4 +-- cmd/harbor/root/user/elevate.go | 2 +- cmd/harbor/root/user/list.go | 4 +-- cmd/harbor/root/user/password.go | 2 +- pkg/prompt/prompt.go | 39 +++++++++++++++++---- pkg/utils/error.go | 26 ++++++++++++-- 18 files changed, 213 insertions(+), 47 deletions(-) diff --git a/cmd/harbor/root/project/robot/create.go b/cmd/harbor/root/project/robot/create.go index a4df6f6fb..6b7cbabf2 100644 --- a/cmd/harbor/root/project/robot/create.go +++ b/cmd/harbor/root/project/robot/create.go @@ -148,7 +148,10 @@ Examples: } permissions = choices } else { - permissions = prompt.GetRobotPermissionsFromUser("project") + permissions, err = prompt.GetRobotPermissionsFromUser("project") + if err != nil { + return fmt.Errorf("failed to get project permissions: %v", err) + } if len(permissions) == 0 { msg := fmt.Errorf("no permissions selected, robot account needs at least one permission") return fmt.Errorf("failed to create robot: %v", utils.ParseHarborErrorMsg(msg)) diff --git a/cmd/harbor/root/project/robot/delete.go b/cmd/harbor/root/project/robot/delete.go index 157a55d95..1473d7b3d 100644 --- a/cmd/harbor/root/project/robot/delete.go +++ b/cmd/harbor/root/project/robot/delete.go @@ -88,13 +88,19 @@ Examples: if err != nil { return fmt.Errorf("failed to get project by name %s: %v", ProjectName, utils.ParseHarborErrorMsg(err)) } - robotID = prompt.GetRobotIDFromUser(int64(project.Payload.ProjectID)) + robotID, err = prompt.GetRobotIDFromUser(int64(project.Payload.ProjectID)) + if err != nil { + return fmt.Errorf("failed to get robot ID from user: %v", utils.ParseHarborErrorMsg(err)) + } } else { projectID, err := prompt.GetProjectIDFromUser() if err != nil { log.Fatalf("failed to get project by id %d: %v", projectID, utils.ParseHarborErrorMsg(err)) } - robotID = prompt.GetRobotIDFromUser(projectID) + robotID, err = prompt.GetRobotIDFromUser(projectID) + if err != nil { + return fmt.Errorf("failed to get robot ID from user: %v", utils.ParseHarborErrorMsg(err)) + } } err = api.DeleteRobot(robotID) if err != nil { diff --git a/cmd/harbor/root/project/robot/refresh.go b/cmd/harbor/root/project/robot/refresh.go index 1d7b1c2e5..ac8c1fa36 100644 --- a/cmd/harbor/root/project/robot/refresh.go +++ b/cmd/harbor/root/project/robot/refresh.go @@ -85,7 +85,10 @@ Examples: if err != nil { log.Fatalf("failed to get project by id %d: %v", projectID, utils.ParseHarborErrorMsg(err)) } - robotID = prompt.GetRobotIDFromUser(projectID) + robotID, err = prompt.GetRobotIDFromUser(projectID) + if err != nil { + log.Fatalf("failed to get robot ID from user: %v", utils.ParseHarborErrorMsg(err)) + } } if secret != "" { diff --git a/cmd/harbor/root/project/robot/update.go b/cmd/harbor/root/project/robot/update.go index c4cf8a042..7dbaefd35 100644 --- a/cmd/harbor/root/project/robot/update.go +++ b/cmd/harbor/root/project/robot/update.go @@ -92,13 +92,19 @@ Examples: if err != nil { log.Fatalf("failed to get project by name %s: %v", ProjectName, err) } - robotID = prompt.GetRobotIDFromUser(int64(project.Payload.ProjectID)) + robotID, err = prompt.GetRobotIDFromUser(int64(project.Payload.ProjectID)) + if err != nil { + log.Fatalf("failed to get robot ID from user: %v", utils.ParseHarborErrorMsg(err)) + } } else { projectID, err := prompt.GetProjectIDFromUser() if err != nil { log.Fatalf("failed to get project by id %d: %v", projectID, utils.ParseHarborErrorMsg(err)) } - robotID = prompt.GetRobotIDFromUser(projectID) + robotID, err = prompt.GetRobotIDFromUser(projectID) + if err != nil { + log.Fatalf("failed to get robot ID from user: %v", utils.ParseHarborErrorMsg(err)) + } } robot, err := api.GetRobot(robotID) @@ -138,7 +144,10 @@ Examples: } permissions = choices } else { - permissions = prompt.GetRobotPermissionsFromUser("project") + permissions, err = prompt.GetRobotPermissionsFromUser("project") + if err != nil { + log.Fatalf("failed to get project permissions: %v", utils.ParseHarborErrorMsg(err)) + } } // []Permission to []*Access diff --git a/cmd/harbor/root/project/robot/view.go b/cmd/harbor/root/project/robot/view.go index b9554be3f..874006fce 100644 --- a/cmd/harbor/root/project/robot/view.go +++ b/cmd/harbor/root/project/robot/view.go @@ -75,13 +75,19 @@ Examples: if err != nil { log.Fatalf("failed to get project by name %s: %v", ProjectName, err) } - robotID = prompt.GetRobotIDFromUser(int64(project.Payload.ProjectID)) + robotID, err = prompt.GetRobotIDFromUser(int64(project.Payload.ProjectID)) + if err != nil { + log.Fatalf("failed to get robot ID from user: %v", utils.ParseHarborErrorMsg(err)) + } } else { projectID, err := prompt.GetProjectIDFromUser() if err != nil { log.Fatalf("failed to get project by id %d: %v", projectID, utils.ParseHarborErrorMsg(err)) } - robotID = prompt.GetRobotIDFromUser(projectID) + robotID, err = prompt.GetRobotIDFromUser(projectID) + if err != nil { + log.Fatalf("failed to get robot ID from user: %v", utils.ParseHarborErrorMsg(err)) + } } robot, err = api.GetRobot(robotID) diff --git a/cmd/harbor/root/robot/create.go b/cmd/harbor/root/robot/create.go index 268f276db..d2ff2f5ea 100644 --- a/cmd/harbor/root/robot/create.go +++ b/cmd/harbor/root/robot/create.go @@ -89,17 +89,21 @@ Examples: var projectPermissionsMap = make(map[string][]models.Permission) var accessesSystem []*models.Access + fmt.Println("1") // Handle config file or interactive input if configFile != "" { + fmt.Println("2") if err := loadFromConfigFile(&opts, configFile, &permissions, projectPermissionsMap); err != nil { return err } } else { + fmt.Println("3") if err := handleInteractiveInput(&opts, all, &permissions, projectPermissionsMap); err != nil { return err } } + fmt.Println("4") // Build system access permissions for _, perm := range permissions { accessesSystem = append(accessesSystem, &models.Access{ @@ -108,10 +112,12 @@ Examples: }) } + fmt.Println("5") // Build merged permissions structure opts.Permissions = buildMergedPermissions(projectPermissionsMap, accessesSystem) opts.Level = "system" + fmt.Println("6") // Create robot and handle response return createRobotAndHandleResponse(&opts, exportToFile) }, @@ -167,21 +173,25 @@ func loadFromConfigFile(opts *create.CreateView, configFile string, permissions } func handleInteractiveInput(opts *create.CreateView, all bool, permissions *[]models.Permission, projectPermissionsMap map[string][]models.Permission) error { + fmt.Println("3a") // Show interactive form if needed if opts.Name == "" || opts.Duration == 0 { create.CreateRobotView(opts) } + fmt.Println("3b") // Validate duration if opts.Duration == 0 { return fmt.Errorf("failed to create robot: %v", utils.ParseHarborErrorMsg(fmt.Errorf("duration cannot be 0"))) } + fmt.Println("3c") // Get system permissions if err := getSystemPermissions(all, permissions); err != nil { return err } + fmt.Println("3d") // Get project permissions return getProjectPermissions(opts, projectPermissionsMap) } @@ -189,12 +199,19 @@ func handleInteractiveInput(opts *create.CreateView, all bool, permissions *[]mo func getSystemPermissions(all bool, permissions *[]models.Permission) error { if len(*permissions) == 0 { if all { - perms, _ := api.GetPermissions() + perms, err := api.GetPermissions() + if err != nil { + return fmt.Errorf("failed to get system permissions: %v", utils.ParseHarborErrorMsg(err)) + } for _, perm := range perms.Payload.System { *permissions = append(*permissions, *perm) } } else { - *permissions = prompt.GetRobotPermissionsFromUser("system") + var err error + *permissions, err = prompt.GetRobotPermissionsFromUser("system") + if err != nil { + return fmt.Errorf("failed to create robot: %v", utils.ParseHarborErrorMsg(err)) + } if len(*permissions) == 0 { return fmt.Errorf("failed to create robot: %v", utils.ParseHarborErrorMsg(fmt.Errorf("no permissions selected, robot account needs at least one permission"))) @@ -231,7 +248,10 @@ func handleMultipleProjectsPermissions(projectPermissionsMap map[string][]models if len(selectedProjects) > 0 { fmt.Println("Select permissions to apply to all selected projects:") - projectPermissions := prompt.GetRobotPermissionsFromUser("project") + projectPermissions, err := prompt.GetRobotPermissionsFromUser("project") + if err != nil { + return fmt.Errorf("failed to get project permissions: %v", err) + } for _, projectName := range selectedProjects { projectPermissionsMap[projectName] = projectPermissions } @@ -251,7 +271,10 @@ func handlePerProjectPermissions(opts *create.CreateView, projectPermissionsMap return fmt.Errorf("project name cannot be empty") } - projectPermissionsMap[projectName] = prompt.GetRobotPermissionsFromUser("project") + projectPermissionsMap[projectName], err = prompt.GetRobotPermissionsFromUser("project") + if err != nil { + return fmt.Errorf("failed to get project permissions: %v", err) + } moreProjects, err := promptMoreProjects() if err != nil { @@ -262,7 +285,10 @@ func handlePerProjectPermissions(opts *create.CreateView, projectPermissionsMap } } } else { - projectPermissions := prompt.GetRobotPermissionsFromUser("project") + projectPermissions, err := prompt.GetRobotPermissionsFromUser("project") + if err != nil { + return fmt.Errorf("failed to get project permissions: %v", err) + } projectPermissionsMap[opts.ProjectName] = projectPermissions } @@ -301,7 +327,12 @@ func buildMergedPermissions(projectPermissionsMap map[string][]models.Permission func createRobotAndHandleResponse(opts *create.CreateView, exportToFile bool) error { response, err := api.CreateRobot(*opts) if err != nil { - return fmt.Errorf("failed to create robot: %v", utils.ParseHarborErrorMsg(err)) + errorCode := utils.ParseHarborErrorCode(err) + if errorCode == "403" { + return fmt.Errorf("Permission denied: (Project) Admin privileges are required to execute this command.") + } else { + return fmt.Errorf("failed to create robot: %v", utils.ParseHarborErrorMsg(err)) + } } logrus.Infof("Successfully created robot account '%s' (ID: %d)", diff --git a/cmd/harbor/root/robot/delete.go b/cmd/harbor/root/robot/delete.go index 70122427a..7cf95e6af 100644 --- a/cmd/harbor/root/robot/delete.go +++ b/cmd/harbor/root/robot/delete.go @@ -60,15 +60,28 @@ Examples: robotName := args[0] robot, err := api.GetRobotByName(robotName) if err != nil { - return fmt.Errorf("failed to delete robots: %v", utils.ParseHarborErrorMsg(err)) + errorCode := utils.ParseHarborErrorCode(err) + if errorCode == "403" { + return fmt.Errorf("Permission denied: (Project) Admin privileges are required to execute this command.") + } else { + return fmt.Errorf("failed to get robot: %v", utils.ParseHarborErrorMsg(err)) + } } robotID = robot.ID } else { - robotID = prompt.GetRobotIDFromUser(-1) + robotID, err = prompt.GetRobotIDFromUser(-1) + if err != nil { + return fmt.Errorf("failed to get robot ID from user: %v", utils.ParseHarborErrorMsg(err)) + } } err = api.DeleteRobot(robotID) if err != nil { - return fmt.Errorf("failed to delete robots: %v", utils.ParseHarborErrorMsg(err)) + errorCode := utils.ParseHarborErrorCode(err) + if errorCode == "403" { + return fmt.Errorf("Permission denied: (Project) Admin privileges are required to execute this command.") + } else { + return fmt.Errorf("failed to delete robot: %v", utils.ParseHarborErrorMsg(err)) + } } fmt.Printf("Robot account (ID: %d) was successfully deleted\n", robotID) return nil diff --git a/cmd/harbor/root/robot/list.go b/cmd/harbor/root/robot/list.go index 80b4427d9..682ab9519 100644 --- a/cmd/harbor/root/robot/list.go +++ b/cmd/harbor/root/robot/list.go @@ -14,6 +14,8 @@ package robot import ( + "fmt" + "github.com/goharbor/harbor-cli/pkg/api" "github.com/goharbor/harbor-cli/pkg/utils" "github.com/goharbor/harbor-cli/pkg/views/robot/list" @@ -61,10 +63,15 @@ Examples: # Get robot details in JSON format harbor-cli robot list --output-format json`, Args: cobra.MaximumNArgs(0), - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { robots, err := api.ListRobot(opts) if err != nil { - log.Errorf("failed to get robots list: %v", utils.ParseHarborErrorMsg(err)) + errorCode := utils.ParseHarborErrorCode(err) + if errorCode == "403" { + return fmt.Errorf("Permission denied: (Project) Admin privileges are required to execute this command.") + } else { + return fmt.Errorf("failed to list robots: %v", utils.ParseHarborErrorMsg(err)) + } } formatFlag := viper.GetString("output-format") @@ -76,6 +83,7 @@ Examples: } else { list.ListRobots(robots.Payload) } + return nil }, } diff --git a/cmd/harbor/root/robot/refresh.go b/cmd/harbor/root/robot/refresh.go index 20d8bc6d9..759c67e55 100644 --- a/cmd/harbor/root/robot/refresh.go +++ b/cmd/harbor/root/robot/refresh.go @@ -81,7 +81,10 @@ Examples: log.Fatalf("failed to parse robot ID: %v", err) } } else { - robotID = prompt.GetRobotIDFromUser(-1) + robotID, err = prompt.GetRobotIDFromUser(-1) + if err != nil { + log.Fatalf("failed to get robot ID from user: %v", utils.ParseHarborErrorMsg(err)) + } } if secret != "" { @@ -96,7 +99,12 @@ Examples: response, err := api.RefreshSecret(secret, robotID) if err != nil { - log.Fatalf("failed to refresh robot secret: %v\n", err) + errorCode := utils.ParseHarborErrorCode(err) + if errorCode == "403" { + log.Fatalf("Permission denied: (Project) Admin privileges are required to execute this command.\n") + } else { + log.Fatalf("failed to refresh robot secret: %v\n", utils.ParseHarborErrorMsg(err)) + } } log.Info("Secret updated successfully.") diff --git a/cmd/harbor/root/robot/update.go b/cmd/harbor/root/robot/update.go index 7da9bcf08..981625f01 100644 --- a/cmd/harbor/root/robot/update.go +++ b/cmd/harbor/root/robot/update.go @@ -95,7 +95,10 @@ Examples: return fmt.Errorf("failed to parse robot ID: %v", err) } } else { - robotID = prompt.GetRobotIDFromUser(-1) + robotID, err = prompt.GetRobotIDFromUser(-1) + if err != nil { + return fmt.Errorf("failed to get robot ID from user: %v", utils.ParseHarborErrorMsg(err)) + } } // Get current robot configuration @@ -306,7 +309,10 @@ func getSystemPermissionsForUpdate(all bool, permissions *[]models.Permission) e *permissions = append(*permissions, *perm) } } else { - newPermissions := prompt.GetRobotPermissionsFromUser("system") + newPermissions, err := prompt.GetRobotPermissionsFromUser("system") + if err != nil { + return fmt.Errorf("failed to update robot: %v", utils.ParseHarborErrorMsg(err)) + } if len(newPermissions) == 0 { return fmt.Errorf("failed to update robot: %v", utils.ParseHarborErrorMsg(fmt.Errorf("no permissions selected, robot account needs at least one permission"))) @@ -377,7 +383,10 @@ func handleMultipleProjectsPermissionsForUpdate(projectPermissionsMap map[string if len(selectedProjects) > 0 { fmt.Println("Select permissions to apply to all selected projects:") - projectPermissions := prompt.GetRobotPermissionsFromUser("project") + projectPermissions, err := prompt.GetRobotPermissionsFromUser("project") + if err != nil { + return fmt.Errorf("failed to update robot: %v", utils.ParseHarborErrorMsg(err)) + } // Validate project permissions validProjectPerms, err := validateProjectPermissions(projectPermissions) @@ -449,7 +458,10 @@ func handlePerProjectPermissionsForUpdate(projectPermissionsMap map[string][]mod // Update permissions for selected projects for _, project := range selectedProjects { fmt.Printf("Updating permissions for project: %s\n", project) - projectPerms := prompt.GetRobotPermissionsFromUser("project") + projectPerms, err := prompt.GetRobotPermissionsFromUser("project") + if err != nil { + return fmt.Errorf("failed to update robot: %v", utils.ParseHarborErrorMsg(err)) + } // Validate project permissions validProjectPerms, err := validateProjectPermissions(projectPerms) @@ -474,7 +486,10 @@ func handlePerProjectPermissionsForUpdate(projectPermissionsMap map[string][]mod return fmt.Errorf("project name cannot be empty") } - projectPerms := prompt.GetRobotPermissionsFromUser("project") + projectPerms, err := prompt.GetRobotPermissionsFromUser("project") + if err != nil { + return fmt.Errorf("failed to update robot: %v", utils.ParseHarborErrorMsg(err)) + } // Validate project permissions validProjectPerms, err := validateProjectPermissions(projectPerms) diff --git a/cmd/harbor/root/robot/view.go b/cmd/harbor/root/robot/view.go index c160034b0..4988519ff 100644 --- a/cmd/harbor/root/robot/view.go +++ b/cmd/harbor/root/robot/view.go @@ -14,13 +14,14 @@ package robot import ( + "fmt" "strconv" "github.com/goharbor/go-client/pkg/sdk/v2.0/client/robot" "github.com/goharbor/harbor-cli/pkg/api" "github.com/goharbor/harbor-cli/pkg/prompt" + "github.com/goharbor/harbor-cli/pkg/utils" "github.com/goharbor/harbor-cli/pkg/views/robot/view" - log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -56,7 +57,7 @@ Examples: # Interactive selection (will prompt for robot) harbor-cli robot view`, Args: cobra.MaximumNArgs(1), - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { var ( robot *robot.GetRobotByIDOK robotID int64 @@ -66,20 +67,34 @@ Examples: if len(args) == 1 { robotID, err = strconv.ParseInt(args[0], 10, 64) if err != nil { - log.Fatalf("failed to parse robot ID: %v", err) + errorCode := utils.ParseHarborErrorCode(err) + if errorCode == "403" { + return fmt.Errorf("Permission denied: (Project) Admin privileges are required to execute this command.") + } else { + return fmt.Errorf("failed to parse robot ID: %v", utils.ParseHarborErrorMsg(err)) + } } } else { - robotID = prompt.GetRobotIDFromUser(-1) + robotID, err = prompt.GetRobotIDFromUser(-1) + if err != nil { + return fmt.Errorf("failed to get robot ID from user: %v", utils.ParseHarborErrorMsg(err)) + } } robot, err = api.GetRobot(robotID) if err != nil { - log.Fatalf("failed to get robot: %v", err) + errorCode := utils.ParseHarborErrorCode(err) + if errorCode == "403" { + return fmt.Errorf("Permission denied: (Project) Admin privileges are required to execute this command.") + } else { + return fmt.Errorf("failed to get robot: %v", utils.ParseHarborErrorMsg(err)) + } } // Convert to a list and display // robots := &models.Robot{robot.Payload} view.ViewRobot(robot.Payload) + return nil }, } diff --git a/cmd/harbor/root/user/create.go b/cmd/harbor/root/user/create.go index bd705e3d9..7eda10cc4 100644 --- a/cmd/harbor/root/user/create.go +++ b/cmd/harbor/root/user/create.go @@ -50,7 +50,7 @@ func UserCreateCmd() *cobra.Command { if err != nil { if isUnauthorizedError(err) { - log.Error("Permission denied: Admin privileges are required to execute this command.") + log.Error("Permission denied: (Project) Admin privileges are required to execute this command.") } else { log.Errorf("failed to create user: %v", err) } diff --git a/cmd/harbor/root/user/delete.go b/cmd/harbor/root/user/delete.go index eaa2b1f4c..69c2f823e 100644 --- a/cmd/harbor/root/user/delete.go +++ b/cmd/harbor/root/user/delete.go @@ -59,7 +59,7 @@ func UserDeleteCmd() *cobra.Command { // Process errors from the goroutines. for err := range errChan { if isUnauthorizedError(err) { - log.Error("Permission denied: Admin privileges are required to execute this command.") + log.Error("Permission denied: (Project) Admin privileges are required to execute this command.") } else { log.Errorf("failed to delete user: %v", err) } @@ -69,7 +69,7 @@ func UserDeleteCmd() *cobra.Command { 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.") + log.Error("Permission denied: (Project) Admin privileges are required to execute this command.") } else { log.Errorf("failed to delete user: %v", err) } diff --git a/cmd/harbor/root/user/elevate.go b/cmd/harbor/root/user/elevate.go index 060f83805..9b63f0f6f 100644 --- a/cmd/harbor/root/user/elevate.go +++ b/cmd/harbor/root/user/elevate.go @@ -56,7 +56,7 @@ func ElevateUserCmd() *cobra.Command { err = api.ElevateUser(userId) if err != nil { if isUnauthorizedError(err) { - log.Error("Permission denied: Admin privileges are required to execute this command.") + log.Error("Permission denied: (Project) Admin privileges are required to execute this command.") } else { log.Errorf("failed to elevate user: %v", err) } diff --git a/cmd/harbor/root/user/list.go b/cmd/harbor/root/user/list.go index 7bad0326d..fd38c0472 100644 --- a/cmd/harbor/root/user/list.go +++ b/cmd/harbor/root/user/list.go @@ -47,7 +47,7 @@ func UserListCmd() *cobra.Command { response, err := api.ListUsers(opts) if err != nil { if isUnauthorizedError(err) { - return fmt.Errorf("Permission denied: Admin privileges are required to execute this command.") + return fmt.Errorf("Permission denied: (Project) Admin privileges are required to execute this command.") } return fmt.Errorf("failed to list users: %v", err) } @@ -63,7 +63,7 @@ func UserListCmd() *cobra.Command { response, err := api.ListUsers(opts) if err != nil { if isUnauthorizedError(err) { - return fmt.Errorf("Permission denied: Admin privileges are required to execute this command.") + return fmt.Errorf("Permission denied: (Project) Admin privileges are required to execute this command.") } return fmt.Errorf("failed to list users: %v", err) } diff --git a/cmd/harbor/root/user/password.go b/cmd/harbor/root/user/password.go index 34a6ce98a..6aeca221c 100644 --- a/cmd/harbor/root/user/password.go +++ b/cmd/harbor/root/user/password.go @@ -57,7 +57,7 @@ func UserPasswordChangeCmd() *cobra.Command { err = api.ResetPassword(userId, opts) if err != nil { if isUnauthorizedError(err) { - log.Error("Permission denied: Admin privileges are required to execute this command.") + log.Error("Permission denied: (Project) Admin privileges are required to execute this command.") } else { log.Errorf("failed to reset user password: %v", err) } diff --git a/pkg/prompt/prompt.go b/pkg/prompt/prompt.go index e33e4ac2b..3b0c55379 100644 --- a/pkg/prompt/prompt.go +++ b/pkg/prompt/prompt.go @@ -316,16 +316,28 @@ func GetActiveContextFromUser() (string, error) { return res, nil } -func GetRobotPermissionsFromUser(kind string) []models.Permission { +func GetRobotPermissionsFromUser(kind string) ([]models.Permission, error) { + type result struct { + permissions []models.Permission + err error + } permissions := make(chan []models.Permission) + results := make(chan result) go func() { - response, _ := api.GetPermissions() + response, err := api.GetPermissions() + if err != nil { + fmt.Println("Permission denied: (Project) Admin privileges are required to execute this command.") + results <- result{nil, err} + return + } robotView.ListPermissions(response.Payload, kind, permissions) + results <- result{<-permissions, nil} }() - return <-permissions + res := <-results + return res.permissions, res.err } -func GetRobotIDFromUser(projectID int64) int64 { +func GetRobotIDFromUser(projectID int64) (int64, error) { robotID := make(chan int64) var opts api.ListFlags if projectID != -1 { @@ -333,10 +345,25 @@ func GetRobotIDFromUser(projectID int64) int64 { } go func() { - response, _ := api.ListRobot(opts) + response, err := api.ListRobot(opts) + if err != nil { + errorCode := utils.ParseHarborErrorCode(err) + if errorCode == "403" { + fmt.Println("Permission denied: (Project) Admin privileges are required to execute this command.") + } else { + fmt.Printf("failed to list robots: %v\n", utils.ParseHarborErrorMsg(err)) + } + close(robotID) + return + } robotView.ListRobot(response.Payload, robotID) }() - return <-robotID + + id, ok := <-robotID + if !ok { + return 0, errors.New("failed to retrieve robot ID") + } + return id, nil } func GetReplicationPolicyFromUser() int64 { diff --git a/pkg/utils/error.go b/pkg/utils/error.go index c1d05824b..05fdfda3f 100644 --- a/pkg/utils/error.go +++ b/pkg/utils/error.go @@ -53,13 +53,35 @@ func ParseHarborErrorMsg(err error) string { } func ParseHarborErrorCode(err error) string { - parts := strings.Split(err.Error(), "]") + if err == nil { + return "" + } + + errMsg := err.Error() + + // Handle: "response status code does not match any response statuses defined for this endpoint in the swagger spec (status 403): {}" + if strings.Contains(errMsg, "(status ") { + // Extract content between "(status " and ")" + startIdx := strings.Index(errMsg, "(status ") + if startIdx != -1 { + startIdx += len("(status ") + endIdx := strings.Index(errMsg[startIdx:], ")") + if endIdx != -1 { + statusCode := strings.TrimSpace(errMsg[startIdx : startIdx+endIdx]) + return statusCode + } + } + } + + // Handle: "[code] message" format + parts := strings.Split(errMsg, "]") if len(parts) >= 2 { codePart := strings.TrimSpace(parts[1]) - if strings.HasPrefix(codePart, "[") && len(codePart) == 4 { + if strings.HasPrefix(codePart, "[") && len(codePart) >= 4 { code := codePart[1:4] return code } } + return "" } From ce02804d94bbe1b6d745e136bf5db7e00499047d Mon Sep 17 00:00:00 2001 From: Patrick Eschenbach <45457307+qcserestipy@users.noreply.github.com> Date: Tue, 27 Jan 2026 07:46:12 +0100 Subject: [PATCH 2/5] Update pkg/utils/error.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Patrick Eschenbach <45457307+qcserestipy@users.noreply.github.com> --- pkg/utils/error.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/utils/error.go b/pkg/utils/error.go index 05fdfda3f..bc885116a 100644 --- a/pkg/utils/error.go +++ b/pkg/utils/error.go @@ -77,7 +77,7 @@ func ParseHarborErrorCode(err error) string { parts := strings.Split(errMsg, "]") if len(parts) >= 2 { codePart := strings.TrimSpace(parts[1]) - if strings.HasPrefix(codePart, "[") && len(codePart) >= 4 { + if strings.HasPrefix(codePart, "[") && len(codePart) == 4 { code := codePart[1:4] return code } From f21568c3d9323b634f61ad8e4cf28db0cf2bbe16 Mon Sep 17 00:00:00 2001 From: Patrick Eschenbach <45457307+qcserestipy@users.noreply.github.com> Date: Tue, 27 Jan 2026 07:47:39 +0100 Subject: [PATCH 3/5] Update cmd/harbor/root/robot/view.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Patrick Eschenbach <45457307+qcserestipy@users.noreply.github.com> --- cmd/harbor/root/robot/view.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/cmd/harbor/root/robot/view.go b/cmd/harbor/root/robot/view.go index 4988519ff..b9fdf357c 100644 --- a/cmd/harbor/root/robot/view.go +++ b/cmd/harbor/root/robot/view.go @@ -67,12 +67,7 @@ Examples: if len(args) == 1 { robotID, err = strconv.ParseInt(args[0], 10, 64) if err != nil { - errorCode := utils.ParseHarborErrorCode(err) - if errorCode == "403" { - return fmt.Errorf("Permission denied: (Project) Admin privileges are required to execute this command.") - } else { - return fmt.Errorf("failed to parse robot ID: %v", utils.ParseHarborErrorMsg(err)) - } + return fmt.Errorf("failed to parse robot ID %q: %v", args[0], err) } } else { robotID, err = prompt.GetRobotIDFromUser(-1) From 3487f25af1aba50262a75f3e5711f03509c88e46 Mon Sep 17 00:00:00 2001 From: Patrick Eschenbach Date: Tue, 27 Jan 2026 07:48:59 +0100 Subject: [PATCH 4/5] fix(cleanup): remove left over debug statements Signed-off-by: Patrick Eschenbach --- cmd/harbor/root/robot/create.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/cmd/harbor/root/robot/create.go b/cmd/harbor/root/robot/create.go index d2ff2f5ea..ee73feafe 100644 --- a/cmd/harbor/root/robot/create.go +++ b/cmd/harbor/root/robot/create.go @@ -89,21 +89,17 @@ Examples: var projectPermissionsMap = make(map[string][]models.Permission) var accessesSystem []*models.Access - fmt.Println("1") // Handle config file or interactive input if configFile != "" { - fmt.Println("2") if err := loadFromConfigFile(&opts, configFile, &permissions, projectPermissionsMap); err != nil { return err } } else { - fmt.Println("3") if err := handleInteractiveInput(&opts, all, &permissions, projectPermissionsMap); err != nil { return err } } - fmt.Println("4") // Build system access permissions for _, perm := range permissions { accessesSystem = append(accessesSystem, &models.Access{ @@ -112,12 +108,10 @@ Examples: }) } - fmt.Println("5") // Build merged permissions structure opts.Permissions = buildMergedPermissions(projectPermissionsMap, accessesSystem) opts.Level = "system" - fmt.Println("6") // Create robot and handle response return createRobotAndHandleResponse(&opts, exportToFile) }, @@ -173,25 +167,21 @@ func loadFromConfigFile(opts *create.CreateView, configFile string, permissions } func handleInteractiveInput(opts *create.CreateView, all bool, permissions *[]models.Permission, projectPermissionsMap map[string][]models.Permission) error { - fmt.Println("3a") // Show interactive form if needed if opts.Name == "" || opts.Duration == 0 { create.CreateRobotView(opts) } - fmt.Println("3b") // Validate duration if opts.Duration == 0 { return fmt.Errorf("failed to create robot: %v", utils.ParseHarborErrorMsg(fmt.Errorf("duration cannot be 0"))) } - fmt.Println("3c") // Get system permissions if err := getSystemPermissions(all, permissions); err != nil { return err } - fmt.Println("3d") // Get project permissions return getProjectPermissions(opts, projectPermissionsMap) } From 1402e06a91978d5ebfa4f80281497b7f9175e21e Mon Sep 17 00:00:00 2001 From: Patrick Eschenbach Date: Tue, 27 Jan 2026 07:55:16 +0100 Subject: [PATCH 5/5] fix(cleanup): remove wrong print statements for user command Signed-off-by: Patrick Eschenbach --- cmd/harbor/root/user/create.go | 2 +- cmd/harbor/root/user/delete.go | 4 ++-- cmd/harbor/root/user/elevate.go | 2 +- cmd/harbor/root/user/list.go | 4 ++-- cmd/harbor/root/user/password.go | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/harbor/root/user/create.go b/cmd/harbor/root/user/create.go index 7eda10cc4..bd705e3d9 100644 --- a/cmd/harbor/root/user/create.go +++ b/cmd/harbor/root/user/create.go @@ -50,7 +50,7 @@ func UserCreateCmd() *cobra.Command { if err != nil { if isUnauthorizedError(err) { - log.Error("Permission denied: (Project) Admin privileges are required to execute this command.") + log.Error("Permission denied: Admin privileges are required to execute this command.") } else { log.Errorf("failed to create user: %v", err) } diff --git a/cmd/harbor/root/user/delete.go b/cmd/harbor/root/user/delete.go index 69c2f823e..eaa2b1f4c 100644 --- a/cmd/harbor/root/user/delete.go +++ b/cmd/harbor/root/user/delete.go @@ -59,7 +59,7 @@ func UserDeleteCmd() *cobra.Command { // Process errors from the goroutines. for err := range errChan { if isUnauthorizedError(err) { - log.Error("Permission denied: (Project) Admin privileges are required to execute this command.") + log.Error("Permission denied: Admin privileges are required to execute this command.") } else { log.Errorf("failed to delete user: %v", err) } @@ -69,7 +69,7 @@ func UserDeleteCmd() *cobra.Command { userID := prompt.GetUserIdFromUser() if err := api.DeleteUser(userID); err != nil { if isUnauthorizedError(err) { - log.Error("Permission denied: (Project) Admin privileges are required to execute this command.") + log.Error("Permission denied: Admin privileges are required to execute this command.") } else { log.Errorf("failed to delete user: %v", err) } diff --git a/cmd/harbor/root/user/elevate.go b/cmd/harbor/root/user/elevate.go index 9b63f0f6f..060f83805 100644 --- a/cmd/harbor/root/user/elevate.go +++ b/cmd/harbor/root/user/elevate.go @@ -56,7 +56,7 @@ func ElevateUserCmd() *cobra.Command { err = api.ElevateUser(userId) if err != nil { if isUnauthorizedError(err) { - log.Error("Permission denied: (Project) Admin privileges are required to execute this command.") + log.Error("Permission denied: Admin privileges are required to execute this command.") } else { log.Errorf("failed to elevate user: %v", err) } diff --git a/cmd/harbor/root/user/list.go b/cmd/harbor/root/user/list.go index fd38c0472..7bad0326d 100644 --- a/cmd/harbor/root/user/list.go +++ b/cmd/harbor/root/user/list.go @@ -47,7 +47,7 @@ func UserListCmd() *cobra.Command { response, err := api.ListUsers(opts) if err != nil { if isUnauthorizedError(err) { - return fmt.Errorf("Permission denied: (Project) Admin privileges are required to execute this command.") + return fmt.Errorf("Permission denied: Admin privileges are required to execute this command.") } return fmt.Errorf("failed to list users: %v", err) } @@ -63,7 +63,7 @@ func UserListCmd() *cobra.Command { response, err := api.ListUsers(opts) if err != nil { if isUnauthorizedError(err) { - return fmt.Errorf("Permission denied: (Project) Admin privileges are required to execute this command.") + return fmt.Errorf("Permission denied: Admin privileges are required to execute this command.") } return fmt.Errorf("failed to list users: %v", err) } diff --git a/cmd/harbor/root/user/password.go b/cmd/harbor/root/user/password.go index 6aeca221c..34a6ce98a 100644 --- a/cmd/harbor/root/user/password.go +++ b/cmd/harbor/root/user/password.go @@ -57,7 +57,7 @@ func UserPasswordChangeCmd() *cobra.Command { err = api.ResetPassword(userId, opts) if err != nil { if isUnauthorizedError(err) { - log.Error("Permission denied: (Project) Admin privileges are required to execute this command.") + log.Error("Permission denied: Admin privileges are required to execute this command.") } else { log.Errorf("failed to reset user password: %v", err) }