From 2175f66b09c9e93657c054bab25e9030b3dd7d9c Mon Sep 17 00:00:00 2001 From: Mujib Ahasan Date: Fri, 6 Feb 2026 21:28:44 +0530 Subject: [PATCH 1/8] feat(user): add fuzzy,match,range flag for user list command Signed-off-by: Mujib Ahasan --- cmd/harbor/root/user/list.go | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/cmd/harbor/root/user/list.go b/cmd/harbor/root/user/list.go index eb5cc4cf1..e8c254160 100644 --- a/cmd/harbor/root/user/list.go +++ b/cmd/harbor/root/user/list.go @@ -26,8 +26,15 @@ import ( ) func UserListCmd() *cobra.Command { - var opts api.ListFlags - var allUsers []*models.UserResp + + var ( + opts api.ListFlags + allUsers []*models.UserResp + // For querying, opts.Q + fuzzy []string + match []string + ranges []string + ) cmd := &cobra.Command{ Use: "list", @@ -47,6 +54,17 @@ func UserListCmd() *cobra.Command { return fmt.Errorf("page size should be less than or equal to 100") } + if len(fuzzy) != 0 || len(match) != 0 || len(ranges) != 0 { + q, qErr := utils.BuildQueryParam(fuzzy, match, ranges, + []string{"name", "id"}, + ) + if qErr != nil { + return qErr + } + + opts.Q = q + } + if opts.PageSize == 0 { opts.PageSize = 100 opts.Page = 1 @@ -99,6 +117,9 @@ func UserListCmd() *cobra.Command { flags.Int64VarP(&opts.PageSize, "page-size", "n", 0, "Size of per page (0 to fetch all)") flags.StringVarP(&opts.Q, "query", "q", "", "Query string to query resources") flags.StringVarP(&opts.Sort, "sort", "s", "", "Sort the resource list in ascending or descending order") + flags.StringSliceVar(&fuzzy, "fuzzy", nil, "Fuzzy match filter (key=value)") + flags.StringSliceVar(&match, "match", nil, "exact match filter (key=value)") + flags.StringSliceVar(&ranges, "range", nil, "range filter (key=min~max)") return cmd } From 13e5204a685070915ec5b9228e5924e1d4987426 Mon Sep 17 00:00:00 2001 From: Mujib Ahasan Date: Sat, 7 Feb 2026 02:58:04 +0530 Subject: [PATCH 2/8] refractor: add fuzzy,match,range flags for resource query Signed-off-by: Mujib Ahasan --- cmd/harbor/root/artifact/list.go | 24 +++++++++++++- cmd/harbor/root/instance/list.go | 24 ++++++++++++-- cmd/harbor/root/project/robot/list.go | 13 +++----- cmd/harbor/root/quota/list.go | 8 +---- cmd/harbor/root/registry/list.go | 23 ++++++++++++- .../root/replication/executions/list.go | 23 ++++++++++++- cmd/harbor/root/replication/policies/list.go | 24 +++++++++++++- cmd/harbor/root/repository/list.go | 22 ++++++++++++- cmd/harbor/root/robot/list.go | 32 ++++++++++++++----- cmd/harbor/root/user/list.go | 2 +- 10 files changed, 164 insertions(+), 31 deletions(-) diff --git a/cmd/harbor/root/artifact/list.go b/cmd/harbor/root/artifact/list.go index 2522e7133..3bc1ca209 100644 --- a/cmd/harbor/root/artifact/list.go +++ b/cmd/harbor/root/artifact/list.go @@ -26,7 +26,14 @@ import ( ) func ListArtifactCommand() *cobra.Command { - var opts api.ListFlags + + var ( + opts api.ListFlags + // For querying, opts.Q + fuzzy []string + match []string + ranges []string + ) cmd := &cobra.Command{ Use: "list", @@ -70,6 +77,18 @@ Supports pagination, search queries, and sorting using flags.`, repoName = prompt.GetRepoNameFromUser(projectName) } + if len(fuzzy) != 0 || len(match) != 0 || len(ranges) != 0 { + q, qErr := utils.BuildQueryParam(fuzzy, match, ranges, + []string{"id", "type", "media_type", "artifact_id", "artifact_type", "project_id", "repository_id", "repository_name", + "manifest_media_type", "digest"}, + ) + if qErr != nil { + return qErr + } + + opts.Q = q + } + artifacts, err = api.ListArtifact(projectName, repoName, opts) if err != nil { @@ -94,6 +113,9 @@ Supports pagination, search queries, and sorting using flags.`, flags.Int64VarP(&opts.PageSize, "page-size", "n", 10, "Size of per page") flags.StringVarP(&opts.Q, "query", "q", "", "Query string to query resources") flags.StringVarP(&opts.Sort, "sort", "s", "", "Sort the resource list in ascending or descending order") + flags.StringSliceVar(&fuzzy, "fuzzy", nil, "Fuzzy match filter (key=value)") + flags.StringSliceVar(&match, "match", nil, "exact match filter (key=value)") + flags.StringSliceVar(&ranges, "range", nil, "range filter (key=min~max)") return cmd } diff --git a/cmd/harbor/root/instance/list.go b/cmd/harbor/root/instance/list.go index 98cb81c49..a7f09fe40 100644 --- a/cmd/harbor/root/instance/list.go +++ b/cmd/harbor/root/instance/list.go @@ -24,7 +24,14 @@ import ( ) func ListInstanceCommand() *cobra.Command { - var opts api.ListFlags + + var ( + opts api.ListFlags + // For querying, opts.Q + fuzzy []string + match []string + ranges []string + ) cmd := &cobra.Command{ Use: "list", @@ -45,8 +52,18 @@ This command provides an easy way to view all instances along with their details return fmt.Errorf("page size should be less than or equal to 100") } - instance, err := api.ListAllInstance(opts) + if len(fuzzy) != 0 || len(match) != 0 || len(ranges) != 0 { + q, qErr := utils.BuildQueryParam(fuzzy, match, ranges, + []string{"id", "name", "description", "endpoint", "vendor", "status", "enabled", "auth_mode"}, + ) + if qErr != nil { + return qErr + } + opts.Q = q + } + + instance, err := api.ListAllInstance(opts) if err != nil { return fmt.Errorf("failed to get instance list: %v", err) } @@ -68,6 +85,9 @@ This command provides an easy way to view all instances along with their details flags.Int64VarP(&opts.PageSize, "page-size", "", 10, "Size of per page") flags.StringVarP(&opts.Q, "query", "q", "", "Query string to query resources") flags.StringVarP(&opts.Sort, "sort", "", "", "Sort the resource list in ascending or descending order") + flags.StringSliceVar(&fuzzy, "fuzzy", nil, "Fuzzy match filter (key=value)") + flags.StringSliceVar(&match, "match", nil, "exact match filter (key=value)") + flags.StringSliceVar(&ranges, "range", nil, "range filter (key=min~max)") return cmd } diff --git a/cmd/harbor/root/project/robot/list.go b/cmd/harbor/root/project/robot/list.go index 4b44a81b2..577b77f0b 100644 --- a/cmd/harbor/root/project/robot/list.go +++ b/cmd/harbor/root/project/robot/list.go @@ -29,7 +29,10 @@ import ( // ListRobotCommand creates a new `harbor project robot list` command func ListRobotCommand() *cobra.Command { - var opts api.ListFlags + + var ( + opts api.ListFlags + ) projectQString := constants.ProjectQString cmd := &cobra.Command{ @@ -126,13 +129,7 @@ Examples: flags.Int64VarP(&opts.PageSize, "page-size", "", 10, "Size of per page") flags.Int64VarP(&opts.ProjectID, "project-id", "", 0, "Project ID") flags.StringVarP(&opts.Q, "query", "q", "", "Query string to query resources") - flags.StringVarP( - &opts.Sort, - "sort", - "", - "", - "Sort the resource list in ascending or descending order", - ) + flags.StringVarP(&opts.Sort, "sort", "", "", "Sort the resource list in ascending or descending order") return cmd } diff --git a/cmd/harbor/root/quota/list.go b/cmd/harbor/root/quota/list.go index 45fc2e9b7..773906427 100644 --- a/cmd/harbor/root/quota/list.go +++ b/cmd/harbor/root/quota/list.go @@ -65,13 +65,7 @@ func ListQuotaCommand() *cobra.Command { flags.Int64VarP(&opts.PageSize, "page-size", "", 0, "Size of per page (use 0 to fetch all)") flags.StringVarP(&opts.Reference, "ref", "", "", "Reference type of quota") flags.StringVarP(&opts.ReferenceID, "refid", "", "", "Reference ID of quota") - flags.StringVarP( - &opts.Sort, - "sort", - "", - "", - "Sort the resource list in ascending or descending order", - ) + flags.StringVarP(&opts.Sort, "sort", "", "", "Sort the resource list in ascending or descending order") return cmd } diff --git a/cmd/harbor/root/registry/list.go b/cmd/harbor/root/registry/list.go index af2aa5d9b..63b7d590c 100644 --- a/cmd/harbor/root/registry/list.go +++ b/cmd/harbor/root/registry/list.go @@ -26,7 +26,14 @@ import ( // NewListRegistryCommand creates a new `harbor list registry` command func ListRegistryCommand() *cobra.Command { - var opts api.ListFlags + + var ( + opts api.ListFlags + // For querying, opts.Q + fuzzy []string + match []string + ranges []string + ) cmd := &cobra.Command{ Use: "list", @@ -43,6 +50,17 @@ func ListRegistryCommand() *cobra.Command { if opts.PageSize > 100 { return fmt.Errorf("page size should be less than or equal to 100") } + + if len(fuzzy) != 0 || len(match) != 0 || len(ranges) != 0 { + q, qErr := utils.BuildQueryParam(fuzzy, match, ranges, + []string{"id", "url", "name", "type", "description", "status", "creation_time"}, + ) + if qErr != nil { + return qErr + } + + opts.Q = q + } registry, err := api.ListRegistries(opts) if err != nil { @@ -70,6 +88,9 @@ func ListRegistryCommand() *cobra.Command { flags.Int64VarP(&opts.PageSize, "page-size", "", 10, "Size of per page") flags.StringVarP(&opts.Q, "query", "q", "", "Query string to query resources") flags.StringVarP(&opts.Sort, "sort", "", "", "Sort the resource list in ascending or descending order") + flags.StringSliceVar(&fuzzy, "fuzzy", nil, "Fuzzy match filter (key=value)") + flags.StringSliceVar(&match, "match", nil, "exact match filter (key=value)") + flags.StringSliceVar(&ranges, "range", nil, "range filter (key=min~max)") return cmd } diff --git a/cmd/harbor/root/replication/executions/list.go b/cmd/harbor/root/replication/executions/list.go index 067ce3d41..50270c698 100644 --- a/cmd/harbor/root/replication/executions/list.go +++ b/cmd/harbor/root/replication/executions/list.go @@ -27,7 +27,14 @@ import ( ) func ListCommand() *cobra.Command { - var opts api.ListFlags + + var ( + opts api.ListFlags + // For querying, opts.Q + fuzzy []string + match []string + ranges []string + ) cmd := &cobra.Command{ Use: "list", Short: "List replication executions", @@ -60,6 +67,17 @@ func ListCommand() *cobra.Command { rpolicyID = prompt.GetReplicationPolicyFromUser() } + if len(fuzzy) != 0 || len(match) != 0 || len(ranges) != 0 { + q, qErr := utils.BuildQueryParam(fuzzy, match, ranges, + []string{"id", "policy_id", "trigger", "status", "status_text", "start_time", "end_time "}, + ) + if qErr != nil { + return qErr + } + + opts.Q = q + } + log.Debug("Fetching executions...") executions, err := api.ListReplicationExecutions(rpolicyID, opts) if err != nil { @@ -92,6 +110,9 @@ func ListCommand() *cobra.Command { flags.Int64VarP(&opts.PageSize, "page-size", "", 0, "Size of per page (0 to fetch all)") flags.StringVarP(&opts.Q, "query", "q", "", "Query string to query resources") flags.StringVarP(&opts.Sort, "sort", "", "", "Sort the resource list in ascending or descending order") + flags.StringSliceVar(&fuzzy, "fuzzy", nil, "Fuzzy match filter (key=value)") + flags.StringSliceVar(&match, "match", nil, "exact match filter (key=value)") + flags.StringSliceVar(&ranges, "range", nil, "range filter (key=min~max)") return cmd } diff --git a/cmd/harbor/root/replication/policies/list.go b/cmd/harbor/root/replication/policies/list.go index 33c775932..b9e7017d4 100644 --- a/cmd/harbor/root/replication/policies/list.go +++ b/cmd/harbor/root/replication/policies/list.go @@ -25,7 +25,15 @@ import ( ) func ListCommand() *cobra.Command { - var opts api.ListFlags + + var ( + opts api.ListFlags + // For querying, opts.Q + fuzzy []string + match []string + ranges []string + ) + cmd := &cobra.Command{ Use: "list", Short: "List replication policies", @@ -45,6 +53,17 @@ func ListCommand() *cobra.Command { } log.Debug("Fetching policies...") + + if len(fuzzy) != 0 || len(match) != 0 || len(ranges) != 0 { + q, qErr := utils.BuildQueryParam(fuzzy, match, ranges, + []string{"id", "name", "description", "creation_time"}, + ) + if qErr != nil { + return qErr + } + + opts.Q = q + } allPolicies, err := api.ListReplicationPolicies(opts) if err != nil { return fmt.Errorf("failed to get projects list: %v", utils.ParseHarborErrorMsg(err)) @@ -76,6 +95,9 @@ func ListCommand() *cobra.Command { flags.Int64VarP(&opts.PageSize, "page-size", "", 0, "Size of per page (0 to fetch all)") flags.StringVarP(&opts.Q, "query", "q", "", "Query string to query resources") flags.StringVarP(&opts.Sort, "sort", "", "", "Sort the resource list in ascending or descending order") + flags.StringSliceVar(&fuzzy, "fuzzy", nil, "Fuzzy match filter (key=value)") + flags.StringSliceVar(&match, "match", nil, "exact match filter (key=value)") + flags.StringSliceVar(&ranges, "range", nil, "range filter (key=min~max)") return cmd } diff --git a/cmd/harbor/root/repository/list.go b/cmd/harbor/root/repository/list.go index 968bdcfdb..2f6fce41a 100644 --- a/cmd/harbor/root/repository/list.go +++ b/cmd/harbor/root/repository/list.go @@ -27,7 +27,13 @@ import ( ) func ListRepositoryCommand() *cobra.Command { - var opts api.ListFlags + var ( + opts api.ListFlags + // For querying, opts.Q + fuzzy []string + match []string + ranges []string + ) cmd := &cobra.Command{ Use: "list", @@ -60,6 +66,17 @@ func ListRepositoryCommand() *cobra.Command { } } + if len(fuzzy) != 0 || len(match) != 0 || len(ranges) != 0 { + q, qErr := utils.BuildQueryParam(fuzzy, match, ranges, + []string{"id", "project_id", "name", "artifact_count", "description", "pull_count", "creation_time"}, + ) + if qErr != nil { + return qErr + } + + opts.Q = q + } + repos, err = api.ListRepository(projectName, false, opts) if err != nil { return fmt.Errorf("failed to list repositories: %v", err) @@ -86,6 +103,9 @@ func ListRepositoryCommand() *cobra.Command { flags.Int64VarP(&opts.PageSize, "page-size", "", 10, "Size of per page") flags.StringVarP(&opts.Q, "query", "q", "", "Query string to query resources") flags.StringVarP(&opts.Sort, "sort", "", "", "Sort the resource list in ascending or descending order") + flags.StringSliceVar(&fuzzy, "fuzzy", nil, "Fuzzy match filter (key=value)") + flags.StringSliceVar(&match, "match", nil, "exact match filter (key=value)") + flags.StringSliceVar(&ranges, "range", nil, "range filter (key=min~max)") return cmd } diff --git a/cmd/harbor/root/robot/list.go b/cmd/harbor/root/robot/list.go index ef58c3891..7da27563c 100644 --- a/cmd/harbor/root/robot/list.go +++ b/cmd/harbor/root/robot/list.go @@ -25,7 +25,14 @@ import ( // ListRobotCommand creates a new `harbor project robot list` command func ListRobotCommand() *cobra.Command { - var opts api.ListFlags + + var ( + opts api.ListFlags + // For querying, opts.Q + fuzzy []string + match []string + ranges []string + ) cmd := &cobra.Command{ Use: "list [projectName]", @@ -74,6 +81,18 @@ Examples: return fmt.Errorf("page size should be less than or equal to 100") } + if len(fuzzy) != 0 || len(match) != 0 || len(ranges) != 0 { + q, qErr := utils.BuildQueryParam(fuzzy, match, ranges, + []string{"id", "name", "description", "secret", "level", "duration", "expires_at", "creation_time", + "editable", "disable"}, + ) + if qErr != nil { + return qErr + } + + opts.Q = q + } + robots, err := api.ListRobot(opts) if err != nil { errorCode := utils.ParseHarborErrorCode(err) @@ -101,13 +120,10 @@ Examples: flags.Int64VarP(&opts.Page, "page", "", 1, "Page number") flags.Int64VarP(&opts.PageSize, "page-size", "", 10, "Size of per page") flags.StringVarP(&opts.Q, "query", "q", "", "Query string to query resources") - flags.StringVarP( - &opts.Sort, - "sort", - "", - "", - "Sort the resource list in ascending or descending order", - ) + flags.StringVarP(&opts.Sort, "sort", "", "", "Sort the resource list in ascending or descending order") + flags.StringSliceVar(&fuzzy, "fuzzy", nil, "Fuzzy match filter (key=value)") + flags.StringSliceVar(&match, "match", nil, "exact match filter (key=value)") + flags.StringSliceVar(&ranges, "range", nil, "range filter (key=min~max)") return cmd } diff --git a/cmd/harbor/root/user/list.go b/cmd/harbor/root/user/list.go index e8c254160..21047a26c 100644 --- a/cmd/harbor/root/user/list.go +++ b/cmd/harbor/root/user/list.go @@ -56,7 +56,7 @@ func UserListCmd() *cobra.Command { if len(fuzzy) != 0 || len(match) != 0 || len(ranges) != 0 { q, qErr := utils.BuildQueryParam(fuzzy, match, ranges, - []string{"name", "id"}, + []string{"email", "username", "user_id", "realname", "comment", "creation_time"}, ) if qErr != nil { return qErr From 9acc3ceb2ab0685522ef239ace50175733d6e1eb Mon Sep 17 00:00:00 2001 From: Mujib Ahasan Date: Mon, 23 Feb 2026 22:17:37 +0530 Subject: [PATCH 3/8] New query flags added to few list subcommands Signed-off-by: Mujib Ahasan --- cmd/harbor/root/tag/immutable/list.go | 28 ++++++++++++++++++++++++++- cmd/harbor/root/webhook/list.go | 28 ++++++++++++++++++++++++++- pkg/api/artifact_handler.go | 9 ++++++++- pkg/api/immutable_handler.go | 13 +++++++++++-- pkg/api/webhook_handler.go | 9 ++++++++- 5 files changed, 81 insertions(+), 6 deletions(-) diff --git a/cmd/harbor/root/tag/immutable/list.go b/cmd/harbor/root/tag/immutable/list.go index dabeec900..9e24809d5 100644 --- a/cmd/harbor/root/tag/immutable/list.go +++ b/cmd/harbor/root/tag/immutable/list.go @@ -26,6 +26,14 @@ import ( ) func ListImmutableCommand() *cobra.Command { + + var ( + opts api.ListFlags + // For querying, opts.Q + fuzzy []string + match []string + ranges []string + ) cmd := &cobra.Command{ Use: "list [PROJECT_NAME]", Short: "Display all immutable tag rules for a project", @@ -45,6 +53,17 @@ You can specify the project name as an argument or, if omitted, you will be prom var resp immutable.ListImmuRulesOK var projectName string + if len(fuzzy) != 0 || len(match) != 0 || len(ranges) != 0 { + q, qErr := utils.BuildQueryParam(fuzzy, match, ranges, + []string{"id", "priority", "disabled", "action", "template"}, + ) + if qErr != nil { + log.Errorf("error while building query parameter: %v", qErr) + } + + opts.Q = q + } + if len(args) > 0 { projectName = args[0] } else { @@ -54,7 +73,7 @@ You can specify the project name as an argument or, if omitted, you will be prom } } - resp, err = api.ListImmutable(projectName) + resp, err = api.ListImmutable(projectName, opts) if err != nil { return fmt.Errorf("failed to list immutablility rule: %v", err) } @@ -77,5 +96,12 @@ You can specify the project name as an argument or, if omitted, you will be prom return nil }, } + + flags := cmd.Flags() + flags.StringVarP(&opts.Q, "query", "q", "", "Query string to query resources") + flags.StringSliceVar(&fuzzy, "fuzzy", nil, "Fuzzy match filter (key=value)") + flags.StringSliceVar(&match, "match", nil, "exact match filter (key=value)") + flags.StringSliceVar(&ranges, "range", nil, "range filter (key=min~max)") + return cmd } diff --git a/cmd/harbor/root/webhook/list.go b/cmd/harbor/root/webhook/list.go index 74619bd73..4f682185c 100644 --- a/cmd/harbor/root/webhook/list.go +++ b/cmd/harbor/root/webhook/list.go @@ -21,11 +21,20 @@ import ( "github.com/goharbor/harbor-cli/pkg/prompt" "github.com/goharbor/harbor-cli/pkg/utils" webhookViews "github.com/goharbor/harbor-cli/pkg/views/webhook/list" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" ) func ListWebhookCommand() *cobra.Command { + var ( + opts api.ListFlags + // For querying, opts.Q + fuzzy []string + match []string + ranges []string + ) + cmd := &cobra.Command{ Use: "list [PROJECT_NAME]", Short: "List all webhook policies for a Harbor project", @@ -47,6 +56,17 @@ Use the '--output-format' flag for raw JSON output.`, var resp webhook.ListWebhookPoliciesOfProjectOK var projectName string + if len(fuzzy) != 0 || len(match) != 0 || len(ranges) != 0 { + q, qErr := utils.BuildQueryParam(fuzzy, match, ranges, + []string{"id", "project_id", "description", "name", "creator", "enabled"}, + ) + if qErr != nil { + log.Errorf("error while building query parameter: %v", qErr) + } + + opts.Q = q + } + if len(args) > 0 { projectName = args[0] } else { @@ -56,7 +76,7 @@ Use the '--output-format' flag for raw JSON output.`, } } - resp, err = api.ListWebhooks(projectName) + resp, err = api.ListWebhooks(projectName, opts) if err != nil { return fmt.Errorf("failed to list webhooks: %v", err) } @@ -75,5 +95,11 @@ Use the '--output-format' flag for raw JSON output.`, return nil }, } + + flags := cmd.Flags() + flags.StringVarP(&opts.Q, "query", "q", "", "Query string to query resources") + flags.StringSliceVar(&fuzzy, "fuzzy", nil, "Fuzzy match filter (key=value)") + flags.StringSliceVar(&match, "match", nil, "exact match filter (key=value)") + flags.StringSliceVar(&ranges, "range", nil, "range filter (key=min~max)") return cmd } diff --git a/pkg/api/artifact_handler.go b/pkg/api/artifact_handler.go index b6957dfa5..0b4e05a8a 100644 --- a/pkg/api/artifact_handler.go +++ b/pkg/api/artifact_handler.go @@ -157,16 +157,23 @@ func DeleteTag(projectName, repoName, reference, tag string) error { } // ListTags lists all tags of a specific artifact. -func ListTags(projectName, repoName, reference string) (*artifact.ListTagsOK, error) { +func ListTags(projectName, repoName, reference string, opts ...ListFlags) (*artifact.ListTagsOK, error) { ctx, client, err := utils.ContextWithClient() if err != nil { return &artifact.ListTagsOK{}, err } + var listFlags ListFlags + + if len(opts) > 0 { + listFlags = opts[0] + } + resp, err := client.Artifact.ListTags(ctx, &artifact.ListTagsParams{ ProjectName: projectName, RepositoryName: repoName, Reference: reference, + Q: &listFlags.Q, }) if err != nil { diff --git a/pkg/api/immutable_handler.go b/pkg/api/immutable_handler.go index eab843b81..1067b4d22 100644 --- a/pkg/api/immutable_handler.go +++ b/pkg/api/immutable_handler.go @@ -51,12 +51,21 @@ func CreateImmutable(opts create.CreateView, projectName string) error { return nil } -func ListImmutable(projectName string) (immutable.ListImmuRulesOK, error) { +func ListImmutable(projectName string, opts ...ListFlags) (immutable.ListImmuRulesOK, error) { ctx, client, err := utils.ContextWithClient() if err != nil { return immutable.ListImmuRulesOK{}, err } - response, err := client.Immutable.ListImmuRules(ctx, &immutable.ListImmuRulesParams{ProjectNameOrID: projectName}) + + var listFlags ListFlags + + if len(opts) > 0 { + listFlags = opts[0] + } + response, err := client.Immutable.ListImmuRules(ctx, &immutable.ListImmuRulesParams{ + ProjectNameOrID: projectName, + Q: &listFlags.Q, + }) if err != nil { return immutable.ListImmuRulesOK{}, err } diff --git a/pkg/api/webhook_handler.go b/pkg/api/webhook_handler.go index 0fa7b2699..f0ecc11a3 100644 --- a/pkg/api/webhook_handler.go +++ b/pkg/api/webhook_handler.go @@ -25,14 +25,21 @@ import ( log "github.com/sirupsen/logrus" ) -func ListWebhooks(projectName string) (webhook.ListWebhookPoliciesOfProjectOK, error) { +func ListWebhooks(projectName string, opts ...ListFlags) (webhook.ListWebhookPoliciesOfProjectOK, error) { ctx, client, err := utils.ContextWithClient() if err != nil { return webhook.ListWebhookPoliciesOfProjectOK{}, err } + var listFlags ListFlags + + if len(opts) > 0 { + listFlags = opts[0] + } + response, err := client.Webhook.ListWebhookPoliciesOfProject(ctx, &webhook.ListWebhookPoliciesOfProjectParams{ ProjectNameOrID: projectName, + Q: &listFlags.Q, }) if err != nil { From 7377fec5f7482310299cb1ead3c72e35d543d1c8 Mon Sep 17 00:00:00 2001 From: Mujib Ahasan Date: Mon, 23 Feb 2026 22:30:54 +0530 Subject: [PATCH 4/8] New query flags added to list scanners subcommand Signed-off-by: Mujib Ahasan --- cmd/harbor/root/scanner/list.go | 29 ++++++++++++++++++++++++++++- pkg/api/scanner_handler.go | 11 +++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/cmd/harbor/root/scanner/list.go b/cmd/harbor/root/scanner/list.go index dd4608b1e..9e893b40d 100644 --- a/cmd/harbor/root/scanner/list.go +++ b/cmd/harbor/root/scanner/list.go @@ -24,12 +24,33 @@ import ( ) func ListScannerCommand() *cobra.Command { + var ( + opts api.ListFlags + // For querying, opts.Q + fuzzy []string + match []string + ranges []string + ) + cmd := &cobra.Command{ Use: "list", Short: "List scanners", Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { - scannersResp, err := api.ListScanners() + + if len(fuzzy) != 0 || len(match) != 0 || len(ranges) != 0 { + q, qErr := utils.BuildQueryParam(fuzzy, match, ranges, + []string{"uuid", "url", "description", "name", "disabled", "is_default", "adapter", "vendor", "health", "version", + "skip_certVerify"}, + ) + if qErr != nil { + log.Errorf("error while building query parameter: %v", qErr) + } + + opts.Q = q + } + + scannersResp, err := api.ListScanners(opts) if err != nil { return fmt.Errorf("failed to list scanners: %v", err) } @@ -53,5 +74,11 @@ func ListScannerCommand() *cobra.Command { return nil }, } + + flags := cmd.Flags() + flags.StringVarP(&opts.Q, "query", "q", "", "Query string to query resources") + flags.StringSliceVar(&fuzzy, "fuzzy", nil, "Fuzzy match filter (key=value)") + flags.StringSliceVar(&match, "match", nil, "exact match filter (key=value)") + flags.StringSliceVar(&ranges, "range", nil, "range filter (key=min~max)") return cmd } diff --git a/pkg/api/scanner_handler.go b/pkg/api/scanner_handler.go index 1c88d6dcc..8fb16b73e 100644 --- a/pkg/api/scanner_handler.go +++ b/pkg/api/scanner_handler.go @@ -54,13 +54,20 @@ func CreateScanner(opts create.CreateView) error { return nil } -func ListScanners() (scanner.ListScannersOK, error) { +func ListScanners(opts ...ListFlags) (scanner.ListScannersOK, error) { ctx, client, err := utils.ContextWithClient() if err != nil { return scanner.ListScannersOK{}, err } + var listFlags ListFlags - response, err := client.Scanner.ListScanners(ctx, &scanner.ListScannersParams{}) + if len(opts) > 0 { + listFlags = opts[0] + } + + response, err := client.Scanner.ListScanners(ctx, &scanner.ListScannersParams{ + Q: &listFlags.Q, + }) if err != nil { return scanner.ListScannersOK{}, err From 82f30b1daf82f891ea75a4728e00b7eb5bf1f2dc Mon Sep 17 00:00:00 2001 From: Mujib Ahasan Date: Sat, 28 Feb 2026 00:39:07 +0530 Subject: [PATCH 5/8] new query flags added to robot list Signed-off-by: Mujib Ahasan --- cmd/harbor/root/project/robot/list.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/cmd/harbor/root/project/robot/list.go b/cmd/harbor/root/project/robot/list.go index 577b77f0b..3ff5f1f55 100644 --- a/cmd/harbor/root/project/robot/list.go +++ b/cmd/harbor/root/project/robot/list.go @@ -32,6 +32,10 @@ func ListRobotCommand() *cobra.Command { var ( opts api.ListFlags + // For querying, opts.Q + fuzzy []string + match []string + ranges []string ) projectQString := constants.ProjectQString @@ -105,6 +109,19 @@ Examples: opts.Q = projectQString + strconv.FormatInt(projectID, 10) } + if len(fuzzy) != 0 || len(match) != 0 || len(ranges) != 0 { + q, qErr := utils.BuildQueryParam(fuzzy, match, ranges, + []string{"id", "secret", "description", "name", "disable", "level", "duration", "editable"}, + ) + if qErr != nil { + log.Errorf("error while building query parameter: %v", qErr) + } + if opts.Q != "" { + opts.Q += "," + } + opts.Q += q + } + robots, err := api.ListRobot(opts) if err != nil { return fmt.Errorf("failed to get robots list: %v", utils.ParseHarborErrorMsg(err)) @@ -129,6 +146,9 @@ Examples: flags.Int64VarP(&opts.PageSize, "page-size", "", 10, "Size of per page") flags.Int64VarP(&opts.ProjectID, "project-id", "", 0, "Project ID") flags.StringVarP(&opts.Q, "query", "q", "", "Query string to query resources") + flags.StringSliceVar(&fuzzy, "fuzzy", nil, "Fuzzy match filter (key=value)") + flags.StringSliceVar(&match, "match", nil, "exact match filter (key=value)") + flags.StringSliceVar(&ranges, "range", nil, "range filter (key=min~max)") flags.StringVarP(&opts.Sort, "sort", "", "", "Sort the resource list in ascending or descending order") return cmd From 166e8824a5210a2d73ad4e67a0464312b1c311e4 Mon Sep 17 00:00:00 2001 From: Mujib Ahasan Date: Wed, 8 Apr 2026 20:38:45 +0530 Subject: [PATCH 6/8] fix: error handling and small nits Signed-off-by: Mujib Ahasan --- cmd/harbor/root/replication/executions/list.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/harbor/root/replication/executions/list.go b/cmd/harbor/root/replication/executions/list.go index 50270c698..6cec5ff85 100644 --- a/cmd/harbor/root/replication/executions/list.go +++ b/cmd/harbor/root/replication/executions/list.go @@ -69,7 +69,7 @@ func ListCommand() *cobra.Command { if len(fuzzy) != 0 || len(match) != 0 || len(ranges) != 0 { q, qErr := utils.BuildQueryParam(fuzzy, match, ranges, - []string{"id", "policy_id", "trigger", "status", "status_text", "start_time", "end_time "}, + []string{"id", "policy_id", "trigger", "status", "status_text", "start_time", "end_time"}, ) if qErr != nil { return qErr From cbc32f9c22bfa9dfa093dfc6089cb4a654248af2 Mon Sep 17 00:00:00 2001 From: Mujib Ahasan Date: Wed, 8 Apr 2026 23:41:44 +0530 Subject: [PATCH 7/8] fix: error handling Signed-off-by: Mujib Ahasan --- cmd/harbor/root/project/robot/list.go | 2 +- cmd/harbor/root/scanner/list.go | 2 +- cmd/harbor/root/tag/immutable/list.go | 1 + cmd/harbor/root/webhook/list.go | 3 +-- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/harbor/root/project/robot/list.go b/cmd/harbor/root/project/robot/list.go index 3ff5f1f55..5d1518bcf 100644 --- a/cmd/harbor/root/project/robot/list.go +++ b/cmd/harbor/root/project/robot/list.go @@ -114,7 +114,7 @@ Examples: []string{"id", "secret", "description", "name", "disable", "level", "duration", "editable"}, ) if qErr != nil { - log.Errorf("error while building query parameter: %v", qErr) + return qErr } if opts.Q != "" { opts.Q += "," diff --git a/cmd/harbor/root/scanner/list.go b/cmd/harbor/root/scanner/list.go index 9e893b40d..0598effc9 100644 --- a/cmd/harbor/root/scanner/list.go +++ b/cmd/harbor/root/scanner/list.go @@ -44,7 +44,7 @@ func ListScannerCommand() *cobra.Command { "skip_certVerify"}, ) if qErr != nil { - log.Errorf("error while building query parameter: %v", qErr) + return qErr } opts.Q = q diff --git a/cmd/harbor/root/tag/immutable/list.go b/cmd/harbor/root/tag/immutable/list.go index 9e24809d5..2bc2bd732 100644 --- a/cmd/harbor/root/tag/immutable/list.go +++ b/cmd/harbor/root/tag/immutable/list.go @@ -59,6 +59,7 @@ You can specify the project name as an argument or, if omitted, you will be prom ) if qErr != nil { log.Errorf("error while building query parameter: %v", qErr) + return } opts.Q = q diff --git a/cmd/harbor/root/webhook/list.go b/cmd/harbor/root/webhook/list.go index 4f682185c..3070ef878 100644 --- a/cmd/harbor/root/webhook/list.go +++ b/cmd/harbor/root/webhook/list.go @@ -21,7 +21,6 @@ import ( "github.com/goharbor/harbor-cli/pkg/prompt" "github.com/goharbor/harbor-cli/pkg/utils" webhookViews "github.com/goharbor/harbor-cli/pkg/views/webhook/list" - log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -61,7 +60,7 @@ Use the '--output-format' flag for raw JSON output.`, []string{"id", "project_id", "description", "name", "creator", "enabled"}, ) if qErr != nil { - log.Errorf("error while building query parameter: %v", qErr) + return qErr } opts.Q = q From d5198318e39fbdc2e62911a97cc3f1a1167955bf Mon Sep 17 00:00:00 2001 From: Mujib Ahasan Date: Wed, 20 May 2026 12:54:22 +0530 Subject: [PATCH 8/8] proper error handling added Signed-off-by: Mujib Ahasan --- cmd/harbor/root/tag/immutable/list.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/harbor/root/tag/immutable/list.go b/cmd/harbor/root/tag/immutable/list.go index 2bc2bd732..ecd5226d6 100644 --- a/cmd/harbor/root/tag/immutable/list.go +++ b/cmd/harbor/root/tag/immutable/list.go @@ -58,8 +58,7 @@ You can specify the project name as an argument or, if omitted, you will be prom []string{"id", "priority", "disabled", "action", "template"}, ) if qErr != nil { - log.Errorf("error while building query parameter: %v", qErr) - return + return qErr } opts.Q = q @@ -77,6 +76,7 @@ You can specify the project name as an argument or, if omitted, you will be prom resp, err = api.ListImmutable(projectName, opts) if err != nil { return fmt.Errorf("failed to list immutablility rule: %v", err) + } FormatFlag := viper.GetString("output-format")