feat: add support for flag groups (#2488)

This commit is contained in:
Teppei Fukuda
2022-07-10 15:03:57 +03:00
committed by GitHub
parent 5b7e0a858d
commit 736e3f11f7
16 changed files with 496 additions and 410 deletions

View File

@@ -30,6 +30,32 @@ type VersionInfo struct {
VulnerabilityDB *metadata.Metadata `json:",omitempty"` VulnerabilityDB *metadata.Metadata `json:",omitempty"`
} }
const (
usageTemplate = `Usage:{{if .Runnable}}
{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
{{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}
Aliases:
{{.NameAndAliases}}{{end}}{{if .HasExample}}
Examples:
{{.Example}}{{end}}{{if .HasAvailableSubCommands}}
Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
%s
Global Flags:
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}
Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}
Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
`
)
var ( var (
outputWriter io.Writer = os.Stdout outputWriter io.Writer = os.Stdout
) )
@@ -199,6 +225,8 @@ func NewImageCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode
ReportFlagGroup: reportFlagGroup, ReportFlagGroup: reportFlagGroup,
ScanFlagGroup: flag.NewScanFlagGroup(), ScanFlagGroup: flag.NewScanFlagGroup(),
SecretFlagGroup: flag.NewSecretFlagGroup(),
VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(),
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -250,8 +278,9 @@ func NewImageCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
SilenceUsage: true, SilenceUsage: true,
} }
cmd.SetFlagErrorFunc(flagErrorFunc)
imageFlags.AddFlags(cmd) imageFlags.AddFlags(cmd)
cmd.SetFlagErrorFunc(flagErrorFunc)
cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, imageFlags.Usages(cmd)))
return cmd return cmd
} }
@@ -267,6 +296,8 @@ func NewFilesystemCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode
ReportFlagGroup: reportFlagGroup, ReportFlagGroup: reportFlagGroup,
ScanFlagGroup: flag.NewScanFlagGroup(), ScanFlagGroup: flag.NewScanFlagGroup(),
SecretFlagGroup: flag.NewSecretFlagGroup(),
VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(),
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -300,6 +331,7 @@ func NewFilesystemCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
cmd.SetFlagErrorFunc(flagErrorFunc) cmd.SetFlagErrorFunc(flagErrorFunc)
fsFlags.AddFlags(cmd) fsFlags.AddFlags(cmd)
cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, fsFlags.Usages(cmd)))
return cmd return cmd
} }
@@ -314,6 +346,8 @@ func NewRootfsCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
MisconfFlagGroup: flag.NewMisconfFlagGroup(), MisconfFlagGroup: flag.NewMisconfFlagGroup(),
ReportFlagGroup: reportFlagGroup, ReportFlagGroup: reportFlagGroup,
ScanFlagGroup: flag.NewScanFlagGroup(), ScanFlagGroup: flag.NewScanFlagGroup(),
SecretFlagGroup: flag.NewSecretFlagGroup(),
VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(),
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -348,6 +382,7 @@ func NewRootfsCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
} }
cmd.SetFlagErrorFunc(flagErrorFunc) cmd.SetFlagErrorFunc(flagErrorFunc)
rootfsFlags.AddFlags(cmd) rootfsFlags.AddFlags(cmd)
cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, rootfsFlags.Usages(cmd)))
return cmd return cmd
} }
@@ -363,6 +398,8 @@ func NewRepositoryCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode
ReportFlagGroup: reportFlagGroup, ReportFlagGroup: reportFlagGroup,
ScanFlagGroup: flag.NewScanFlagGroup(), ScanFlagGroup: flag.NewScanFlagGroup(),
SecretFlagGroup: flag.NewSecretFlagGroup(),
VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(),
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -392,6 +429,7 @@ func NewRepositoryCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
} }
cmd.SetFlagErrorFunc(flagErrorFunc) cmd.SetFlagErrorFunc(flagErrorFunc)
repoFlags.AddFlags(cmd) repoFlags.AddFlags(cmd)
cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, repoFlags.Usages(cmd)))
return cmd return cmd
} }
@@ -415,6 +453,7 @@ func NewClientCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
RemoteFlagGroup: remoteFlags, RemoteFlagGroup: remoteFlags,
ReportFlagGroup: flag.NewReportFlagGroup(), ReportFlagGroup: flag.NewReportFlagGroup(),
ScanFlagGroup: flag.NewScanFlagGroup(), ScanFlagGroup: flag.NewScanFlagGroup(),
VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(),
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -444,6 +483,7 @@ func NewClientCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
} }
cmd.SetFlagErrorFunc(flagErrorFunc) cmd.SetFlagErrorFunc(flagErrorFunc)
clientFlags.AddFlags(cmd) clientFlags.AddFlags(cmd)
cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, clientFlags.Usages(cmd)))
return cmd return cmd
} }
@@ -459,6 +499,12 @@ func NewServerCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
Use: "server [flags]", Use: "server [flags]",
Aliases: []string{"s"}, Aliases: []string{"s"},
Short: "Server mode", Short: "Server mode",
Example: ` # Run a server
$ trivy server
# Listen on 0.0.0.0:10000
$ trivy server --listen 0.0.0.0:10000
`,
Args: cobra.ExactArgs(0), Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if err := serverFlags.Bind(cmd); err != nil { if err := serverFlags.Bind(cmd); err != nil {
@@ -475,6 +521,7 @@ func NewServerCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
} }
cmd.SetFlagErrorFunc(flagErrorFunc) cmd.SetFlagErrorFunc(flagErrorFunc)
serverFlags.AddFlags(cmd) serverFlags.AddFlags(cmd)
cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, serverFlags.Usages(cmd)))
return cmd return cmd
} }
@@ -528,6 +575,7 @@ func NewConfigCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
} }
cmd.SetFlagErrorFunc(flagErrorFunc) cmd.SetFlagErrorFunc(flagErrorFunc)
configFlags.AddFlags(cmd) configFlags.AddFlags(cmd)
cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, configFlags.Usages(cmd)))
return cmd return cmd
} }
@@ -696,6 +744,8 @@ func NewKubernetesCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
MisconfFlagGroup: flag.NewMisconfFlagGroup(), MisconfFlagGroup: flag.NewMisconfFlagGroup(),
ReportFlagGroup: flag.NewReportFlagGroup(), ReportFlagGroup: flag.NewReportFlagGroup(),
ScanFlagGroup: scanFlags, ScanFlagGroup: scanFlags,
SecretFlagGroup: flag.NewSecretFlagGroup(),
VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(),
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "kubernetes [flags] { cluster | all | specific resources like kubectl. eg: pods, pod/NAME }", Use: "kubernetes [flags] { cluster | all | specific resources like kubectl. eg: pods, pod/NAME }",
@@ -736,6 +786,7 @@ func NewKubernetesCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
} }
cmd.SetFlagErrorFunc(flagErrorFunc) cmd.SetFlagErrorFunc(flagErrorFunc)
k8sFlags.AddFlags(cmd) k8sFlags.AddFlags(cmd)
cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, k8sFlags.Usages(cmd)))
return cmd return cmd
} }
@@ -754,6 +805,7 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
ReportFlagGroup: reportFlagGroup, ReportFlagGroup: reportFlagGroup,
ScanFlagGroup: flag.NewScanFlagGroup(), ScanFlagGroup: flag.NewScanFlagGroup(),
SBOMFlagGroup: flag.NewSBOMFlagGroup(), SBOMFlagGroup: flag.NewSBOMFlagGroup(),
VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(),
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -766,7 +818,7 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
$ trivy sbom --format cyclonedx /path/to/report.cdx $ trivy sbom --format cyclonedx /path/to/report.cdx
`, `,
PreRunE: func(cmd *cobra.Command, args []string) error { PreRunE: func(cmd *cobra.Command, args []string) error {
if err := scanFlags.Bind(cmd); err != nil { if err := sbomFlags.Bind(cmd); err != nil {
return xerrors.Errorf("flag bind error: %w", err) return xerrors.Errorf("flag bind error: %w", err)
} }
return validateArgs(cmd, args) return validateArgs(cmd, args)
@@ -790,6 +842,7 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
} }
cmd.SetFlagErrorFunc(flagErrorFunc) cmd.SetFlagErrorFunc(flagErrorFunc)
sbomFlags.AddFlags(cmd) sbomFlags.AddFlags(cmd)
cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, sbomFlags.Usages(cmd)))
return cmd return cmd
} }

