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: separate format packages and update list command formats #32

Merged
merged 5 commits into from
Jan 24, 2024
Merged
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
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
Loading