From e966f4d869c5ec8cd0827681bd5a24da08f6e6c6 Mon Sep 17 00:00:00 2001 From: "Aditya@8767" Date: Sat, 9 May 2026 15:53:11 +0530 Subject: [PATCH] fix: resolve member update by username and align update wrappers with create - project member update --member now resolves the project_members.id via ListMembers (mirroring DeleteMemberByUsername) instead of misusing user_id as the member id, which silently targeted the wrong member when ids did not coincide. - scanner UpdateScanner normalizes Auth "None" to "" the same way CreateScanner does, so updates that set --auth None no longer leave the scanner with an unrecognized auth method. - webhook UpdateWebhook trims whitespace from the endpoint URL the same way CreateWebhook does. Signed-off-by: Aditya@8767 --- cmd/harbor/root/project/member/update.go | 40 ++++++++++++------------ pkg/api/member_handler.go | 30 ++++++++++++++++++ pkg/api/scanner_handler.go | 4 +++ pkg/api/webhook_handler.go | 2 +- 4 files changed, 55 insertions(+), 21 deletions(-) diff --git a/cmd/harbor/root/project/member/update.go b/cmd/harbor/root/project/member/update.go index 82f982327..9674ce0b9 100644 --- a/cmd/harbor/root/project/member/update.go +++ b/cmd/harbor/root/project/member/update.go @@ -53,36 +53,36 @@ func UpdateMemberCommand() *cobra.Command { } } - if memberName == "" { - opts.ID = prompt.GetMemberIDFromUser(opts.ProjectNameOrID, memberName) - - if opts.ID == 0 { - return fmt.Errorf("No members found in project") - } - } else { - opts.ID, err = api.GetUsersIdByName(memberName) - if err != nil { - return err - } - } - if roleID == 0 { roleID = prompt.GetRoleIDFromUser() } - opts.RoleID = &models.RoleRequest{ - RoleID: roleID, - } + role := &models.RoleRequest{RoleID: roleID} // when set true parses projectNameOrID as projectName // else it parses as an integer ID opts.XIsResourceName = !isID + opts.RoleID = role + + if memberName != "" { + if err = api.UpdateMemberByUsername(opts.ProjectNameOrID, memberName, opts.XIsResourceName, role); err != nil { + return fmt.Errorf("failed to update member: %v", err) + } + fmt.Printf("successfully updated %s to role ID %d in project %s\n", memberName, roleID, opts.ProjectNameOrID) + return nil + } + + if opts.ID == 0 { + opts.ID = prompt.GetMemberIDFromUser(opts.ProjectNameOrID, memberName) + if opts.ID == 0 { + return fmt.Errorf("No members found in project") + } + } - err = api.UpdateMember(opts) - if err != nil { - return fmt.Errorf("failed to get members list: %v", err) + if err = api.UpdateMember(opts); err != nil { + return fmt.Errorf("failed to update member: %v", err) } - fmt.Printf("successfully updated user with ID %d with role ID %d for project %s\n", opts.ID, opts.RoleID, opts.ProjectNameOrID) + fmt.Printf("successfully updated member ID %d to role ID %d in project %s\n", opts.ID, roleID, opts.ProjectNameOrID) return nil }, } diff --git a/pkg/api/member_handler.go b/pkg/api/member_handler.go index ceff19240..0faa563f0 100644 --- a/pkg/api/member_handler.go +++ b/pkg/api/member_handler.go @@ -195,6 +195,36 @@ func DeleteMemberByUsername(projectName string, username string, xIsResourceName return nil } +// UpdateMemberByUsername resolves a project-member entry by username and +// updates its role. The Harbor UpdateProjectMember API takes the project +// member ID (project_members.id), not the user ID — these are distinct and +// must not be confused, so callers should prefer this helper over manually +// looking up the user ID and passing it as Mid. +func UpdateMemberByUsername(projectNameOrID, username string, xIsResourceName bool, role *models.RoleRequest) error { + members, err := ListMembers(projectNameOrID, username, true) + if err != nil { + return err + } + + var memberID int64 + for _, m := range members.Payload { + if m.EntityName == username { + memberID = m.ID + break + } + } + if memberID == 0 { + return fmt.Errorf("member with username %q not found in project %q", username, projectNameOrID) + } + + return UpdateMember(UpdateMemberOptions{ + ProjectNameOrID: projectNameOrID, + ID: memberID, + XIsResourceName: xIsResourceName, + RoleID: role, + }) +} + func UpdateMember(opts UpdateMemberOptions) error { ctx, client, err := utils.ContextWithClient() if err != nil { diff --git a/pkg/api/scanner_handler.go b/pkg/api/scanner_handler.go index 1c88d6dcc..8417d20d4 100644 --- a/pkg/api/scanner_handler.go +++ b/pkg/api/scanner_handler.go @@ -135,6 +135,10 @@ func UpdateScanner(registrationID string, opts models.ScannerRegistration) error return err } + if opts.Auth == "None" { + opts.Auth = "" + } + scannerRegReq := models.ScannerRegistrationReq{ Name: &opts.Name, Description: opts.Description, diff --git a/pkg/api/webhook_handler.go b/pkg/api/webhook_handler.go index 0fa7b2699..0ba238e02 100644 --- a/pkg/api/webhook_handler.go +++ b/pkg/api/webhook_handler.go @@ -113,7 +113,7 @@ func UpdateWebhook(opts *edit.EditView) error { Name: opts.Name, Targets: []*models.WebhookTargetObject{ { - Address: opts.EndpointURL, + Address: strings.TrimSpace(opts.EndpointURL), AuthHeader: opts.AuthHeader, PayloadFormat: models.PayloadFormatType(opts.PayloadFormat), SkipCertVerify: !opts.VerifyRemoteCertificate,