Files
trivy/pkg/flag/report_flags.go

233 lines
6.6 KiB
Go

package flag
import (
"io"
"os"
"strings"
"golang.org/x/exp/slices"
"golang.org/x/xerrors"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/report"
"github.com/aquasecurity/trivy/pkg/result"
)
// e.g. config yaml
// report:
// format: table
// dependency-tree: true
// exit-code: 1
// severity: HIGH,CRITICAL
var (
FormatFlag = Flag{
Name: "format",
ConfigName: "format",
Shorthand: "f",
Value: report.FormatTable,
Usage: "format (table, json, sarif, template, cyclonedx, spdx, spdx-json, github)",
}
ReportFormatFlag = Flag{
Name: "report",
ConfigName: "report",
Value: "all",
Usage: "specify a report format for the output. (all,summary)",
}
TemplateFlag = Flag{
Name: "template",
ConfigName: "template",
Shorthand: "t",
Value: "",
Usage: "output template",
}
DependencyTreeFlag = Flag{
Name: "dependency-tree",
ConfigName: "dependency-tree",
Value: false,
Usage: "show dependency origin tree (EXPERIMENTAL)",
}
ListAllPkgsFlag = Flag{
Name: "list-all-pkgs",
ConfigName: "list-all-pkgs",
Value: false,
Usage: "enabling the option will output all packages regardless of vulnerability",
}
IgnoreFileFlag = Flag{
Name: "ignorefile",
ConfigName: "ignorefile",
Value: result.DefaultIgnoreFile,
Usage: "specify .trivyignore file",
}
IgnorePolicyFlag = Flag{
Name: "ignore-policy",
ConfigName: "ignore-policy",
Value: "",
Usage: "specify the Rego file path to evaluate each vulnerability",
}
ExitCodeFlag = Flag{
Name: "exit-code",
ConfigName: "exit-code",
Value: 0,
Usage: "specify exit code when any security issues are found",
}
OutputFlag = Flag{
Name: "output",
ConfigName: "output",
Shorthand: "o",
Value: "",
Usage: "output file name",
}
SeverityFlag = Flag{
Name: "severity",
ConfigName: "severity",
Shorthand: "s",
Value: strings.Join(dbTypes.SeverityNames, ","),
Usage: "severities of security issues to be displayed (comma separated)",
}
)
// ReportFlagGroup composes common printer flag structs
// used for commands requiring reporting logic.
type ReportFlagGroup struct {
Format *Flag
ReportFormat *Flag
Template *Flag
DependencyTree *Flag
ListAllPkgs *Flag
IgnoreFile *Flag
IgnorePolicy *Flag
ExitCode *Flag
Output *Flag
Severity *Flag
}
type ReportOptions struct {
Format string
ReportFormat string
Template string
DependencyTree bool
ListAllPkgs bool
IgnoreFile string
ExitCode int
IgnorePolicy string
Output io.Writer
Severities []dbTypes.Severity
}
func NewReportFlagGroup() *ReportFlagGroup {
return &ReportFlagGroup{
Format: &FormatFlag,
ReportFormat: &ReportFormatFlag,
Template: &TemplateFlag,
DependencyTree: &DependencyTreeFlag,
ListAllPkgs: &ListAllPkgsFlag,
IgnoreFile: &IgnoreFileFlag,
IgnorePolicy: &IgnorePolicyFlag,
ExitCode: &ExitCodeFlag,
Output: &OutputFlag,
Severity: &SeverityFlag,
}
}
func (f *ReportFlagGroup) Name() string {
return "Report"
}
func (f *ReportFlagGroup) Flags() []*Flag {
return []*Flag{f.Format, f.ReportFormat, f.Template, f.DependencyTree, f.ListAllPkgs, f.IgnoreFile,
f.IgnorePolicy, f.ExitCode, f.Output, f.Severity}
}
func (f *ReportFlagGroup) ToOptions(out io.Writer) (ReportOptions, error) {
format := getString(f.Format)
template := getString(f.Template)
dependencyTree := getBool(f.DependencyTree)
listAllPkgs := getBool(f.ListAllPkgs)
output := getString(f.Output)
if template != "" {
if format == "" {
log.Logger.Warn("'--template' is ignored because '--format template' is not specified. Use '--template' option with '--format template' option.")
} else if format != "template" {
log.Logger.Warnf("'--template' is ignored because '--format %s' is specified. Use '--template' option with '--format template' option.", format)
}
} else {
if format == report.FormatTemplate {
log.Logger.Warn("'--format template' is ignored because '--template' is not specified. Specify '--template' option when you use '--format template'.")
}
}
// "--list-all-pkgs" option is unavailable with "--format table".
// If user specifies "--list-all-pkgs" with "--format table", we should warn it.
if listAllPkgs && format == report.FormatTable {
log.Logger.Warn(`"--list-all-pkgs" cannot be used with "--format table". Try "--format json" or other formats.`)
}
// "--dependency-tree" option is available only with "--format table".
if dependencyTree {
log.Logger.Infof(`"--dependency-tree" only shows dependencies for "package-lock.json" files`)
if format != report.FormatTable {
log.Logger.Warn(`"--dependency-tree" can be used only with "--format table".`)
}
}
// Enable '--list-all-pkgs' if needed
if f.forceListAllPkgs(format, listAllPkgs, dependencyTree) {
listAllPkgs = true
}
if output != "" {
var err error
if out, err = os.Create(output); err != nil {
return ReportOptions{}, xerrors.Errorf("failed to create an output file: %w", err)
}
}
return ReportOptions{
Format: format,
ReportFormat: getString(f.ReportFormat),
Template: template,
DependencyTree: dependencyTree,
ListAllPkgs: listAllPkgs,
IgnoreFile: getString(f.IgnoreFile),
ExitCode: getInt(f.ExitCode),
IgnorePolicy: getString(f.IgnorePolicy),
Output: out,
Severities: splitSeverity(getStringSlice(f.Severity)),
}, nil
}
func (f *ReportFlagGroup) forceListAllPkgs(format string, listAllPkgs, dependencyTree bool) bool {
if slices.Contains(report.SupportedSBOMFormats, format) && !listAllPkgs {
log.Logger.Debugf("%q automatically enables '--list-all-pkgs'.", report.SupportedSBOMFormats)
return true
}
if dependencyTree && !listAllPkgs {
log.Logger.Debugf("'--dependency-tree' enables '--list-all-pkgs'.")
return true
}
return false
}
func splitSeverity(severity []string) []dbTypes.Severity {
switch {
case len(severity) == 0:
return nil
case len(severity) == 1 && strings.Contains(severity[0], ","): // get severities from flag
severity = strings.Split(severity[0], ",")
}
var severities []dbTypes.Severity
for _, s := range severity {
sev, err := dbTypes.NewSeverity(strings.ToUpper(s))
if err != nil {
log.Logger.Warnf("unknown severity option: %s", err)
continue
}
severities = append(severities, sev)
}
log.Logger.Debugf("Severities: %q", severities)
return severities
}