Skip to content
Draft
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
22 changes: 16 additions & 6 deletions cmd/harbor/root/labels/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ func ListLabelCommand() *cobra.Command {
fuzzy []string
match []string
ranges []string
all []string
any []string

validKeys = []string{"name", "id", "label_id", "creation_time", "owner_id", "color", "description"}
)

cmd := &cobra.Command{
Use: "list",
Short: "list labels",
Expand Down Expand Up @@ -65,9 +70,7 @@ func ListLabelCommand() *cobra.Command {
}

if len(fuzzy) != 0 || len(match) != 0 || len(ranges) != 0 { // Only Building Query if a param exists
q, qErr := utils.BuildQueryParam(fuzzy, match, ranges,
[]string{"name", "id", "label_id", "creation_time", "owner_id", "color", "description"},
)
q, qErr := utils.BuildQueryParam(fuzzy, match, ranges, all, any, validKeys)
if qErr != nil {
return qErr
}
Expand All @@ -94,6 +97,15 @@ func ListLabelCommand() *cobra.Command {
},
}

// Adding Query Description
var qDesc string
if cmd.Long != "" {
qDesc = "\n\n" + utils.GenerateQueryDocs(validKeys)
} else {
qDesc = utils.GenerateQueryDocs(validKeys)
}
cmd.Long += qDesc

flags := cmd.Flags()
flags.Int64VarP(&opts.Page, "page", "", 1, "Page number")
flags.Int64VarP(&opts.PageSize, "page-size", "", 20, "Size of per page")
Expand All @@ -102,9 +114,7 @@ func ListLabelCommand() *cobra.Command {
flags.Int64VarP(&opts.ProjectID, "project-id", "i", 0, "project ID when query project labels")
flags.BoolVarP(&isGlobal, "global", "", false, "whether to list global or project scope labels. (default scope is global)")
flags.StringVarP(&opts.Sort, "sort", "", "", "Sort the label 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)")
utils.SetQueryFlags(flags, &match, &fuzzy, &ranges, &all, &any)

return cmd
}
27 changes: 18 additions & 9 deletions cmd/harbor/root/project/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,14 @@ func ListProjectCommand() *cobra.Command {
allProjects []*models.Project
err error
// For querying, opts.Q
fuzzy []string
match []string
ranges []string
fuzzy []string
match []string
ranges []string
all []string
any []string
validKeys = []string{"name", "project_id", "public", "creation_time", "owner_id"}
)

cmd := &cobra.Command{
Use: "list",
Short: "List projects",
Expand Down Expand Up @@ -72,9 +76,7 @@ func ListProjectCommand() *cobra.Command {
}

if len(fuzzy) != 0 || len(match) != 0 || len(ranges) != 0 { // Only Building Query if a param exists
q, qErr := utils.BuildQueryParam(fuzzy, match, ranges,
[]string{"name", "project_id", "public", "creation_time", "owner_id"},
)
q, qErr := utils.BuildQueryParam(fuzzy, match, ranges, all, any, validKeys)
if qErr != nil {
return qErr
}
Expand Down Expand Up @@ -108,16 +110,23 @@ func ListProjectCommand() *cobra.Command {
},
}

// Adding Query Description
var qDesc string
if cmd.Long != "" {
qDesc = "\n\n" + utils.GenerateQueryDocs(validKeys)
} else {
qDesc = utils.GenerateQueryDocs(validKeys)
}
cmd.Long += qDesc

flags := cmd.Flags()
flags.StringVarP(&opts.Name, "name", "", "", "Name of the project")
flags.Int64VarP(&opts.Page, "page", "", 1, "Page number")
flags.Int64VarP(&opts.PageSize, "page-size", "", 0, "Size of per page (0 to fetch all)")
flags.BoolVarP(&private, "private", "", false, "Show only private projects")
flags.BoolVarP(&public, "public", "", false, "Show only public projects")
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)")
utils.SetQueryFlags(flags, &match, &fuzzy, &ranges, &all, &any) // Adds the 5 query flags

return cmd
}
Expand Down
129 changes: 120 additions & 9 deletions pkg/utils/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,63 +16,174 @@ package utils
import (
"fmt"
"strings"

"github.com/spf13/pflag"
)

