diff --git a/cmd/harbor/root/cmd.go b/cmd/harbor/root/cmd.go index 33509cbf8..039e39ea6 100644 --- a/cmd/harbor/root/cmd.go +++ b/cmd/harbor/root/cmd.go @@ -85,7 +85,7 @@ harbor help }, } - root.PersistentFlags().StringVarP(&output, "output-format", "o", "", "Output format. One of: json|yaml") + root.PersistentFlags().StringVarP(&output, "output-format", "o", "", "Output format. One of: json|yaml|csv") root.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.config/harbor-cli/config.yaml)") root.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output") diff --git a/cmd/harbor/root/vulnerability/cmd.go b/cmd/harbor/root/vulnerability/cmd.go index d432c4a54..defcc4aa4 100644 --- a/cmd/harbor/root/vulnerability/cmd.go +++ b/cmd/harbor/root/vulnerability/cmd.go @@ -26,6 +26,7 @@ func Vulnerability() *cobra.Command { cmd.AddCommand( GetVulnerabilitySummaryCommand(), + ListVulnerabilitiesCommand(), ) return cmd diff --git a/cmd/harbor/root/vulnerability/list.go b/cmd/harbor/root/vulnerability/list.go new file mode 100644 index 000000000..e6a269932 --- /dev/null +++ b/cmd/harbor/root/vulnerability/list.go @@ -0,0 +1,123 @@ +// Copyright Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package vulnerability + +import ( + "fmt" + + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" + "github.com/goharbor/harbor-cli/pkg/api" + "github.com/goharbor/harbor-cli/pkg/utils" + vulnlist "github.com/goharbor/harbor-cli/pkg/views/vulnerability/list" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func ListVulnerabilitiesCommand() *cobra.Command { + var opts api.ListVulnerabilityOptions + + cmd := &cobra.Command{ + Use: "list", + Short: "List vulnerabilities in Security Hub", + Long: "List vulnerabilities from Harbor Security Hub", + Example: ` harbor vulnerability list`, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + if opts.Page < 1 { + return fmt.Errorf("page number must be greater than or equal to 1") + } + + if opts.PageSize <= 0 || opts.PageSize > 100 { + return fmt.Errorf("page size must be greater than 0 and less than or equal to 100") + } + + allVulnerabilities, hasNext, err := fetchVulnerabilities(opts) + if err != nil { + return fmt.Errorf("failed to list vulnerabilities: %v", utils.ParseHarborErrorMsg(err)) + } + + if len(allVulnerabilities) == 0 { + log.Info("No vulnerabilities found") + return nil + } + + formatFlag := viper.GetString("output-format") + if formatFlag != "" { + err = utils.PrintFormat(allVulnerabilities, formatFlag) + if err != nil { + return err + } + } else { + vulnlist.ViewVulnerabilityList(allVulnerabilities, hasNext) + } + + return nil + }, + } + + flags := cmd.Flags() + flags.Int64VarP(&opts.Page, "page", "", 1, "Page number") + flags.Int64VarP(&opts.PageSize, "page-size", "", 10, "Size of per page") + flags.StringVarP(&opts.CVEID, "cve-id", "", "", "Filter by exact CVE ID") + flags.StringVarP(&opts.CVSSScore, "cvss-score", "", "", "Filter by CVSS v3 score range (e.g. 7.0~10.0) or exact score (e.g. 7.0)") + flags.StringVarP(&opts.Severity, "severity", "", "", "Filter by severity level") + flags.StringVarP(&opts.Repository, "repository", "", "", "Filter by exact repository name") + flags.StringVarP(&opts.ProjectName, "project-name", "", "", "Filter by exact project name") + flags.StringVarP(&opts.Package, "package", "", "", "Filter by exact package name") + flags.StringVarP(&opts.Tag, "tag", "", "", "Filter by exact artifact tag") + flags.StringVarP(&opts.Digest, "digest", "", "", "Filter by exact artifact digest") + flags.StringVarP(&opts.Exclude, "exclude", "", "", "Exclude vulnerabilities using a ',' separated query string (e.g., k=v or k=[min~max])") + flags.BoolVarP(&opts.Fixable, "fixable", "", false, "Only show fixable vulnerabilities") + flags.BoolVarP(&opts.All, "all", "", false, "Show all vulnerabilities (up to 1000)") + + return cmd +} + +func fetchVulnerabilities(opts api.ListVulnerabilityOptions) ([]*models.VulnerabilityItem, bool, error) { + if opts.All { + var allVulnerabilities []*models.VulnerabilityItem + + log.Debug("Fetching all vulnerabilities") + opts.PageSize = 100 + opts.Page = 1 + + for { + vulnerabilities, err := api.ListVulnerabilities(opts) + if err != nil { + return nil, false, err + } + + if len(vulnerabilities) == 0 { + break + } + + allVulnerabilities = append(allVulnerabilities, vulnerabilities...) + opts.Page++ + + if opts.Page > 10 { + return allVulnerabilities, true, nil + } + } + + return allVulnerabilities, false, nil + } + + vulnerabilities, err := api.ListVulnerabilities(opts) + if err != nil { + return nil, false, err + } + + return vulnerabilities, false, nil +} diff --git a/doc/cli-docs/harbor-artifact-delete.md b/doc/cli-docs/harbor-artifact-delete.md index f79da59e6..72bfc679b 100644 --- a/doc/cli-docs/harbor-artifact-delete.md +++ b/doc/cli-docs/harbor-artifact-delete.md @@ -22,7 +22,7 @@ harbor artifact delete [flags] ```sh -c, --config string config file (default is $HOME/.config/harbor-cli/config.yaml) - -o, --output-format string Output format. One of: json|yaml + -o, --output-format string Output format. One of: json|yaml|csv -v, --verbose verbose output ``` diff --git a/doc/cli-docs/harbor-artifact-label-add.md b/doc/cli-docs/harbor-artifact-label-add.md index 31c189a47..3354817e3 100644 --- a/doc/cli-docs/harbor-artifact-label-add.md +++ b/doc/cli-docs/harbor-artifact-label-add.md @@ -38,7 +38,7 @@ harbor artifact label add [flags] ```sh -c, --config string config file (default is $HOME/.config/harbor-cli/config.yaml) - -o, --output-format string Output format. One of: json|yaml + -o, --output-format string Output format. One of: json|yaml|csv -v, --verbose verbose output ``` diff --git a/doc/cli-docs/harbor-artifact-label-delete.md b/doc/cli-docs/harbor-artifact-label-delete.md index 42d0008d2..0691ccc2c 100644 --- a/doc/cli-docs/harbor-artifact-label-delete.md +++ b/doc/cli-docs/harbor-artifact-label-delete.md @@ -40,7 +40,7 @@ harbor artifact label delete [flags] ```sh -c, --config string config file (default is $HOME/.config/harbor-cli/config.yaml) - -o, --output-format string Output format. One of: json|yaml + -o, --output-format string Output format. One of: json|yaml|csv -v, --verbose verbose output ``` diff --git a/doc/cli-docs/harbor-artifact-label-list.md b/doc/cli-docs/harbor-artifact-label-list.md index 9254ee144..f68bdcbea 100644 --- a/doc/cli-docs/harbor-artifact-label-list.md +++ b/doc/cli-docs/harbor-artifact-label-list.md @@ -46,7 +46,7 @@ harbor artifact label list [flags] ```sh -c, --config string config file (default is $HOME/.config/harbor-cli/config.yaml) - -o, --output-format string Output format. One of: json|yaml + -o, --output-format string Output format. One of: json|yaml|csv -v, --verbose verbose output ``` diff --git a/doc/cli-docs/harbor-artifact-label.md b/doc/cli-docs/harbor-artifact-label.md index 6a2666e9e..1b095bd18 100644 --- a/doc/cli-docs/harbor-artifact-label.md +++ b/doc/cli-docs/harbor-artifact-label.md @@ -30,7 +30,7 @@ harbor artifact label del //