mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-22 23:26:39 -08:00
feat: support aliases in CLI flag, env and config (#3481)
This commit is contained in:
2
go.mod
2
go.mod
@@ -316,7 +316,7 @@ require (
|
|||||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||||
github.com/spdx/tools-golang v0.3.1-0.20230104082527-d6f58551be3f
|
github.com/spdx/tools-golang v0.3.1-0.20230104082527-d6f58551be3f
|
||||||
github.com/spf13/afero v1.9.2 // indirect
|
github.com/spf13/afero v1.9.2 // indirect
|
||||||
github.com/spf13/cast v1.5.0 // indirect
|
github.com/spf13/cast v1.5.0
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
github.com/stretchr/objx v0.5.0 // indirect
|
github.com/stretchr/objx v0.5.0 // indirect
|
||||||
github.com/subosito/gotenv v1.4.1 // indirect
|
github.com/subosito/gotenv v1.4.1 // indirect
|
||||||
|
|||||||
@@ -26,6 +26,12 @@ var (
|
|||||||
ConfigName: "db.skip-update",
|
ConfigName: "db.skip-update",
|
||||||
Value: false,
|
Value: false,
|
||||||
Usage: "skip updating vulnerability database",
|
Usage: "skip updating vulnerability database",
|
||||||
|
Aliases: []Alias{
|
||||||
|
{
|
||||||
|
Name: "skip-update",
|
||||||
|
Deprecated: true, // --security-update was renamed to --skip-db-update
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
NoProgressFlag = Flag{
|
NoProgressFlag = Flag{
|
||||||
Name: "no-progress",
|
Name: "no-progress",
|
||||||
@@ -84,7 +90,14 @@ func (f *DBFlagGroup) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *DBFlagGroup) Flags() []*Flag {
|
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) ToOptions() (DBOptions, error) {
|
func (f *DBFlagGroup) ToOptions() (DBOptions, error) {
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ var (
|
|||||||
ConfigName: "kubernetes.context",
|
ConfigName: "kubernetes.context",
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "specify a context to scan",
|
Usage: "specify a context to scan",
|
||||||
|
Aliases: []Alias{
|
||||||
|
{Name: "ctx"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
K8sNamespaceFlag = Flag{
|
K8sNamespaceFlag = Flag{
|
||||||
Name: "namespace",
|
Name: "namespace",
|
||||||
@@ -23,8 +26,11 @@ var (
|
|||||||
ComponentsFlag = Flag{
|
ComponentsFlag = Flag{
|
||||||
Name: "components",
|
Name: "components",
|
||||||
ConfigName: "kubernetes.components",
|
ConfigName: "kubernetes.components",
|
||||||
Value: []string{"workload", "infra"},
|
Value: []string{
|
||||||
Usage: "specify which components to scan",
|
"workload",
|
||||||
|
"infra",
|
||||||
|
},
|
||||||
|
Usage: "specify which components to scan",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -56,7 +62,12 @@ func (f *K8sFlagGroup) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *K8sFlagGroup) Flags() []*Flag {
|
func (f *K8sFlagGroup) Flags() []*Flag {
|
||||||
return []*Flag{f.ClusterContext, f.Namespace, f.KubeConfig, f.Components}
|
return []*Flag{
|
||||||
|
f.ClusterContext,
|
||||||
|
f.Namespace,
|
||||||
|
f.KubeConfig,
|
||||||
|
f.Components,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *K8sFlagGroup) ToOptions() K8sOptions {
|
func (f *K8sFlagGroup) ToOptions() K8sOptions {
|
||||||
|
|||||||
@@ -3,9 +3,12 @@ package flag
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/cast"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
@@ -38,6 +41,15 @@ type Flag struct {
|
|||||||
|
|
||||||
// Deprecated represents if the flag is deprecated
|
// Deprecated represents if the flag is deprecated
|
||||||
Deprecated bool
|
Deprecated bool
|
||||||
|
|
||||||
|
// Aliases represents aliases
|
||||||
|
Aliases []Alias
|
||||||
|
}
|
||||||
|
|
||||||
|
type Alias struct {
|
||||||
|
Name string
|
||||||
|
ConfigName string
|
||||||
|
Deprecated bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type FlagGroup interface {
|
type FlagGroup interface {
|
||||||
@@ -142,39 +154,58 @@ func bind(cmd *cobra.Command, flag *Flag) error {
|
|||||||
viper.SetDefault(flag.ConfigName, flag.Value)
|
viper.SetDefault(flag.ConfigName, flag.Value)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bind CLI flags
|
||||||
if flag.Persistent {
|
if flag.Persistent {
|
||||||
if err := viper.BindPFlag(flag.ConfigName, cmd.PersistentFlags().Lookup(flag.Name)); err != nil {
|
if err := viper.BindPFlag(flag.ConfigName, cmd.PersistentFlags().Lookup(flag.Name)); err != nil {
|
||||||
return err
|
return xerrors.Errorf("bind flag error: %w", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := viper.BindPFlag(flag.ConfigName, cmd.Flags().Lookup(flag.Name)); err != nil {
|
if err := viper.BindPFlag(flag.ConfigName, cmd.Flags().Lookup(flag.Name)); err != nil {
|
||||||
return err
|
return xerrors.Errorf("bind flag error: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// We don't use viper.AutomaticEnv, so we need to add a prefix manually here.
|
|
||||||
if err := viper.BindEnv(flag.ConfigName, strings.ToUpper("trivy_"+strings.ReplaceAll(flag.Name, "-", "_"))); err != nil {
|
// Bind environmental variable
|
||||||
|
if err := bindEnv(flag); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getString(flag *Flag) string {
|
func bindEnv(flag *Flag) error {
|
||||||
if flag == nil {
|
// We don't use viper.AutomaticEnv, so we need to add a prefix manually here.
|
||||||
return ""
|
envName := strings.ToUpper("trivy_" + strings.ReplaceAll(flag.Name, "-", "_"))
|
||||||
|
if err := viper.BindEnv(flag.ConfigName, envName); err != nil {
|
||||||
|
return xerrors.Errorf("bind env error: %w", err)
|
||||||
}
|
}
|
||||||
return viper.GetString(flag.ConfigName)
|
|
||||||
|
// Bind env aliases
|
||||||
|
for _, alias := range flag.Aliases {
|
||||||
|
envAlias := strings.ToUpper("trivy_" + strings.ReplaceAll(alias.Name, "-", "_"))
|
||||||
|
if err := viper.BindEnv(flag.ConfigName, envAlias); err != nil {
|
||||||
|
return xerrors.Errorf("bind env error: %w", err)
|
||||||
|
}
|
||||||
|
if alias.Deprecated {
|
||||||
|
if _, ok := os.LookupEnv(envAlias); ok {
|
||||||
|
log.Logger.Warnf("'%s' is deprecated. Use '%s' instead.", envAlias, envName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getString(flag *Flag) string {
|
||||||
|
return cast.ToString(getValue(flag))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStringSlice(flag *Flag) []string {
|
func getStringSlice(flag *Flag) []string {
|
||||||
if flag == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// viper always returns a string for ENV
|
// viper always returns a string for ENV
|
||||||
// https://github.com/spf13/viper/blob/419fd86e49ef061d0d33f4d1d56d5e2a480df5bb/viper.go#L545-L553
|
// https://github.com/spf13/viper/blob/419fd86e49ef061d0d33f4d1d56d5e2a480df5bb/viper.go#L545-L553
|
||||||
// and uses strings.Field to separate values (whitespace only)
|
// and uses strings.Field to separate values (whitespace only)
|
||||||
// we need to separate env values with ','
|
// we need to separate env values with ','
|
||||||
v := viper.GetStringSlice(flag.ConfigName)
|
v := cast.ToStringSlice(getValue(flag))
|
||||||
switch {
|
switch {
|
||||||
case len(v) == 0: // no strings
|
case len(v) == 0: // no strings
|
||||||
return nil
|
return nil
|
||||||
@@ -185,24 +216,36 @@ func getStringSlice(flag *Flag) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getInt(flag *Flag) int {
|
func getInt(flag *Flag) int {
|
||||||
if flag == nil {
|
return cast.ToInt(getValue(flag))
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return viper.GetInt(flag.ConfigName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBool(flag *Flag) bool {
|
func getBool(flag *Flag) bool {
|
||||||
if flag == nil {
|
return cast.ToBool(getValue(flag))
|
||||||
return false
|
|
||||||
}
|
|
||||||
return viper.GetBool(flag.ConfigName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDuration(flag *Flag) time.Duration {
|
func getDuration(flag *Flag) time.Duration {
|
||||||
|
return cast.ToDuration(getValue(flag))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getValue(flag *Flag) any {
|
||||||
if flag == nil {
|
if flag == nil {
|
||||||
return 0
|
return nil
|
||||||
}
|
}
|
||||||
return viper.GetDuration(flag.ConfigName)
|
|
||||||
|
// First, looks for aliases in config file (trivy.yaml).
|
||||||
|
// Note that viper.RegisterAlias cannot be used for this purpose.
|
||||||
|
var v any
|
||||||
|
for _, alias := range flag.Aliases {
|
||||||
|
if alias.ConfigName == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
v = viper.Get(alias.ConfigName)
|
||||||
|
if v != nil {
|
||||||
|
log.Logger.Warnf("'%s' in config file is deprecated. Use '%s' instead.", alias.ConfigName, flag.ConfigName)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return viper.Get(flag.ConfigName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Flags) groups() []FlagGroup {
|
func (f *Flags) groups() []FlagGroup {
|
||||||
@@ -260,13 +303,17 @@ func (f *Flags) groups() []FlagGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Flags) AddFlags(cmd *cobra.Command) {
|
func (f *Flags) AddFlags(cmd *cobra.Command) {
|
||||||
|
aliases := make(flagAliases)
|
||||||
for _, group := range f.groups() {
|
for _, group := range f.groups() {
|
||||||
for _, flag := range group.Flags() {
|
for _, flag := range group.Flags() {
|
||||||
addFlag(cmd, flag)
|
addFlag(cmd, flag)
|
||||||
|
|
||||||
|
// Register flag aliases
|
||||||
|
aliases.Add(flag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().SetNormalizeFunc(flagNameNormalize)
|
cmd.Flags().SetNormalizeFunc(aliases.NormalizeFunc())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Flags) Usages(cmd *cobra.Command) string {
|
func (f *Flags) Usages(cmd *cobra.Command) string {
|
||||||
@@ -403,20 +450,38 @@ func (f *Flags) ToOptions(appVersion string, args []string, globalFlags *GlobalF
|
|||||||
return opts, nil
|
return opts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func flagNameNormalize(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
type flagAlias struct {
|
||||||
switch name {
|
formalName string
|
||||||
case "skip-update":
|
deprecated bool
|
||||||
name = SkipDBUpdateFlag.Name
|
once sync.Once
|
||||||
case "policy":
|
}
|
||||||
name = ConfigPolicyFlag.Name
|
|
||||||
case "data":
|
// flagAliases have aliases for CLI flags
|
||||||
name = ConfigDataFlag.Name
|
type flagAliases map[string]*flagAlias
|
||||||
case "namespaces":
|
|
||||||
name = PolicyNamespaceFlag.Name
|
func (a flagAliases) Add(flag *Flag) {
|
||||||
case "ctx":
|
if flag == nil {
|
||||||
name = ClusterContextFlag.Name
|
return
|
||||||
case "security-checks":
|
}
|
||||||
name = ScannersFlag.Name
|
for _, alias := range flag.Aliases {
|
||||||
}
|
a[alias.Name] = &flagAlias{
|
||||||
return pflag.NormalizedName(name)
|
formalName: flag.Name,
|
||||||
|
deprecated: alias.Deprecated,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a flagAliases) NormalizeFunc() func(*pflag.FlagSet, string) pflag.NormalizedName {
|
||||||
|
return func(_ *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||||
|
if alias, ok := a[name]; ok {
|
||||||
|
if alias.deprecated {
|
||||||
|
// NormalizeFunc is called several times
|
||||||
|
alias.once.Do(func() {
|
||||||
|
log.Logger.Warnf("'--%s' is deprecated. Use '--%s' instead.", name, alias.formalName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
name = alias.formalName
|
||||||
|
}
|
||||||
|
return pflag.NormalizedName(name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,18 +24,27 @@ var (
|
|||||||
ConfigName: "rego.policy",
|
ConfigName: "rego.policy",
|
||||||
Value: []string{},
|
Value: []string{},
|
||||||
Usage: "specify paths to the Rego policy files directory, applying config files",
|
Usage: "specify paths to the Rego policy files directory, applying config files",
|
||||||
|
Aliases: []Alias{
|
||||||
|
{Name: "policy"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
ConfigDataFlag = Flag{
|
ConfigDataFlag = Flag{
|
||||||
Name: "config-data",
|
Name: "config-data",
|
||||||
ConfigName: "rego.data",
|
ConfigName: "rego.data",
|
||||||
Value: []string{},
|
Value: []string{},
|
||||||
Usage: "specify paths from which data for the Rego policies will be recursively loaded",
|
Usage: "specify paths from which data for the Rego policies will be recursively loaded",
|
||||||
|
Aliases: []Alias{
|
||||||
|
{Name: "data"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
PolicyNamespaceFlag = Flag{
|
PolicyNamespaceFlag = Flag{
|
||||||
Name: "policy-namespaces",
|
Name: "policy-namespaces",
|
||||||
ConfigName: "rego.namespaces",
|
ConfigName: "rego.namespaces",
|
||||||
Value: []string{},
|
Value: []string{},
|
||||||
Usage: "Rego namespaces",
|
Usage: "Rego namespaces",
|
||||||
|
Aliases: []Alias{
|
||||||
|
{Name: "namespaces"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,13 @@ var (
|
|||||||
types.VulnerabilityScanner,
|
types.VulnerabilityScanner,
|
||||||
types.SecretScanner,
|
types.SecretScanner,
|
||||||
},
|
},
|
||||||
|
Aliases: []Alias{
|
||||||
|
{
|
||||||
|
Name: "security-checks",
|
||||||
|
ConfigName: "scan.security-checks",
|
||||||
|
Deprecated: true, // --security-checks was renamed to --scanners
|
||||||
|
},
|
||||||
|
},
|
||||||
Usage: "comma-separated list of what security issues to detect (vuln,config,secret,license)",
|
Usage: "comma-separated list of what security issues to detect (vuln,config,secret,license)",
|
||||||
}
|
}
|
||||||
FilePatternsFlag = Flag{
|
FilePatternsFlag = Flag{
|
||||||
|
|||||||
Reference in New Issue
Block a user