diff --git a/cmd/harbor/root/project/member/create.go b/cmd/harbor/root/project/member/create.go index 5fba323fe..98a1fa89c 100644 --- a/cmd/harbor/root/project/member/create.go +++ b/cmd/harbor/root/project/member/create.go @@ -16,9 +16,9 @@ package member import ( "fmt" + "strings" "github.com/goharbor/go-client/pkg/sdk/v2.0/models" - "github.com/sirupsen/logrus" "github.com/goharbor/harbor-cli/pkg/api" "github.com/goharbor/harbor-cli/pkg/prompt" @@ -28,6 +28,37 @@ import ( "github.com/spf13/cobra" ) +// roleFlagAliases maps every accepted spelling of --role to a canonical +// Harbor role ID. Keys are pre-normalized (lowercase, separators stripped). +var roleFlagAliases = map[string]int{ + "projectadmin": 1, "admin": 1, + "developer": 2, + "guest": 3, + "maintainer": 4, + "limitedguest": 5, +} + +// resolveRoleFlags collapses --role and --roleid into one canonical Harbor +// role ID (1..5). (0, nil) means "no role specified" — the interactive view +// will prompt for one. +func resolveRoleFlags(roleName string, roleID int) (int, error) { + if roleName != "" { + key := strings.ToLower(strings.NewReplacer("_", "", " ", "", "-", "").Replace(roleName)) + matched, ok := roleFlagAliases[key] + if !ok { + return 0, fmt.Errorf("invalid --role %q (expected one of: Project_Admin, Developer, Guest, Maintainer, Limited_Guest)", roleName) + } + if roleID != 0 && roleID != matched { + return 0, fmt.Errorf("--role %q (id %d) conflicts with --roleid %d", roleName, matched, roleID) + } + roleID = matched + } + if roleID != 0 && (roleID < 1 || roleID > 5) { + return 0, fmt.Errorf("invalid --roleid %d (must be 1=Admin, 2=Developer, 3=Guest, 4=Maintainer, 5=LimitedGuest)", roleID) + } + return roleID, nil +} + func CreateMemberCommand() *cobra.Command { var opts create.CreateView opts.MemberUser = &models.UserEntity{} // Initialize MemberUser @@ -59,9 +90,14 @@ func CreateMemberCommand() *cobra.Command { } } + opts.RoleID, err = resolveRoleFlags(opts.RoleName, opts.RoleID) + if err != nil { + return err + } + sysInfo, err := api.GetSystemInfo() if err != nil { - fmt.Println("could not access server info") + return fmt.Errorf("could not access server info: %v", utils.ParseHarborErrorMsg(err)) } createView := &create.CreateView{ @@ -81,7 +117,6 @@ func CreateMemberCommand() *cobra.Command { }, } - // check if role and member is valid if opts.RoleID != 0 && opts.MemberUser.Username != "" { err = api.CreateMember(*createView) } else { @@ -89,7 +124,7 @@ func CreateMemberCommand() *cobra.Command { } if err != nil { - logrus.Errorf("failed to create user: %v", err) + return fmt.Errorf("failed to create member: %v", utils.ParseHarborErrorMsg(err)) } fmt.Printf("successfully added user %s to project %s\n", createView.MemberUser.Username, opts.ProjectName) diff --git a/pkg/api/instance_handler.go b/pkg/api/instance_handler.go index df951ecca..aa7d15c93 100644 --- a/pkg/api/instance_handler.go +++ b/pkg/api/instance_handler.go @@ -22,6 +22,7 @@ import ( "github.com/goharbor/go-client/pkg/sdk/v2.0/models" "github.com/goharbor/harbor-cli/pkg/utils" "github.com/goharbor/harbor-cli/pkg/views/instance/create" + log "github.com/sirupsen/logrus" ) func CreateInstance(opts create.CreateView) error { diff --git a/pkg/api/member_handler.go b/pkg/api/member_handler.go index ceff19240..b05ab2bd9 100644 --- a/pkg/api/member_handler.go +++ b/pkg/api/member_handler.go @@ -80,7 +80,7 @@ func CreateMember(opts create.CreateView) error { ctx, &member.CreateProjectMemberParams{ XIsResourceName: &opts.XIsResourceID, ProjectMember: &models.ProjectMember{ - RoleID: int64(opts.RoleID + 1), + RoleID: int64(opts.RoleID), MemberUser: opts.MemberUser, MemberGroup: opts.MemberGroup, }, diff --git a/pkg/api/registry_handler.go b/pkg/api/registry_handler.go index 42d075705..c2b66beb7 100644 --- a/pkg/api/registry_handler.go +++ b/pkg/api/registry_handler.go @@ -122,10 +122,10 @@ func GetRegistryResponse(registryId int64) (*models.Registry, error) { return nil, err } if response.Payload.ID == 0 { - return nil, err + return nil, fmt.Errorf("registry %d not found", registryId) } - return response.GetPayload(), err + return response.GetPayload(), nil } func UpdateRegistry(updateView *models.Registry, projectID int64) error { @@ -187,5 +187,5 @@ func GetRegistryIdByName(registryName string) (int64, error) { } } - return 0, err + return 0, fmt.Errorf("registry %q not found", registryName) } diff --git a/pkg/api/user_handler.go b/pkg/api/user_handler.go index 68ff01821..4af04ce1a 100644 --- a/pkg/api/user_handler.go +++ b/pkg/api/user_handler.go @@ -117,7 +117,7 @@ func GetUsersIdByName(userName string) (int64, error) { } } - return 0, err + return 0, fmt.Errorf("user %q not found", userName) } func ResetPassword(userId int64, opts reset.PasswordChangeView) error { diff --git a/pkg/views/member/create/view.go b/pkg/views/member/create/view.go index cc96aea3a..1a2b4b4c1 100644 --- a/pkg/views/member/create/view.go +++ b/pkg/views/member/create/view.go @@ -55,10 +55,14 @@ var RoleOptions = map[string]int{ } func CreateMemberView(createView *CreateView) { - roleOptions := []string{"Project Admin", "Developer", "Guest", "Maintainer", "Limited Guest"} - var roleSelectOptions []huh.Option[int] - for id, name := range roleOptions { - roleSelectOptions = append(roleSelectOptions, huh.NewOption(name, id)) + // Bind labels directly to canonical Harbor role IDs so the form and + // the --role/--roleid flags share one representation. + roleSelectOptions := []huh.Option[int]{ + huh.NewOption("Project Admin", RoleOptions["Admin"]), + huh.NewOption("Developer", RoleOptions["Developer"]), + huh.NewOption("Guest", RoleOptions["Guest"]), + huh.NewOption("Maintainer", RoleOptions["Maintainer"]), + huh.NewOption("Limited Guest", RoleOptions["LimitedGuest"]), } groupOptions := []string{"None", "LDAP group", "HTTP group", "OIDC group"}