github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/report/table/table.go (about) 1 package table 2 3 import ( 4 "fmt" 5 "io" 6 "os" 7 "runtime" 8 "strings" 9 "sync" 10 11 "github.com/fatih/color" 12 "golang.org/x/exp/slices" 13 14 "github.com/aquasecurity/table" 15 "github.com/aquasecurity/tml" 16 dbTypes "github.com/aquasecurity/trivy-db/pkg/types" 17 "github.com/devseccon/trivy/pkg/types" 18 ) 19 20 var ( 21 SeverityColor = []func(a ...interface{}) string{ 22 color.New(color.FgCyan).SprintFunc(), // UNKNOWN 23 color.New(color.FgBlue).SprintFunc(), // LOW 24 color.New(color.FgYellow).SprintFunc(), // MEDIUM 25 color.New(color.FgHiRed).SprintFunc(), // HIGH 26 color.New(color.FgRed).SprintFunc(), // CRITICAL 27 } 28 ) 29 30 // Writer implements Writer and output in tabular form 31 type Writer struct { 32 Severities []dbTypes.Severity 33 Output io.Writer 34 35 // Show dependency origin tree 36 Tree bool 37 38 // We have to show a message once about using the '-format json' subcommand to get the full pkgPath 39 ShowMessageOnce *sync.Once 40 41 // For misconfigurations 42 IncludeNonFailures bool 43 Trace bool 44 45 // For licenses 46 LicenseRiskThreshold int 47 IgnoredLicenses []string 48 } 49 50 type Renderer interface { 51 Render() string 52 } 53 54 // Write writes the result on standard output 55 func (tw Writer) Write(report types.Report) error { 56 for _, result := range report.Results { 57 // Not display a table of custom resources 58 if result.Class == types.ClassCustom { 59 continue 60 } 61 tw.write(result) 62 } 63 return nil 64 } 65 66 func (tw Writer) write(result types.Result) { 67 if result.IsEmpty() && result.Class != types.ClassOSPkg { 68 return 69 } 70 71 var renderer Renderer 72 switch { 73 // vulnerability 74 case result.Class == types.ClassOSPkg || result.Class == types.ClassLangPkg: 75 renderer = NewVulnerabilityRenderer(result, tw.isOutputToTerminal(), tw.Tree, tw.Severities) 76 // misconfiguration 77 case result.Class == types.ClassConfig: 78 renderer = NewMisconfigRenderer(result, tw.Severities, tw.Trace, tw.IncludeNonFailures, tw.isOutputToTerminal()) 79 // secret 80 case result.Class == types.ClassSecret: 81 renderer = NewSecretRenderer(result.Target, result.Secrets, tw.isOutputToTerminal(), tw.Severities) 82 // package license 83 case result.Class == types.ClassLicense: 84 renderer = NewPkgLicenseRenderer(result, tw.isOutputToTerminal(), tw.Severities) 85 // file license 86 case result.Class == types.ClassLicenseFile: 87 renderer = NewFileLicenseRenderer(result, tw.isOutputToTerminal(), tw.Severities) 88 default: 89 return 90 } 91 92 _, _ = fmt.Fprint(tw.Output, renderer.Render()) 93 } 94 95 func (tw Writer) isOutputToTerminal() bool { 96 return IsOutputToTerminal(tw.Output) 97 } 98 99 func newTableWriter(output io.Writer, isTerminal bool) *table.Table { 100 tableWriter := table.New(output) 101 if isTerminal { // use ansi output if we're not piping elsewhere 102 tableWriter.SetHeaderStyle(table.StyleBold) 103 tableWriter.SetLineStyle(table.StyleDim) 104 } 105 tableWriter.SetBorders(true) 106 tableWriter.SetAutoMerge(true) 107 tableWriter.SetRowLines(true) 108 109 return tableWriter 110 } 111 112 func summarize(specifiedSeverities []dbTypes.Severity, severityCount map[string]int) (int, []string) { 113 var total int 114 var severities []string 115 for _, sev := range specifiedSeverities { 116 severities = append(severities, sev.String()) 117 } 118 119 var summaries []string 120 for _, severity := range dbTypes.SeverityNames { 121 if !slices.Contains(severities, severity) { 122 continue 123 } 124 count := severityCount[severity] 125 r := fmt.Sprintf("%s: %d", severity, count) 126 summaries = append(summaries, r) 127 total += count 128 } 129 130 return total, summaries 131 } 132 133 func IsOutputToTerminal(output io.Writer) bool { 134 if runtime.GOOS == "windows" { 135 // if its windows, we don't support formatting 136 return false 137 } 138 139 if output != os.Stdout { 140 return false 141 } 142 o, err := os.Stdout.Stat() 143 if err != nil { 144 return false 145 } 146 return (o.Mode() & os.ModeCharDevice) == os.ModeCharDevice 147 } 148 149 func RenderTarget(w io.Writer, target string, isTerminal bool) { 150 if isTerminal { 151 // nolint 152 _ = tml.Fprintf(w, "\n<underline><bold>%s</bold></underline>\n\n", target) 153 } else { 154 _, _ = fmt.Fprintf(w, "\n%s\n", target) 155 _, _ = fmt.Fprintf(w, "%s\n", strings.Repeat("=", len(target))) 156 } 157 } 158 159 func ColorizeSeverity(value, severity string) string { 160 for i, name := range dbTypes.SeverityNames { 161 if severity == name { 162 return SeverityColor[i](value) 163 } 164 } 165 return color.New(color.FgBlue).SprintFunc()(severity) 166 }