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  }