View File

@@ -6,7 +6,6 @@ import (
"time" "time"
"github.com/samber/lo" "github.com/samber/lo"
"github.com/spf13/cobra"
"golang.org/x/xerrors" "golang.org/x/xerrors"
) )
@@ -85,40 +84,29 @@ type RedisOptions struct {
// NewCacheFlagGroup returns a default CacheFlagGroup // NewCacheFlagGroup returns a default CacheFlagGroup
func NewCacheFlagGroup() *CacheFlagGroup { func NewCacheFlagGroup() *CacheFlagGroup {
return &CacheFlagGroup{ return &CacheFlagGroup{
ClearCache: lo.ToPtr(ClearCacheFlag), ClearCache: &ClearCacheFlag,
CacheBackend: lo.ToPtr(CacheBackendFlag), CacheBackend: &CacheBackendFlag,
CacheTTL: lo.ToPtr(CacheTTLFlag), CacheTTL: &CacheTTLFlag,
RedisCACert: lo.ToPtr(RedisCACertFlag), RedisCACert: &RedisCACertFlag,
RedisCert: lo.ToPtr(RedisCertFlag), RedisCert: &RedisCertFlag,
RedisKey: lo.ToPtr(RedisKeyFlag), RedisKey: &RedisKeyFlag,
} }
} }
func (f *CacheFlagGroup) flags() []*Flag { func (fg *CacheFlagGroup) Name() string {
return []*Flag{f.ClearCache, f.CacheBackend, f.CacheTTL, f.RedisCACert, f.RedisCert, f.RedisKey} return "Cache"
} }
func (f *CacheFlagGroup) AddFlags(cmd *cobra.Command) { func (fg *CacheFlagGroup) Flags() []*Flag {
for _, flag := range f.flags() { return []*Flag{fg.ClearCache, fg.CacheBackend, fg.CacheTTL, fg.RedisCACert, fg.RedisCert, fg.RedisKey}
addFlag(cmd, flag)
}
} }
func (f *CacheFlagGroup) Bind(cmd *cobra.Command) error { func (fg *CacheFlagGroup) ToOptions() (CacheOptions, error) {
for _, flag := range f.flags() { cacheBackend := getString(fg.CacheBackend)
if err := bind(cmd, flag); err != nil {
return err
}
}
return nil
}
func (f *CacheFlagGroup) ToOptions() (CacheOptions, error) {
cacheBackend := getString(f.CacheBackend)
redisOptions := RedisOptions{ redisOptions := RedisOptions{
RedisCACert: getString(f.RedisCACert), RedisCACert: getString(fg.RedisCACert),
RedisCert: getString(f.RedisCert), RedisCert: getString(fg.RedisCert),
RedisKey: getString(f.RedisKey), RedisKey: getString(fg.RedisKey),
} }
// "redis://" or "fs" are allowed for now // "redis://" or "fs" are allowed for now
@@ -135,9 +123,9 @@ func (f *CacheFlagGroup) ToOptions() (CacheOptions, error) {
} }
return CacheOptions{ return CacheOptions{
ClearCache: getBool(f.ClearCache), ClearCache: getBool(fg.ClearCache),
CacheBackend: cacheBackend, CacheBackend: cacheBackend,
CacheTTL: getDuration(f.CacheTTL), CacheTTL: getDuration(fg.CacheTTL),
RedisOptions: redisOptions, RedisOptions: redisOptions,
}, nil }, nil
} }

View File