// Builds the `q` param for List API's
func BuildQueryParam(fuzzy, match, ranges []string, validKeys []string) (string, error) {
// BuildQueryParam builds the `q` param for List API's
func BuildQueryParam(fuzzy, match, ranges, all, any []string, validKeys []string) (string, error) {
var parts []string
m := map[string]bool{} // existence map for key mapping

// Fuzzy
for _, v := range fuzzy {
kv := strings.Split(v, "=")
if len(kv) != 2 {
return "", fmt.Errorf("invalid fuzzy arg: %s ", v)
return "", fmt.Errorf("invalid fuzzy arg: %s", v)
}

if err := validateKey(kv[0], validKeys); err != nil {
return "", err
}

// Checking if key already exists
if m[kv[0]] {
return "", fmt.Errorf("found duplicate key: %s", kv[0])
}

m[kv[0]] = true
parts = append(parts, fmt.Sprintf("%s=~%s", kv[0], kv[1]))
}

// Exact Match's
// Exact match
for _, v := range match {
kv := strings.Split(v, "=")
if len(kv) != 2 {
return "", fmt.Errorf("invalid match arg: %s ", v)
return "", fmt.Errorf("invalid match arg: %s", v)
}

if err := validateKey(kv[0], validKeys); err != nil {
return "", err
}

// Checking if key already exists
if m[kv[0]] {
return "", fmt.Errorf("found duplicate key: %s", kv[0])
}

m[kv[0]] = true
parts = append(parts, fmt.Sprintf("%s=%s", kv[0], kv[1]))
}

// Ranges
// Range (min~max)
for _, v := range ranges {
kv := strings.Split(v, "=")
if len(kv) != 2 {
return "", fmt.Errorf("invalid range arg: %s ", v)
return "", fmt.Errorf("invalid range arg: %s", v)
}

if err := validateKey(kv[0], validKeys); err != nil {
return "", err
}

// Validating that range is in format min~max
// Checking if key already exists
if m[kv[0]] {
return "", fmt.Errorf("found duplicate key: %s", kv[0])
}

rng := strings.Split(kv[1], "~")
if len(rng) != 2 {
return "", fmt.Errorf("invalid range arg: %s ", v)
return "", fmt.Errorf("invalid range arg: %s", v)
}

m[kv[0]] = true
parts = append(parts, fmt.Sprintf("%s=[%s~%s]", kv[0], rng[0], rng[1]))
}

// All
for _, v := range all {
kv := strings.Split(v, "=")
if len(kv) != 2 {
return "", fmt.Errorf("invalid all arg: %s", v)
}

if err := validateKey(kv[0], validKeys); err != nil {
return "", err
}

// Checking if key already exists
if m[kv[0]] {
return "", fmt.Errorf("found duplicate key: %s", kv[0])
}

m[kv[0]] = true
vals := strings.Split(kv[1], ",") // Splitting and replacing "," with " ", Harbor syntax is {v1 v2 v3}
parts = append(parts, fmt.Sprintf("%s={%s}", kv[0], strings.Join(vals, " ")))
}

// Any
for _, v := range any {
kv := strings.Split(v, "=")
if len(kv) != 2 {
return "", fmt.Errorf("invalid any arg: %s", v)
}

if err := validateKey(kv[0], validKeys); err != nil {
return "", err
}

// Checking if key already exists
if m[kv[0]] {
return "", fmt.Errorf("found duplicate key: %s", kv[0])
}

m[kv[0]] = true
vals := strings.Split(kv[1], ",") // Splitting and replacing "," with " ", Harbor syntax is {v1 v2 v3}
parts = append(parts, fmt.Sprintf("%s=(%s)", kv[0], strings.Join(vals, " ")))
}

return strings.Join(parts, ","), nil
}

func GenerateQueryDocs(validKeys []string) string {
keys := strings.Join(validKeys, ", ")

doc := fmt.Sprintf(`
Query Filters

The following flags can be used to filter results.

Supported query types:

--match key=value
Match an exact value.

--fuzzy key=value
Perform a fuzzy match (partial match).

--range key=min:max
Match values within a range.

--all key=v1,v2
Match resources that contain ALL specified values.

--any key=v1,v2
Match resources that contain ANY of the specified values.

Examples:

--match project_id=12
--fuzzy name=test
--range update_time=2024-01-01~2024-02-01
--any tag=v1,v2
--all label=prod,stable

Valid keys for this command:

%s
`, keys)

return strings.TrimSpace(doc)
}

func SetQueryFlags(f *pflag.FlagSet, match, fuzzy, ranges, and, or *[]string) {
f.StringSliceVar(fuzzy, "fuzzy", nil, "Fuzzy match filter (key=value)")
f.StringSliceVar(match, "match", nil, "exact match filter (key=value)")
f.StringSliceVar(ranges, "range", nil, "range filter (key=min~max)")
f.StringSliceVar(and, "all", nil, "match-all filter (key=v1,v2,v3)")
f.StringSliceVar(or, "any", nil, "match-any filter (key=v1,v2,v3)")
}

// Validates Key provided by user for ListFlags.Q
func validateKey(key string, validKeys []string) error {
found := false
Expand Down
Loading