Skip to content

Commit

Permalink
Add statistics to JSON and YAML reports
Browse files Browse the repository at this point in the history
Signed-off-by: egibs <20933572+egibs@users.noreply.github.com>
  • Loading branch information
egibs committed Dec 19, 2024
1 parent 6e326a4 commit 14dc8d2
Show file tree
Hide file tree
Showing 17 changed files with 69 additions and 27 deletions.
6 changes: 3 additions & 3 deletions cmd/mal/mal.go
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ func main() {
return err
}

err = renderer.Full(ctx, res)
err = renderer.Full(ctx, &mc, res)
if err != nil {
returnCode = ExitRenderFailed
return err
Expand Down Expand Up @@ -467,7 +467,7 @@ func main() {
return err
}

err = renderer.Full(ctx, res)
err = renderer.Full(ctx, &mc, res)
if err != nil {
returnCode = ExitRenderFailed
return err
Expand Down Expand Up @@ -550,7 +550,7 @@ func main() {
return length
}(&res.Files)

err = renderer.Full(ctx, res)
err = renderer.Full(ctx, &mc, res)
if err != nil {
returnCode = ExitRenderFailed
return err
Expand Down
2 changes: 1 addition & 1 deletion pkg/action/archive_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ func TestScanArchive(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := r.Full(ctx, res); err != nil {
if err := r.Full(ctx, nil, res); err != nil {
t.Fatalf("full: %v", err)
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/action/oci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func TestOCI(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := r.Full(ctx, res); err != nil {
if err := r.Full(ctx, nil, res); err != nil {
t.Fatalf("full: %v", err)
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/action/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,7 @@ func Scan(ctx context.Context, c malcontent.Config) (*malcontent.Report, error)
}
return true
})
if c.Stats {
if c.Stats && c.Renderer.Name() != "JSON" && c.Renderer.Name() != "YAML" {
err = render.Statistics(&c, r)
if err != nil {
return r, fmt.Errorf("stats: %w", err)
Expand Down
2 changes: 1 addition & 1 deletion pkg/malcontent/malcontent.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
type Renderer interface {
Scanning(context.Context, string)
File(context.Context, *FileReport) error
Full(context.Context, *Report) error
Full(context.Context, *Config, *Report) error
Name() string
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/refresh/refresh.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ func executeRefresh(ctx context.Context, testData []TestData) error {
return fmt.Errorf("refresh sample data for %s: %w", data.OutputPath, err)
}

if err := data.Config.Renderer.Full(ctx, res); err != nil {
if err := data.Config.Renderer.Full(ctx, nil, res); err != nil {
return fmt.Errorf("render results for %s: %w", data.OutputPath, err)
}

Expand Down
6 changes: 5 additions & 1 deletion pkg/render/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func (r JSON) File(_ context.Context, _ *malcontent.FileReport) error {
return nil
}

func (r JSON) Full(_ context.Context, rep *malcontent.Report) error {
func (r JSON) Full(_ context.Context, c *malcontent.Config, rep *malcontent.Report) error {
jr := Report{
Diff: rep.Diff,
Files: make(map[string]*malcontent.FileReport),
Expand All @@ -52,6 +52,10 @@ func (r JSON) Full(_ context.Context, rep *malcontent.Report) error {
return true
})

if c != nil && c.Stats {
jr.Stats = serializedStats(c, rep)
}

j, err := json.MarshalIndent(jr, "", " ")
if err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion pkg/render/markdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func (r Markdown) File(ctx context.Context, fr *malcontent.FileReport) error {
return nil
}

func (r Markdown) Full(ctx context.Context, rep *malcontent.Report) error {
func (r Markdown) Full(ctx context.Context, _ *malcontent.Config, rep *malcontent.Report) error {
if rep.Diff == nil {
return nil
}
Expand Down
34 changes: 34 additions & 0 deletions pkg/render/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package render
import (
"fmt"
"io"
"sort"

"github.com/chainguard-dev/malcontent/pkg/malcontent"
)
Expand All @@ -15,6 +16,17 @@ type Report struct {
Diff *malcontent.DiffReport `json:",omitempty" yaml:",omitempty"`
Files map[string]*malcontent.FileReport `json:",omitempty" yaml:",omitempty"`
Filter string `json:",omitempty" yaml:",omitempty"`
Stats *Stats `json:",omitempty" yaml:",omitempty"`
}

// Stats stores a JSON- or YAML-friendly Statistics report.
type Stats struct {
PkgStats []malcontent.StrMetric `json:",omitempty" yaml:",omitempty"`
ProcessedFiles int `json:",omitempty" yaml:",omitempty"`
RiskStats []malcontent.IntMetric `json:",omitempty" yaml:",omitempty"`
SkippedFiles int `json:",omitempty" yaml:",omitempty"`
TotalBehaviors int `json:",omitempty" yaml:",omitempty"`
TotalRisks int `json:",omitempty" yaml:",omitempty"`
}

// New returns a new Renderer.
Expand Down Expand Up @@ -56,3 +68,25 @@ func riskEmoji(score int) string {

return symbol
}

func serializedStats(c *malcontent.Config, r *malcontent.Report) *Stats {
pkgStats, _, totalBehaviors := PkgStatistics(c, &r.Files)
riskStats, totalRisks, processedFiles, skippedFiles := RiskStatistics(c, &r.Files)

sort.Slice(pkgStats, func(i, j int) bool {
return pkgStats[i].Key < pkgStats[j].Key
})

sort.Slice(riskStats, func(i, j int) bool {
return riskStats[i].Key < riskStats[j].Key
})

return &Stats{
PkgStats: pkgStats,
ProcessedFiles: processedFiles,
RiskStats: riskStats,
SkippedFiles: skippedFiles,
TotalBehaviors: totalBehaviors,
TotalRisks: totalRisks,
}
}
2 changes: 1 addition & 1 deletion pkg/render/simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (r Simple) File(_ context.Context, fr *malcontent.FileReport) error {
return nil
}

func (r Simple) Full(_ context.Context, rep *malcontent.Report) error {
func (r Simple) Full(_ context.Context, _ *malcontent.Config, rep *malcontent.Report) error {
if rep.Diff == nil {
return nil
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/render/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func smLength(m *sync.Map) int {
return length
}

func riskStatistics(c *malcontent.Config, files *sync.Map) ([]malcontent.IntMetric, int, int, int) {
func RiskStatistics(c *malcontent.Config, files *sync.Map) ([]malcontent.IntMetric, int, int, int) {
length := smLength(files)

riskMap := make(map[int][]string, length)
Expand Down Expand Up @@ -73,7 +73,7 @@ func riskStatistics(c *malcontent.Config, files *sync.Map) ([]malcontent.IntMetr
return stats, total(), processedFiles, skippedFiles
}

func pkgStatistics(_ *malcontent.Config, files *sync.Map) ([]malcontent.StrMetric, int, int) {
func PkgStatistics(_ *malcontent.Config, files *sync.Map) ([]malcontent.StrMetric, int, int) {
length := smLength(files)
numBehaviors := 0
pkgMap := make(map[string]int, length)
Expand Down Expand Up @@ -117,8 +117,8 @@ func pkgStatistics(_ *malcontent.Config, files *sync.Map) ([]malcontent.StrMetri
}

func Statistics(c *malcontent.Config, r *malcontent.Report) error {
riskStats, totalRisks, processedFiles, skippedFiles := riskStatistics(c, &r.Files)
pkgStats, width, totalBehaviors := pkgStatistics(c, &r.Files)
riskStats, totalRisks, processedFiles, skippedFiles := RiskStatistics(c, &r.Files)
pkgStats, width, totalBehaviors := PkgStatistics(c, &r.Files)

statsSymbol := "📊"
riskSymbol := "⚠️ "
Expand Down
2 changes: 1 addition & 1 deletion pkg/render/strings.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func (r StringMatches) File(_ context.Context, fr *malcontent.FileReport) error
return nil
}

func (r StringMatches) Full(_ context.Context, rep *malcontent.Report) error {
func (r StringMatches) Full(_ context.Context, _ *malcontent.Config, rep *malcontent.Report) error {
// Non-diff files are handled on the fly by File()
if rep.Diff == nil {
return nil
Expand Down
2 changes: 1 addition & 1 deletion pkg/render/tea.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ func (r *Interactive) File(ctx context.Context, fr *malcontent.FileReport) error
return nil
}

func (r *Interactive) Full(ctx context.Context, rep *malcontent.Report) error {
func (r *Interactive) Full(ctx context.Context, _ *malcontent.Config, rep *malcontent.Report) error {
defer func() {
r.program.Send(scanCompleteMsg{})
r.wg.Wait()
Expand Down
2 changes: 1 addition & 1 deletion pkg/render/terminal.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func (r Terminal) File(ctx context.Context, fr *malcontent.FileReport) error {
return nil
}

func (r Terminal) Full(ctx context.Context, rep *malcontent.Report) error {
func (r Terminal) Full(ctx context.Context, _ *malcontent.Config, rep *malcontent.Report) error {
// Non-diff files are handled on the fly by File()
if rep.Diff == nil {
return nil
Expand Down
2 changes: 1 addition & 1 deletion pkg/render/terminal_brief.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func (r TerminalBrief) File(_ context.Context, fr *malcontent.FileReport) error
return nil
}

func (r TerminalBrief) Full(_ context.Context, rep *malcontent.Report) error {
func (r TerminalBrief) Full(_ context.Context, _ *malcontent.Config, rep *malcontent.Report) error {
// Non-diff files are handled on the fly by File()
if rep.Diff == nil {
return nil
Expand Down
6 changes: 5 additions & 1 deletion pkg/render/yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func (r YAML) File(_ context.Context, _ *malcontent.FileReport) error {
return nil
}

func (r YAML) Full(_ context.Context, rep *malcontent.Report) error {
func (r YAML) Full(_ context.Context, c *malcontent.Config, rep *malcontent.Report) error {
// Make the sync.Map YAML-friendly
yr := Report{
Diff: rep.Diff,
Expand All @@ -52,6 +52,10 @@ func (r YAML) Full(_ context.Context, rep *malcontent.Report) error {
return true
})

if c != nil && c.Stats {
yr.Stats = serializedStats(c, rep)
}

yaml, err := yaml.Marshal(yr)
if err != nil {
return err
Expand Down
14 changes: 7 additions & 7 deletions tests/samples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func TestJSON(t *testing.T) {
t.Fatalf("scan failed: %v", err)
}

if err := render.Full(ctx, res); err != nil {
if err := render.Full(ctx, nil, res); err != nil {
t.Fatalf("full: %v", err)
}

Expand Down Expand Up @@ -196,7 +196,7 @@ func TestSimple(t *testing.T) {
t.Fatalf("scan failed: %v", err)
}

if err := simple.Full(ctx, res); err != nil {
if err := simple.Full(ctx, nil, res); err != nil {
t.Fatalf("full: %v", err)
}

Expand Down Expand Up @@ -278,7 +278,7 @@ func TestDiff(t *testing.T) {
t.Fatalf("diff failed: %v", err)
}

if err := simple.Full(ctx, res); err != nil {
if err := simple.Full(ctx, nil, res); err != nil {
t.Fatalf("full: %v", err)
}

Expand Down Expand Up @@ -345,7 +345,7 @@ func TestDiffFileChange(t *testing.T) {
t.Fatalf("diff failed: %v", err)
}

if err := simple.Full(ctx, res); err != nil {
if err := simple.Full(ctx, nil, res); err != nil {
t.Fatalf("full: %v", err)
}

Expand Down Expand Up @@ -412,7 +412,7 @@ func TestDiffFileIncrease(t *testing.T) {
t.Fatalf("diff failed: %v", err)
}

if err := simple.Full(ctx, res); err != nil {
if err := simple.Full(ctx, nil, res); err != nil {
t.Fatalf("full: %v", err)
}

Expand Down Expand Up @@ -512,7 +512,7 @@ func TestMarkdown(t *testing.T) {
t.Fatalf("scan failed: %v", err)
}

if err := simple.Full(ctx, res); err != nil {
if err := simple.Full(ctx, nil, res); err != nil {
t.Fatalf("full: %v", err)
}

Expand Down Expand Up @@ -594,7 +594,7 @@ func Template(b *testing.B, paths []string) func() {
if err != nil {
b.Fatal(err)
}
if err := simple.Full(ctx, res); err != nil {
if err := simple.Full(ctx, nil, res); err != nil {
b.Fatalf("full: %v", err)
}
}
Expand Down

0 comments on commit 14dc8d2

Please sign in to comment.