Skip to content

Commit

Permalink
feat: separate format packages and update list command formats (#32)
Browse files Browse the repository at this point in the history
---------

Signed-off-by: Christopher Phillips <[email protected]>
  • Loading branch information
spiffcs authored Jan 24, 2024
1 parent 39dc1ae commit 082dc48
Show file tree
Hide file tree
Showing 17 changed files with 362 additions and 249 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ curl -sSfL https://raw.githubusercontent.com/anchore/grant/main/install.sh | sh

## Usage

Grant can be used with any container image, sbom document, or directory to scan for licenses and check those results
Grant can be used with any container image, sbom document, or directory to scan for licenses and check those classifierResults
against a set of rules provided by the user.

Rules take the form of a pattern to match the license against, a name to identify the rule, a mode to either allow,
Expand Down
15 changes: 9 additions & 6 deletions cmd/grant/cli/command/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/spf13/cobra"

"github.com/anchore/clio"
"github.com/anchore/grant/cmd/grant/cli/internal"
"github.com/anchore/grant/cmd/grant/cli/internal/check"
"github.com/anchore/grant/cmd/grant/cli/option"
"github.com/anchore/grant/event"
Expand Down Expand Up @@ -114,12 +115,14 @@ func runCheck(cfg *CheckConfig, userInput []string) (errs error) {
}

reportConfig := check.ReportConfig{
Policy: policy,
Format: check.Format(cfg.Output),
ShowPackages: cfg.ShowPackages,
CheckNonSPDX: cfg.CheckNonSPDX,
OsiApproved: cfg.OsiApproved,
Monitor: monitor,
Policy: policy,
Options: internal.ReportOptions{
Format: internal.Format(cfg.Output),
ShowPackages: cfg.ShowPackages,
CheckNonSPDX: cfg.CheckNonSPDX,
OsiApproved: cfg.OsiApproved,
},
Monitor: monitor,
}
rep, err := check.NewReport(reportConfig, userInput...)
if err != nil {
Expand Down
21 changes: 11 additions & 10 deletions cmd/grant/cli/command/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import (
"github.com/spf13/cobra"

"github.com/anchore/clio"
"github.com/anchore/grant/cmd/grant/cli/internal/check"
"github.com/anchore/grant/cmd/grant/cli/internal"
"github.com/anchore/grant/cmd/grant/cli/internal/list"
"github.com/anchore/grant/cmd/grant/cli/option"
"github.com/anchore/grant/event"
"github.com/anchore/grant/grant"
"github.com/anchore/grant/internal/bus"
"github.com/anchore/grant/internal/input"
)
Expand Down Expand Up @@ -69,16 +69,17 @@ func runList(cfg *ListConfig, userInput []string) (errs error) {
}
}()

reportConfig := check.ReportConfig{
Format: check.Format(cfg.Output),
ShowPackages: cfg.ShowPackages,
CheckNonSPDX: cfg.CheckNonSPDX,
Policy: grant.DefaultPolicy(),
Monitor: monitor,
reportConfig := list.ReportConfig{
Options: internal.ReportOptions{
Format: internal.Format(cfg.Output),
ShowPackages: cfg.ShowPackages,
CheckNonSPDX: cfg.CheckNonSPDX,
},
Monitor: monitor,
}
rep, err := check.NewReport(reportConfig, userInput...)
rep, err := list.NewReport(reportConfig, userInput...)
if err != nil {
return err
}
return rep.RenderList()
return rep.Render()
}
20 changes: 0 additions & 20 deletions cmd/grant/cli/internal/check/format.go

This file was deleted.

110 changes: 32 additions & 78 deletions cmd/grant/cli/internal/check/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ package check
import (
"encoding/json"
"errors"
"fmt"
"strings"
"time"

"github.com/gookit/color"
list "github.com/jedib0t/go-pretty/v6/list"

"github.com/anchore/grant/cmd/grant/cli/internal"
"github.com/anchore/grant/event"
"github.com/anchore/grant/grant"
"github.com/anchore/grant/grant/evalutation"
Expand All @@ -31,15 +33,12 @@ type Report struct {
}

type ReportConfig struct {
Format Format
Policy grant.Policy
ShowPackages bool
CheckNonSPDX bool
OsiApproved bool
Monitor *event.ManualStagedProgress
Policy grant.Policy
Options internal.ReportOptions
Monitor *event.ManualStagedProgress
}

// NewReport will generate a new report for the given format.
// NewReport will generate a new report for the given format for the check command
// The supplied policy is applied to all user requests.
// If no policy is provided, the default policy will be used
// If no requests are provided, an empty report will be generated
Expand All @@ -50,12 +49,12 @@ func NewReport(rc ReportConfig, userRequests ...string) (*Report, error) {
rc.Policy = grant.DefaultPolicy()
}

rc.Format = validateFormat(rc.Format)
cases := grant.NewCases(rc.Policy, userRequests...)
rc.Options.Format = internal.ValidateFormat(rc.Options.Format)
cases := grant.NewCases(userRequests...)
ec := evalutation.EvaluationConfig{
Policy: rc.Policy,
CheckNonSPDX: rc.CheckNonSPDX,
OsiApproved: rc.OsiApproved,
CheckNonSPDX: rc.Options.CheckNonSPDX,
OsiApproved: rc.Options.OsiApproved,
}

results := evalutation.NewResults(ec, cases...)
Expand All @@ -70,41 +69,33 @@ func NewReport(rc ReportConfig, userRequests ...string) (*Report, error) {

// Render will call Render on each result in the report and return the report
func (r *Report) Render() error {
switch r.Config.Format {
case Table:
switch r.Config.Options.Format {
case internal.Table:
return r.renderCheckTree()
case JSON:
case internal.JSON:
return r.renderJSON()
default:
r.errors = append(r.errors, fmt.Errorf("invalid format: %s; valid formats are: %s", r.Config.Options.Format, internal.ValidFormats))
return errors.Join(r.errors...)
}
return errors.Join(r.errors...)
}

func (r *Report) RenderList() error {
switch r.Config.Format {
case Table:
return r.renderList()
case JSON:
return errors.New("json format not yet supported")
}
return errors.Join(r.errors...)
}

type GrantReport struct {
ReportID string `json:"report_id" yaml:"report_id"`
Timestamp string `json:"timestamp" yaml:"timestamp"`
Inputs []string `json:"inputs" yaml:"inputs"`
Results []ReportEvaluation `json:"results" yaml:"results"`
type Response struct {
ReportID string `json:"report_id" yaml:"report_id"`
Timestamp string `json:"timestamp" yaml:"timestamp"`
Inputs []string `json:"inputs" yaml:"inputs"`
Results []Evaluation `json:"results" yaml:"results"`
}

type ReportEvaluation struct {
type Evaluation struct {
Input string `json:"input" yaml:"input"`
License string `json:"license" yaml:"license"`
Package string `json:"package" yaml:"package"`
Passed bool `json:"passed" yaml:"passed"`
Reasons []string `json:"reasons" yaml:"reasons"`
}

func NewReportEvaluation(input string, le evalutation.LicenseEvaluation) ReportEvaluation {
func NewEvaluation(input string, le evalutation.LicenseEvaluation) Evaluation {
licenseName := le.License.SPDXExpression
if !le.License.IsSPDX() {
licenseName = le.License.Name
Expand All @@ -115,7 +106,7 @@ func NewReportEvaluation(input string, le evalutation.LicenseEvaluation) ReportE
details := r.Detail
reasons = append(reasons, details)
}
re := ReportEvaluation{
re := Evaluation{
Input: input,
License: licenseName,
Package: le.Package.Name,
Expand All @@ -126,14 +117,14 @@ func NewReportEvaluation(input string, le evalutation.LicenseEvaluation) ReportE
}

func (r *Report) renderJSON() error {
evaluations := make([]ReportEvaluation, 0)
evaluations := make([]Evaluation, 0)
for _, res := range r.Results {
for _, e := range res.Evaluations {
re := NewReportEvaluation(res.Case.UserInput, e)
re := NewEvaluation(res.Case.UserInput, e)
evaluations = append(evaluations, re)
}
}
report := GrantReport{
report := Response{
ReportID: r.ReportID,
Timestamp: r.Timestamp,
Inputs: r.Results.UserInputs(),
Expand All @@ -157,17 +148,17 @@ func (r *Report) renderCheckTree() error {
uiLists = append(uiLists, resulList)
resulList.AppendItem(color.Primary.Sprintf("%s", res.Case.UserInput))

for _, rule := range res.Case.Policy.Rules {
for _, rule := range r.Config.Policy.Rules {
failedEvaluations := r.Results.GetFailedEvaluations(res.Case.UserInput, rule)
if len(failedEvaluations) == 0 {
resulList.Indent()
resulList.AppendItem(color.Success.Sprintf("No License Violations Found for Rule %s", rule.Name))
resulList.UnIndent()
continue
}
renderEvaluations(rule, r.Config.ShowPackages, resulList, failedEvaluations)
renderEvaluations(rule, r.Config.Options.ShowPackages, resulList, failedEvaluations)
}
if r.Config.OsiApproved {
if r.Config.Options.OsiApproved {
osiRule := grant.Rule{
Name: evalutation.RuleNameNotOSIApproved,
}
Expand All @@ -178,10 +169,10 @@ func (r *Report) renderCheckTree() error {
resulList.AppendItem(color.Success.Sprintf("%s", "No OSI Violations Found"))
resulList.UnIndent()
} else {
renderEvaluations(osiRule, r.Config.ShowPackages, resulList, failedEvaluations)
renderEvaluations(osiRule, r.Config.Options.ShowPackages, resulList, failedEvaluations)
}
}
if r.Config.ShowPackages {
if r.Config.Options.ShowPackages {
renderOrphanPackages(resulList, res, false) // keep primary coloring for tree
}
}
Expand All @@ -195,43 +186,6 @@ func (r *Report) renderCheckTree() error {
return nil
}

func (r *Report) renderList() error {
var uiLists []list.Writer
for _, res := range r.Results {
r.Monitor.Increment()
r.Monitor.AtomicStage.Set(res.Case.UserInput)
resulList := newList()
uiLists = append(uiLists, resulList)
resulList.AppendItem(color.Primary.Sprintf("%s", res.Case.UserInput))
for _, license := range res.Evaluations.GetLicenses() {
resulList.Indent()
resulList.AppendItem(color.Light.Sprintf("%s", license))
resulList.UnIndent()
if r.Config.ShowPackages {
packages := res.Evaluations.Packages(license)
resulList.Indent()
resulList.Indent()
for _, pkg := range packages {
resulList.AppendItem(color.Secondary.Sprintf("%s", pkg))
}
resulList.UnIndent()
resulList.UnIndent()
}
}
if r.Config.ShowPackages {
renderOrphanPackages(resulList, res, true)
}
}
r.Monitor.AtomicStage.Set(strings.Join(r.Results.UserInputs(), ", "))

// segment the results into lists by user input
// lists can optionally show the packages that were evaluated
for _, l := range uiLists {
bus.Report(l.Render())
}
return nil
}

func renderOrphanPackages(l list.Writer, res evalutation.Result, invert bool) {
title := color.Secondary
newItem := color.Light
Expand Down
8 changes: 8 additions & 0 deletions cmd/grant/cli/internal/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package internal

type ReportOptions struct {
Format Format
ShowPackages bool
CheckNonSPDX bool
OsiApproved bool
}
28 changes: 28 additions & 0 deletions cmd/grant/cli/internal/format.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package internal

import "github.com/google/uuid"

type Format string

const (
JSON Format = "json"
Table Format = "table"
)

var ValidFormats = []Format{JSON, Table}

// ValidateFormat returns a valid format or the default format if the given format is invalid
func ValidateFormat(f Format) Format {
switch f {
case "json":
return JSON
case "table":
return Table
default:
return Table
}
}

func NewReportID() string {
return uuid.Must(uuid.NewRandom()).String()
}
Loading

0 comments on commit 082dc48

Please sign in to comment.