Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add report summary table #8177

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions pkg/report/table/summary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package table

import (
"github.com/aquasecurity/table"
"github.com/aquasecurity/trivy/pkg/types"
)

type Scanner interface {
Header() string
Alignment() table.Alignment

// Count returns the number of findings, but -1 if the scanner is not applicable
Count(result types.Result) int
}

func NewScanner(scanner types.Scanner) Scanner {
switch scanner {
case types.VulnerabilityScanner:
return VulnerabilityScanner{}
case types.MisconfigScanner:
return MisconfigScanner{}
case types.SecretScanner:
return SecretScanner{}
case types.LicenseScanner:
return LicenseScanner{}
}
return nil
}

type scannerAlignment struct{}

func (s scannerAlignment) Alignment() table.Alignment {
return table.AlignCenter
}

type VulnerabilityScanner struct{ scannerAlignment }

func (s VulnerabilityScanner) Header() string {
return "Vulnerabilities"
}

func (s VulnerabilityScanner) Count(result types.Result) int {
if result.Class == types.ClassOSPkg || result.Class == types.ClassLangPkg {
return len(result.Vulnerabilities)
}
return -1
}

type MisconfigScanner struct{ scannerAlignment }

func (s MisconfigScanner) Header() string {
return "Misconfigurations"
}

func (s MisconfigScanner) Count(result types.Result) int {
if result.Class == types.ClassConfig {
return len(result.Misconfigurations)
}
return -1
}

type SecretScanner struct{ scannerAlignment }

func (s SecretScanner) Header() string {
return "Secrets"
}

func (s SecretScanner) Count(result types.Result) int {
if result.Class == types.ClassSecret {
return len(result.Secrets)
}
return -1
}

type LicenseScanner struct{ scannerAlignment }

func (s LicenseScanner) Header() string {
return "Licenses"
}

func (s LicenseScanner) Count(result types.Result) int {
if result.Class == types.ClassLicense {
return len(result.Licenses)
}
return -1
}
75 changes: 75 additions & 0 deletions pkg/report/table/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"strings"

"github.com/fatih/color"
"github.com/samber/lo"
"golang.org/x/xerrors"

"github.com/aquasecurity/table"
"github.com/aquasecurity/tml"
Expand All @@ -29,6 +31,7 @@ var (

// Writer implements Writer and output in tabular form
type Writer struct {
Scanners types.Scanners
Severities []dbTypes.Severity
Output io.Writer

Expand All @@ -53,6 +56,13 @@ type Renderer interface {

// Write writes the result on standard output
func (tw Writer) Write(_ context.Context, report types.Report) error {
if !tw.isOutputToTerminal() {
tml.DisableFormatting()
}

if err := tw.renderSummary(report); err != nil {
return xerrors.Errorf("failed to render summary: %w", err)
}

for _, result := range report.Results {
// Not display a table of custom resources
Expand All @@ -64,6 +74,60 @@ func (tw Writer) Write(_ context.Context, report types.Report) error {
return nil
}

func (tw Writer) renderSummary(report types.Report) error {
// Fprintln has a bug
if err := tml.Fprintf(tw.Output, "\n<underline><bold>Report Summary</bold></underline>\n\n"); err != nil {
return err
}

t := newTableWriter(tw.Output, tw.isOutputToTerminal())
t.SetAutoMerge(false)
t.SetColumnMaxWidth(80)

var scanners []Scanner
for _, scanner := range tw.Scanners {
s := NewScanner(scanner)
if lo.IsNil(s) {
continue
}
scanners = append(scanners, s)
}

headers := []string{
"Target",
"Type",
}
alignments := []table.Alignment{
table.AlignLeft,
table.AlignCenter,
}
for _, scanner := range scanners {
headers = append(headers, scanner.Header())
alignments = append(alignments, scanner.Alignment())
}
t.SetHeaders(headers...)
t.SetAlignment(alignments...)

for _, result := range report.Results {
resultType := string(result.Type)
if result.Class == types.ClassSecret {
resultType = "text"
} else if result.Class == types.ClassLicense {
resultType = "-"
}
rows := []string{
result.Target,
resultType,
}
for _, scanner := range scanners {
rows = append(rows, tw.colorizeCount(scanner.Count(result)))
}
t.AddRows(rows)
}
t.Render()
return nil
}

func (tw Writer) write(result types.Result) {
if result.IsEmpty() && result.Class != types.ClassOSPkg {
return
Expand Down Expand Up @@ -97,6 +161,17 @@ func (tw Writer) isOutputToTerminal() bool {
return IsOutputToTerminal(tw.Output)
}

func (tw Writer) colorizeCount(count int) string {
if count < 0 {
return "-"
}
sprintf := fmt.Sprintf
if count != 0 && tw.isOutputToTerminal() {
sprintf = color.New(color.FgHiRed).SprintfFunc()
}
return sprintf("%d", count)
}

func newTableWriter(output io.Writer, isTerminal bool) *table.Table {
tableWriter := table.New(output)
if isTerminal { // use ansi output if we're not piping elsewhere
Expand Down
1 change: 1 addition & 0 deletions pkg/report/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func Write(ctx context.Context, report types.Report, option flag.Options) (err e
switch option.Format {
case types.FormatTable:
writer = &table.Writer{
Scanners: option.Scanners,
Output: output,
Severities: option.Severities,
Tree: option.DependencyTree,
Expand Down
Loading