@@ -1,8 +1,6 @@
package flag package flag
import ( import (
"github.com/samber/lo"
"github.com/spf13/cobra"
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/log"
@@ -46,6 +44,7 @@ var (
ConfigName: "db.light", ConfigName: "db.light",
Value: false, Value: false,
Usage: "deprecated", Usage: "deprecated",
Deprecated: true,
} }
) )
@@ -71,34 +70,23 @@ type DBOptions struct {
// NewDBFlagGroup returns a default DBFlagGroup // NewDBFlagGroup returns a default DBFlagGroup
func NewDBFlagGroup() *DBFlagGroup { func NewDBFlagGroup() *DBFlagGroup {
return &DBFlagGroup{ return &DBFlagGroup{
Reset: lo.ToPtr(ResetFlag), Reset: &ResetFlag,
DownloadDBOnly: lo.ToPtr(DownloadDBOnlyFlag), DownloadDBOnly: &DownloadDBOnlyFlag,
SkipDBUpdate: lo.ToPtr(SkipDBUpdateFlag), SkipDBUpdate: &SkipDBUpdateFlag,
Light: lo.ToPtr(LightFlag), Light: &LightFlag,
NoProgress: lo.ToPtr(NoProgressFlag), NoProgress: &NoProgressFlag,
DBRepository: lo.ToPtr(DBRepositoryFlag), DBRepository: &DBRepositoryFlag,
} }
} }
func (f *DBFlagGroup) flags() []*Flag { func (f *DBFlagGroup) Name() string {
return "DB"
}
func (f *DBFlagGroup) Flags() []*Flag {
return []*Flag{f.Reset, f.DownloadDBOnly, f.SkipDBUpdate, f.NoProgress, f.DBRepository, f.Light} return []*Flag{f.Reset, f.DownloadDBOnly, f.SkipDBUpdate, f.NoProgress, f.DBRepository, f.Light}
} }
func (f *DBFlagGroup) AddFlags(cmd *cobra.Command) {
for _, flag := range f.flags() {
addFlag(cmd, flag)
}
}
func (f *DBFlagGroup) Bind(cmd *cobra.Command) error {
for _, flag := range f.flags() {
if err := bind(cmd, flag); err != nil {
return err
}
}
return nil
}
func (f *DBFlagGroup) ToOptions() (DBOptions, error) { func (f *DBFlagGroup) ToOptions() (DBOptions, error) {
skipDBUpdate := getBool(f.SkipDBUpdate) skipDBUpdate := getBool(f.SkipDBUpdate)
downloadDBOnly := getBool(f.DownloadDBOnly) downloadDBOnly := getBool(f.DownloadDBOnly)

View File

@@ -1,9 +1,5 @@
package flag package flag
import (
"github.com/spf13/cobra"
)
// e.g. config yaml // e.g. config yaml
// image: // image:
// removed-pkgs: true // removed-pkgs: true
@@ -41,25 +37,14 @@ func NewImageFlagGroup() *ImageFlagGroup {
} }
} }
func (f *ImageFlagGroup) flags() []*Flag { func (f *ImageFlagGroup) Name() string {
return "Image"
}
func (f *ImageFlagGroup) Flags() []*Flag {
return []*Flag{f.Input, f.ScanRemovedPkgs} return []*Flag{f.Input, f.ScanRemovedPkgs}
} }
func (f *ImageFlagGroup) AddFlags(cmd *cobra.Command) {
for _, flag := range f.flags() {
addFlag(cmd, flag)
}
}
func (f *ImageFlagGroup) Bind(cmd *cobra.Command) error {
for _, flag := range f.flags() {
if err := bind(cmd, flag); err != nil {
return err
}
}
return nil
}
func (f *ImageFlagGroup) ToOptions() ImageOptions { func (f *ImageFlagGroup) ToOptions() ImageOptions {
return ImageOptions{ return ImageOptions{
Input: getString(f.Input), Input: getString(f.Input),

View File

@@ -1,10 +1,5 @@
package flag package flag
import (
"github.com/samber/lo"
"github.com/spf13/cobra"
)
var ( var (
ClusterContextFlag = Flag{ ClusterContextFlag = Flag{
Name: "context", Name: "context",
@@ -32,30 +27,19 @@ type K8sOptions struct {
func NewK8sFlagGroup() *K8sFlagGroup { func NewK8sFlagGroup() *K8sFlagGroup {
return &K8sFlagGroup{ return &K8sFlagGroup{
ClusterContext: lo.ToPtr(ClusterContextFlag), ClusterContext: &ClusterContextFlag,
Namespace: lo.ToPtr(K8sNamespaceFlag), Namespace: &K8sNamespaceFlag,
} }
} }
func (f *K8sFlagGroup) flags() []*Flag { func (f *K8sFlagGroup) Name() string {
return "Kubernetes"
}
func (f *K8sFlagGroup) Flags() []*Flag {
return []*Flag{f.ClusterContext, f.Namespace} return []*Flag{f.ClusterContext, f.Namespace}
} }
func (f *K8sFlagGroup) AddFlags(cmd *cobra.Command) {
for _, flag := range f.flags() {
addFlag(cmd, flag)
}
}
func (f *K8sFlagGroup) Bind(cmd *cobra.Command) error {
for _, flag := range f.flags() {
if err := bind(cmd, flag); err != nil {
return err
}
}
return nil
}
func (f *K8sFlagGroup) ToOptions() K8sOptions { func (f *K8sFlagGroup) ToOptions() K8sOptions {
return K8sOptions{ return K8sOptions{
ClusterContext: getString(f.ClusterContext), ClusterContext: getString(f.ClusterContext),

View File

@@ -1,9 +1,6 @@
package flag package flag
import ( import (
"github.com/samber/lo"
"github.com/spf13/cobra"
"github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/log"
) )
@@ -30,6 +27,7 @@ var (
ConfigName: "misconfiguration.skip-policy-update", ConfigName: "misconfiguration.skip-policy-update",
Value: false, Value: false,
Usage: "deprecated", Usage: "deprecated",
Deprecated: true,
} }
TraceFlag = Flag{ TraceFlag = Flag{
Name: "trace", Name: "trace",
@@ -84,35 +82,24 @@ type MisconfOptions struct {
func NewMisconfFlagGroup() *MisconfFlagGroup { func NewMisconfFlagGroup() *MisconfFlagGroup {
return &MisconfFlagGroup{ return &MisconfFlagGroup{
FilePatterns: lo.ToPtr(FilePatternsFlag), FilePatterns: &FilePatternsFlag,
IncludeNonFailures: lo.ToPtr(IncludeNonFailuresFlag), IncludeNonFailures: &IncludeNonFailuresFlag,
SkipPolicyUpdate: lo.ToPtr(SkipPolicyUpdateFlag), SkipPolicyUpdate: &SkipPolicyUpdateFlag,
Trace: lo.ToPtr(TraceFlag), Trace: &TraceFlag,
PolicyPaths: lo.ToPtr(ConfigPolicyFlag), PolicyPaths: &ConfigPolicyFlag,
DataPaths: lo.ToPtr(ConfigDataFlag), DataPaths: &ConfigDataFlag,
PolicyNamespaces: lo.ToPtr(PolicyNamespaceFlag), PolicyNamespaces: &PolicyNamespaceFlag,
} }
} }
func (f *MisconfFlagGroup) flags() []*Flag { func (f *MisconfFlagGroup) Name() string {
return "Misconfiguration"
}
func (f *MisconfFlagGroup) Flags() []*Flag {
return []*Flag{f.FilePatterns, f.IncludeNonFailures, f.SkipPolicyUpdate, f.Trace, f.PolicyPaths, f.DataPaths, f.PolicyNamespaces} return []*Flag{f.FilePatterns, f.IncludeNonFailures, f.SkipPolicyUpdate, f.Trace, f.PolicyPaths, f.DataPaths, f.PolicyNamespaces}
} }
func (f *MisconfFlagGroup) AddFlags(cmd *cobra.Command) {
for _, flag := range f.flags() {
addFlag(cmd, flag)
}
}
func (f *MisconfFlagGroup) Bind(cmd *cobra.Command) error {
for _, flag := range f.flags() {
if err := bind(cmd, flag); err != nil {
return err
}
}
return nil
}
func (f *MisconfFlagGroup) ToOptions() (MisconfOptions, error) { func (f *MisconfFlagGroup) ToOptions() (MisconfOptions, error) {
skipPolicyUpdateFlag := getBool(f.SkipPolicyUpdate) skipPolicyUpdateFlag := getBool(f.SkipPolicyUpdate)
if skipPolicyUpdateFlag { if skipPolicyUpdateFlag {

View File

@@ -1,6 +1,7 @@
package flag package flag
import ( import (
"fmt"
"io" "io"
"strings" "strings"
"time" "time"
@@ -32,11 +33,14 @@ type Flag struct {
// Persistent represents if the flag is persistent // Persistent represents if the flag is persistent
Persistent bool Persistent bool
// Deprecated represents if the flag is deprecated
Deprecated bool
} }
type FlagGroup interface { type FlagGroup interface {
AddFlags(cmd *cobra.Command) Name() string
Bind(cmd *cobra.Command) error Flags() []*Flag
} }
type Flags struct { type Flags struct {
@@ -49,6 +53,8 @@ type Flags struct {
ReportFlagGroup *ReportFlagGroup ReportFlagGroup *ReportFlagGroup
SBOMFlagGroup *SBOMFlagGroup SBOMFlagGroup *SBOMFlagGroup
ScanFlagGroup *ScanFlagGroup ScanFlagGroup *ScanFlagGroup
SecretFlagGroup *SecretFlagGroup
VulnerabilityFlagGroup *VulnerabilityFlagGroup
} }
// Options holds all the runtime configuration // Options holds all the runtime configuration
@@ -63,6 +69,8 @@ type Options struct {
ReportOptions ReportOptions
SBOMOptions SBOMOptions
ScanOptions ScanOptions
SecretOptions
VulnerabilityOptions
// Trivy's version, not populated via CLI flags // Trivy's version, not populated via CLI flags
AppVersion string AppVersion string
@@ -75,37 +83,28 @@ func addFlag(cmd *cobra.Command, flag *Flag) {
if flag == nil || flag.Name == "" { if flag == nil || flag.Name == "" {
return return
} }
var flags *pflag.FlagSet
if flag.Persistent {
flags = cmd.PersistentFlags()
} else {
flags = cmd.Flags()
}
switch v := flag.Value.(type) { switch v := flag.Value.(type) {
case int: case int:
if flag.Persistent { flags.IntP(flag.Name, flag.Shorthand, v, flag.Usage)
cmd.PersistentFlags().IntP(flag.Name, flag.Shorthand, v, flag.Usage)
} else {
cmd.Flags().IntP(flag.Name, flag.Shorthand, v, flag.Usage)
}
case string: case string:
if flag.Persistent { flags.StringP(flag.Name, flag.Shorthand, v, flag.Usage)
cmd.PersistentFlags().StringP(flag.Name, flag.Shorthand, v, flag.Usage)
} else {
cmd.Flags().StringP(flag.Name, flag.Shorthand, v, flag.Usage)
}
case []string: case []string:
if flag.Persistent { flags.StringSliceP(flag.Name, flag.Shorthand, v, flag.Usage)
cmd.PersistentFlags().StringSliceP(flag.Name, flag.Shorthand, v, flag.Usage)
} else {
cmd.Flags().StringSliceP(flag.Name, flag.Shorthand, v, flag.Usage)
}
case bool: case bool:
if flag.Persistent { flags.BoolP(flag.Name, flag.Shorthand, v, flag.Usage)
cmd.PersistentFlags().BoolP(flag.Name, flag.Shorthand, v, flag.Usage)
} else {
cmd.Flags().BoolP(flag.Name, flag.Shorthand, v, flag.Usage)
}
case time.Duration: case time.Duration:
if flag.Persistent { flags.DurationP(flag.Name, flag.Shorthand, v, flag.Usage)
cmd.PersistentFlags().DurationP(flag.Name, flag.Shorthand, v, flag.Usage)
} else {
cmd.PersistentFlags().DurationP(flag.Name, flag.Shorthand, v, flag.Usage)
} }
if flag.Deprecated {
flags.MarkHidden(flag.Name) // nolint: gosec
} }
} }
@@ -166,6 +165,13 @@ func getDuration(flag *Flag) time.Duration {
func (f *Flags) groups() []FlagGroup { func (f *Flags) groups() []FlagGroup {
var groups []FlagGroup var groups []FlagGroup
// This order affects the usage message, so they are sorted by frequency of use.
if f.ScanFlagGroup != nil {
groups = append(groups, f.ScanFlagGroup)
}
if f.ReportFlagGroup != nil {
groups = append(groups, f.ReportFlagGroup)
}
if f.CacheFlagGroup != nil { if f.CacheFlagGroup != nil {
groups = append(groups, f.CacheFlagGroup) groups = append(groups, f.CacheFlagGroup)
} }
@@ -175,47 +181,70 @@ func (f *Flags) groups() []FlagGroup {
if f.ImageFlagGroup != nil { if f.ImageFlagGroup != nil {
groups = append(groups, f.ImageFlagGroup) groups = append(groups, f.ImageFlagGroup)
} }
if f.K8sFlagGroup != nil { if f.SBOMFlagGroup != nil {
groups = append(groups, f.K8sFlagGroup) groups = append(groups, f.SBOMFlagGroup)
}
if f.VulnerabilityFlagGroup != nil {
groups = append(groups, f.VulnerabilityFlagGroup)
} }
if f.MisconfFlagGroup != nil { if f.MisconfFlagGroup != nil {
groups = append(groups, f.MisconfFlagGroup) groups = append(groups, f.MisconfFlagGroup)
} }
if f.SecretFlagGroup != nil {
groups = append(groups, f.SecretFlagGroup)
}
if f.K8sFlagGroup != nil {
groups = append(groups, f.K8sFlagGroup)
}
if f.RemoteFlagGroup != nil { if f.RemoteFlagGroup != nil {
groups = append(groups, f.RemoteFlagGroup) groups = append(groups, f.RemoteFlagGroup)
} }
if f.ReportFlagGroup != nil {
groups = append(groups, f.ReportFlagGroup)
}
if f.SBOMFlagGroup != nil {
groups = append(groups, f.SBOMFlagGroup)
}
if f.ScanFlagGroup != nil {
groups = append(groups, f.ScanFlagGroup)
}
return groups return groups
} }
func (f *Flags) AddFlags(cmd *cobra.Command) { func (f *Flags) AddFlags(cmd *cobra.Command) {
for _, group := range f.groups() { for _, group := range f.groups() {
if group == nil { for _, flag := range group.Flags() {
continue addFlag(cmd, flag)
} }
group.AddFlags(cmd)
} }
cmd.Flags().SetNormalizeFunc(flagNameNormalize) cmd.Flags().SetNormalizeFunc(flagNameNormalize)
} }
func (f *Flags) Usages(cmd *cobra.Command) string {
var usages string
for _, group := range f.groups() {
flags := pflag.NewFlagSet(cmd.Name(), pflag.ContinueOnError)
lflags := cmd.LocalFlags()
for _, flag := range group.Flags() {
if flag == nil {
continue
}
flags.AddFlag(lflags.Lookup(flag.Name))
}
if !flags.HasAvailableFlags() {
continue
}
usages += fmt.Sprintf("%s Flags\n", group.Name())
usages += flags.FlagUsages() + "\n"
}
return strings.TrimSpace(usages)
}
func (f *Flags) Bind(cmd *cobra.Command) error { func (f *Flags) Bind(cmd *cobra.Command) error {
for _, group := range f.groups() { for _, group := range f.groups() {
if group == nil { if group == nil {
continue continue
} }
if err := group.Bind(cmd); err != nil { for _, flag := range group.Flags() {
if err := bind(cmd, flag); err != nil {
return xerrors.Errorf("flag groups: %w", err) return xerrors.Errorf("flag groups: %w", err)
} }
} }
}
return nil return nil
} }
@@ -277,6 +306,14 @@ func (f *Flags) ToOptions(appVersion string, args []string, globalFlags *GlobalF
opts.ScanOptions = f.ScanFlagGroup.ToOptions(args) opts.ScanOptions = f.ScanFlagGroup.ToOptions(args)
} }
if f.SecretFlagGroup != nil {
opts.SecretOptions = f.SecretFlagGroup.ToOptions()
}
if f.VulnerabilityFlagGroup != nil {
opts.VulnerabilityOptions = f.VulnerabilityFlagGroup.ToOptions()
}
return opts, nil return opts, nil
} }

View File

@@ -4,8 +4,6 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/spf13/cobra"
"github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/log"
) )
@@ -87,25 +85,14 @@ func NewServerFlags() *RemoteFlagGroup {
} }
} }
func (f *RemoteFlagGroup) flags() []*Flag { func (f *RemoteFlagGroup) Name() string {
return "Client/Server"
}
func (f *RemoteFlagGroup) Flags() []*Flag {
return []*Flag{f.Token, f.TokenHeader, f.ServerAddr, f.CustomHeaders, f.Listen} return []*Flag{f.Token, f.TokenHeader, f.ServerAddr, f.CustomHeaders, f.Listen}
} }
func (f *RemoteFlagGroup) Bind(cmd *cobra.Command) error {
for _, flag := range f.flags() {
if err := bind(cmd, flag); err != nil {
return err
}
}
return nil
}
func (f *RemoteFlagGroup) AddFlags(cmd *cobra.Command) {
for _, flag := range f.flags() {
addFlag(cmd, flag)
}
}
func (f *RemoteFlagGroup) ToOptions() RemoteOptions { func (f *RemoteFlagGroup) ToOptions() RemoteOptions {
serverAddr := getString(f.ServerAddr) serverAddr := getString(f.ServerAddr)
customHeaders := splitCustomHeaders(getStringSlice(f.CustomHeaders)) customHeaders := splitCustomHeaders(getStringSlice(f.CustomHeaders))

View File

@@ -5,8 +5,6 @@ import (
"os" "os"
"strings" "strings"
"github.com/samber/lo"
"github.com/spf13/cobra"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"golang.org/x/xerrors" "golang.org/x/xerrors"
@@ -87,14 +85,6 @@ var (
Value: strings.Join(dbTypes.SeverityNames, ","), Value: strings.Join(dbTypes.SeverityNames, ","),
Usage: "severities of security issues to be displayed (comma separated)", Usage: "severities of security issues to be displayed (comma separated)",
} }
// Vulnerabilities
IgnoreUnfixedFlag = Flag{
Name: "ignore-unfixed",
ConfigName: "vulnerability.ignore-unfixed",
Value: false,
Usage: "display only fixed vulnerabilities",
}
) )
// ReportFlagGroup composes common printer flag structs // ReportFlagGroup composes common printer flag structs
@@ -105,7 +95,6 @@ type ReportFlagGroup struct {
Template *Flag Template *Flag
DependencyTree *Flag DependencyTree *Flag
ListAllPkgs *Flag ListAllPkgs *Flag
IgnoreUnfixed *Flag
IgnoreFile *Flag IgnoreFile *Flag
IgnorePolicy *Flag IgnorePolicy *Flag
ExitCode *Flag ExitCode *Flag
@@ -119,7 +108,6 @@ type ReportOptions struct {
Template string Template string
DependencyTree bool DependencyTree bool
ListAllPkgs bool ListAllPkgs bool
IgnoreUnfixed bool
IgnoreFile string IgnoreFile string
ExitCode int ExitCode int
IgnorePolicy string IgnorePolicy string
@@ -129,38 +117,26 @@ type ReportOptions struct {
func NewReportFlagGroup() *ReportFlagGroup { func NewReportFlagGroup() *ReportFlagGroup {
return &ReportFlagGroup{ return &ReportFlagGroup{
Format: lo.ToPtr(FormatFlag), Format: &FormatFlag,
ReportFormat: lo.ToPtr(ReportFormatFlag), ReportFormat: &ReportFormatFlag,
Template: lo.ToPtr(TemplateFlag), Template: &TemplateFlag,
DependencyTree: lo.ToPtr(DependencyTreeFlag), DependencyTree: &DependencyTreeFlag,
ListAllPkgs: lo.ToPtr(ListAllPkgsFlag), ListAllPkgs: &ListAllPkgsFlag,
IgnoreUnfixed: lo.ToPtr(IgnoreUnfixedFlag), IgnoreFile: &IgnoreFileFlag,
IgnoreFile: lo.ToPtr(IgnoreFileFlag), IgnorePolicy: &IgnorePolicyFlag,
IgnorePolicy: lo.ToPtr(IgnorePolicyFlag), ExitCode: &ExitCodeFlag,
ExitCode: lo.ToPtr(ExitCodeFlag), Output: &OutputFlag,
Output: lo.ToPtr(OutputFlag), Severity: &SeverityFlag,
Severity: lo.ToPtr(SeverityFlag),
} }
} }
func (f *ReportFlagGroup) flags() []*Flag { func (f *ReportFlagGroup) Name() string {
return []*Flag{f.Format, f.ReportFormat, f.Template, f.DependencyTree, f.ListAllPkgs, f.IgnoreUnfixed, f.IgnoreFile, f.IgnorePolicy, return "Report"
f.ExitCode, f.Output, f.Severity}
} }
func (f *ReportFlagGroup) AddFlags(cmd *cobra.Command) { func (f *ReportFlagGroup) Flags() []*Flag {
for _, flag := range f.flags() { return []*Flag{f.Format, f.ReportFormat, f.Template, f.DependencyTree, f.ListAllPkgs, f.IgnoreFile,
addFlag(cmd, flag) f.IgnorePolicy, f.ExitCode, f.Output, f.Severity}
}
}
func (f *ReportFlagGroup) Bind(cmd *cobra.Command) error {
for _, flag := range f.flags() {
if err := bind(cmd, flag); err != nil {
return err
}
}
return nil
} }
func (f *ReportFlagGroup) ToOptions(out io.Writer) (ReportOptions, error) { func (f *ReportFlagGroup) ToOptions(out io.Writer) (ReportOptions, error) {
@@ -211,12 +187,11 @@ func (f *ReportFlagGroup) ToOptions(out io.Writer) (ReportOptions, error) {
Template: template, Template: template,
DependencyTree: dependencyTree, DependencyTree: dependencyTree,
ListAllPkgs: listAllPkgs, ListAllPkgs: listAllPkgs,
IgnoreUnfixed: getBool(f.IgnoreUnfixed),
IgnoreFile: getString(f.IgnoreFile), IgnoreFile: getString(f.IgnoreFile),
ExitCode: getInt(f.ExitCode), ExitCode: getInt(f.ExitCode),
IgnorePolicy: getString(f.IgnorePolicy), IgnorePolicy: getString(f.IgnorePolicy),
Output: out, Output: out,
Severities: splitSeverity(getString(f.Severity)), Severities: splitSeverity(getStringSlice(f.Severity)),
}, nil }, nil
} }
@@ -232,13 +207,16 @@ func (f *ReportFlagGroup) forceListAllPkgs(format string, listAllPkgs, dependenc
return false return false
} }
func splitSeverity(severity string) []dbTypes.Severity { func splitSeverity(severity []string) []dbTypes.Severity {
if severity == "" { switch {
case len(severity) == 0:
return nil return nil
case len(severity) == 1 && strings.Contains(severity[0], ","): // get severities from flag
severity = strings.Split(severity[0], ",")
} }
var severities []dbTypes.Severity var severities []dbTypes.Severity
for _, s := range strings.Split(severity, ",") { for _, s := range severity {
sev, err := dbTypes.NewSeverity(s) sev, err := dbTypes.NewSeverity(s)
if err != nil { if err != nil {
log.Logger.Warnf("unknown severity option: %s", err) log.Logger.Warnf("unknown severity option: %s", err)

View File

@@ -186,7 +186,6 @@ func TestReportFlagGroup_ToOptions(t *testing.T) {
DependencyTree: &flag.DependencyTreeFlag, DependencyTree: &flag.DependencyTreeFlag,
ListAllPkgs: &flag.ListAllPkgsFlag, ListAllPkgs: &flag.ListAllPkgsFlag,
IgnoreFile: &flag.IgnoreFileFlag, IgnoreFile: &flag.IgnoreFileFlag,
IgnoreUnfixed: &flag.IgnoreUnfixedFlag,
IgnorePolicy: &flag.IgnorePolicyFlag, IgnorePolicy: &flag.IgnorePolicyFlag,
ExitCode: &flag.ExitCodeFlag, ExitCode: &flag.ExitCodeFlag,
Output: &flag.OutputFlag, Output: &flag.OutputFlag,

View File

@@ -1,7 +1,6 @@
package flag package flag
import ( import (
"github.com/spf13/cobra"
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/log"
@@ -10,13 +9,17 @@ import (
var ( var (
ArtifactTypeFlag = Flag{ ArtifactTypeFlag = Flag{
Name: "artifact-type", Name: "artifact-type",
ConfigName: "sbom.artifact-type",
Value: "", Value: "",
Usage: "deprecated", Usage: "deprecated",
Deprecated: true,
} }
SBOMFormatFlag = Flag{ SBOMFormatFlag = Flag{
Name: "sbom-format", Name: "sbom-format",
ConfigName: "sbom.format",
Value: "", Value: "",
Usage: "deprecated", Usage: "deprecated",
Deprecated: true,
} }
) )
@@ -37,20 +40,12 @@ func NewSBOMFlagGroup() *SBOMFlagGroup {
} }
} }
func (f *SBOMFlagGroup) AddFlags(cmd *cobra.Command) { func (f *SBOMFlagGroup) Name() string {
if f.ArtifactType != nil { return "SBOM"
cmd.Flags().String(ArtifactTypeFlag.Name, "", "deprecated")
cmd.Flags().MarkHidden(ArtifactTypeFlag.Name) // nolint: gosec
}
if f.SBOMFormat != nil {
cmd.Flags().String(SBOMFormatFlag.Name, "", "deprecated")
cmd.Flags().MarkHidden(SBOMFormatFlag.Name) // nolint: gosec
}
} }
func (f *SBOMFlagGroup) Bind(cmd *cobra.Command) error { func (f *SBOMFlagGroup) Flags() []*Flag {
// All the flags are deprecated return []*Flag{f.ArtifactType, f.SBOMFormat}
return nil
} }
func (f *SBOMFlagGroup) ToOptions() (SBOMOptions, error) { func (f *SBOMFlagGroup) ToOptions() (SBOMOptions, error) {

View File

@@ -4,8 +4,6 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/samber/lo"
"github.com/spf13/cobra"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/log"
@@ -37,18 +35,6 @@ var (
Value: fmt.Sprintf("%s,%s", types.SecurityCheckVulnerability, types.SecurityCheckSecret), Value: fmt.Sprintf("%s,%s", types.SecurityCheckVulnerability, types.SecurityCheckSecret),
Usage: "comma-separated list of what security issues to detect (vuln,config,secret)", Usage: "comma-separated list of what security issues to detect (vuln,config,secret)",
} }
VulnTypeFlag = Flag{
Name: "vuln-type",
ConfigName: "vulnerability.type",
Value: strings.Join([]string{types.VulnTypeOS, types.VulnTypeLibrary}, ","),
Usage: "comma-separated list of vulnerability types (os,library)",
}
SecretConfigFlag = Flag{
Name: "secret-config",
ConfigName: "secret.config",
Value: "trivy-secret.yaml",
Usage: "specify a path to config file for secret scanning",
}
) )
type ScanFlagGroup struct { type ScanFlagGroup struct {
@@ -56,9 +42,6 @@ type ScanFlagGroup struct {
SkipFiles *Flag SkipFiles *Flag
OfflineScan *Flag OfflineScan *Flag
SecurityChecks *Flag SecurityChecks *Flag
VulnType *Flag
SecretConfig *Flag
} }
type ScanOptions struct { type ScanOptions struct {
@@ -67,42 +50,23 @@ type ScanOptions struct {
SkipFiles []string SkipFiles []string
OfflineScan bool OfflineScan bool
SecurityChecks []string SecurityChecks []string
// Vulnerabilities
VulnType []string
// Secrets
SecretConfigPath string
} }
func NewScanFlagGroup() *ScanFlagGroup { func NewScanFlagGroup() *ScanFlagGroup {
return &ScanFlagGroup{ return &ScanFlagGroup{
SkipDirs: lo.ToPtr(SkipDirsFlag), SkipDirs: &SkipDirsFlag,
SkipFiles: lo.ToPtr(SkipFilesFlag), SkipFiles: &SkipFilesFlag,
OfflineScan: lo.ToPtr(OfflineScanFlag), OfflineScan: &OfflineScanFlag,
SecurityChecks: lo.ToPtr(SecurityChecksFlag), SecurityChecks: &SecurityChecksFlag,
VulnType: lo.ToPtr(VulnTypeFlag),
SecretConfig: lo.ToPtr(SecretConfigFlag),
} }
} }
func (f *ScanFlagGroup) flags() []*Flag { func (f *ScanFlagGroup) Name() string {
return []*Flag{f.SkipDirs, f.SkipFiles, f.OfflineScan, f.SecurityChecks, f.VulnType, f.SecretConfig} return "Scan"
} }
func (f *ScanFlagGroup) Bind(cmd *cobra.Command) error { func (f *ScanFlagGroup) Flags() []*Flag {
for _, flag := range f.flags() { return []*Flag{f.SkipDirs, f.SkipFiles, f.OfflineScan, f.SecurityChecks}
if err := bind(cmd, flag); err != nil {
return err
}
}
return nil
}
func (f *ScanFlagGroup) AddFlags(cmd *cobra.Command) {
for _, flag := range f.flags() {
addFlag(cmd, flag)
}
} }
func (f *ScanFlagGroup) ToOptions(args []string) ScanOptions { func (f *ScanFlagGroup) ToOptions(args []string) ScanOptions {
@@ -116,31 +80,10 @@ func (f *ScanFlagGroup) ToOptions(args []string) ScanOptions {
SkipDirs: getStringSlice(f.SkipDirs), SkipDirs: getStringSlice(f.SkipDirs),
SkipFiles: getStringSlice(f.SkipFiles), SkipFiles: getStringSlice(f.SkipFiles),
OfflineScan: getBool(f.OfflineScan), OfflineScan: getBool(f.OfflineScan),
VulnType: parseVulnType(getStringSlice(f.VulnType)),
SecurityChecks: parseSecurityCheck(getStringSlice(f.SecurityChecks)), SecurityChecks: parseSecurityCheck(getStringSlice(f.SecurityChecks)),
SecretConfigPath: getString(f.SecretConfig),
} }
} }
func parseVulnType(vulnType []string) []string {
switch {
case len(vulnType) == 0: // no types
return nil
case len(vulnType) == 1 && strings.Contains(vulnType[0], ","): // get checks from flag
vulnType = strings.Split(vulnType[0], ",")
}
var vulnTypes []string
for _, v := range vulnType {
if !slices.Contains(types.VulnTypes, v) {
log.Logger.Warnf("unknown vulnerability type: %s", v)
continue
}
vulnTypes = append(vulnTypes, v)
}
return vulnTypes
}
func parseSecurityCheck(securityCheck []string) []string { func parseSecurityCheck(securityCheck []string) []string {
switch { switch {
case len(securityCheck) == 0: // no checks case len(securityCheck) == 0: // no checks

View File

@@ -36,32 +36,6 @@ func TestScanFlagGroup_ToOptions(t *testing.T) {
Target: "alpine:latest", Target: "alpine:latest",
}, },
}, },
{
name: "happy path for OS vulnerabilities",
args: []string{"alpine:latest"},
fields: fields{
vulnType: "os",
securityChecks: "vuln",
},
want: flag.ScanOptions{
Target: "alpine:latest",
VulnType: []string{types.VulnTypeOS},
SecurityChecks: []string{types.SecurityCheckVulnerability},
},
},
{
name: "happy path for library vulnerabilities",
args: []string{"alpine:latest"},
fields: fields{
vulnType: "library",
securityChecks: "vuln",
},
want: flag.ScanOptions{
Target: "alpine:latest",
VulnType: []string{types.VulnTypeLibrary},
SecurityChecks: []string{types.SecurityCheckVulnerability},
},
},
{ {
name: "happy path for configs", name: "happy path for configs",
args: []string{"alpine:latest"}, args: []string{"alpine:latest"},
@@ -85,18 +59,6 @@ func TestScanFlagGroup_ToOptions(t *testing.T) {
`unknown security check: WRONG-CHECK`, `unknown security check: WRONG-CHECK`,
}, },
}, },
{
name: "with wrong vuln type",
fields: fields{
vulnType: "os,nonevuln",
},
want: flag.ScanOptions{
VulnType: []string{types.VulnTypeOS},
},
wantLogs: []string{
`unknown vulnerability type: nonevuln`,
},
},
{ {
name: "without target (args)", name: "without target (args)",
args: []string{}, args: []string{},
@@ -156,7 +118,6 @@ func TestScanFlagGroup_ToOptions(t *testing.T) {
SkipDirs: &flag.SkipDirsFlag, SkipDirs: &flag.SkipDirsFlag,
SkipFiles: &flag.SkipFilesFlag, SkipFiles: &flag.SkipFilesFlag,
OfflineScan: &flag.OfflineScanFlag, OfflineScan: &flag.OfflineScanFlag,
VulnType: &flag.VulnTypeFlag,
SecurityChecks: &flag.SecurityChecksFlag, SecurityChecks: &flag.SecurityChecksFlag,
} }

38
pkg/flag/secret_flags.go Normal file
View File

@@ -0,0 +1,38 @@
package flag
var (
SecretConfigFlag = Flag{
Name: "secret-config",
ConfigName: "secret.config",
Value: "trivy-secret.yaml",
Usage: "specify a path to config file for secret scanning",
}
)
type SecretFlagGroup struct {
SecretConfig *Flag
}
type SecretOptions struct {
SecretConfigPath string
}
func NewSecretFlagGroup() *SecretFlagGroup {
return &SecretFlagGroup{
SecretConfig: &SecretConfigFlag,
}
}
func (f *SecretFlagGroup) Name() string {
return "Secret"
}
func (f *SecretFlagGroup) Flags() []*Flag {
return []*Flag{f.SecretConfig}
}
func (f *SecretFlagGroup) ToOptions() SecretOptions {
return SecretOptions{
SecretConfigPath: getString(f.SecretConfig),
}
}

View File

@@ -0,0 +1,76 @@
package flag
import (
"strings"
"golang.org/x/exp/slices"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/types"
)
var (
VulnTypeFlag = Flag{
Name: "vuln-type",
ConfigName: "vulnerability.type",
Value: strings.Join([]string{types.VulnTypeOS, types.VulnTypeLibrary}, ","),
Usage: "comma-separated list of vulnerability types (os,library)",
}
IgnoreUnfixedFlag = Flag{
Name: "ignore-unfixed",
ConfigName: "vulnerability.ignore-unfixed",
Value: false,
Usage: "display only fixed vulnerabilities",
}
)
type VulnerabilityFlagGroup struct {
VulnType *Flag
IgnoreUnfixed *Flag
}
type VulnerabilityOptions struct {
VulnType []string
IgnoreUnfixed bool
}
func NewVulnerabilityFlagGroup() *VulnerabilityFlagGroup {
return &VulnerabilityFlagGroup{
VulnType: &VulnTypeFlag,
IgnoreUnfixed: &IgnoreUnfixedFlag,
}
}
func (f *VulnerabilityFlagGroup) Name() string {
return "Vulnerability"
}
func (f *VulnerabilityFlagGroup) Flags() []*Flag {
return []*Flag{f.VulnType, f.IgnoreUnfixed}
}
func (f *VulnerabilityFlagGroup) ToOptions() VulnerabilityOptions {
return VulnerabilityOptions{
VulnType: parseVulnType(getStringSlice(f.VulnType)),
IgnoreUnfixed: getBool(f.IgnoreUnfixed),
}
}
func parseVulnType(vulnType []string) []string {
switch {
case len(vulnType) == 0: // no types
return nil
case len(vulnType) == 1 && strings.Contains(vulnType[0], ","): // get checks from flag
vulnType = strings.Split(vulnType[0], ",")
}
var vulnTypes []string
for _, v := range vulnType {
if !slices.Contains(types.VulnTypes, v) {
log.Logger.Warnf("unknown vulnerability type: %s", v)
continue
}
vulnTypes = append(vulnTypes, v)
}
return vulnTypes
}

View File

@@ -0,0 +1,87 @@
package flag_test
import (
"testing"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"github.com/aquasecurity/trivy/pkg/flag"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/types"
)
func TestVulnerabilityFlagGroup_ToOptions(t *testing.T) {
type fields struct {
vulnType string
}
tests := []struct {
name string
args []string
fields fields
want flag.VulnerabilityOptions
wantLogs []string
}{
{
name: "happy path for OS vulnerabilities",
args: []string{"alpine:latest"},
fields: fields{
vulnType: "os",
},
want: flag.VulnerabilityOptions{
VulnType: []string{types.VulnTypeOS},
},
},
{
name: "happy path for library vulnerabilities",
args: []string{"alpine:latest"},
fields: fields{
vulnType: "library",
},
want: flag.VulnerabilityOptions{
VulnType: []string{types.VulnTypeLibrary},
},
},
{
name: "wrong vuln type",
fields: fields{
vulnType: "os,nonevuln",
},
want: flag.VulnerabilityOptions{
VulnType: []string{types.VulnTypeOS},
},
wantLogs: []string{
`unknown vulnerability type: nonevuln`,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
level := zap.WarnLevel
core, obs := observer.New(level)
log.Logger = zap.New(core).Sugar()
viper.Set(flag.VulnTypeFlag.ConfigName, tt.fields.vulnType)
// Assert options
f := &flag.VulnerabilityFlagGroup{
VulnType: &flag.VulnTypeFlag,
}
got := f.ToOptions()
assert.Equalf(t, tt.want, got, "ToOptions()")
// Assert log messages
var gotMessages []string
for _, entry := range obs.AllUntimed() {
gotMessages = append(gotMessages, entry.Message)
}
assert.Equal(t, tt.wantLogs, gotMessages, tt.name)
})
}
}