Skip to content

Commit

Permalink
feat: update cli options to be separate per commands
Browse files Browse the repository at this point in the history
Signed-off-by: Christopher Phillips <[email protected]>
  • Loading branch information
spiffcs committed Jan 24, 2024
1 parent 6f4ed82 commit 33548f1
Show file tree
Hide file tree
Showing 12 changed files with 249 additions and 161 deletions.
14 changes: 8 additions & 6 deletions cmd/grant/cli/command/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,14 @@ func runCheck(cfg *CheckConfig, userInput []string) (errs error) {
}

reportConfig := check.ReportConfig{
Policy: policy,
Format: internal.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
20 changes: 10 additions & 10 deletions cmd/grant/cli/command/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ import (

"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/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 @@ -70,16 +69,17 @@ func runList(cfg *ListConfig, userInput []string) (errs error) {
}
}()

reportConfig := check.ReportConfig{
Format: internal.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()
}
21 changes: 7 additions & 14 deletions cmd/grant/cli/internal/check/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package check
import (
"encoding/json"
"errors"
"fmt"
"strings"
"time"

Expand Down Expand Up @@ -37,7 +38,7 @@ type ReportConfig struct {
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 @@ -49,7 +50,7 @@ func NewReport(rc ReportConfig, userRequests ...string) (*Report, error) {
}

rc.Options.Format = internal.ValidateFormat(rc.Options.Format)
cases := grant.NewCases(rc.Policy, userRequests...)
cases := grant.NewCases(userRequests...)
ec := evalutation.EvaluationConfig{
Policy: rc.Policy,
CheckNonSPDX: rc.Options.CheckNonSPDX,
Expand All @@ -73,18 +74,10 @@ func (r *Report) Render() error {
return r.renderCheckTree()
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.Options.Format {
case internal.Table:
return r.renderList()
case internal.JSON:
return errors.New("json format not yet supported")
}
return errors.Join(r.errors...)
}

type Response struct {
Expand Down Expand Up @@ -155,7 +148,7 @@ 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()
Expand Down
10 changes: 9 additions & 1 deletion cmd/grant/cli/internal/format.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package internal

import "github.com/google/uuid"

type Format string

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

// validFormat returns a valid format or the default format if the given format is invalid
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":
Expand All @@ -18,3 +22,7 @@ func ValidateFormat(f Format) Format {
return Table
}
}

func NewReportID() string {
return uuid.Must(uuid.NewRandom()).String()
}
86 changes: 75 additions & 11 deletions cmd/grant/cli/internal/list/report.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
package list

import (
"encoding/json"
"errors"
"fmt"
"time"

"github.com/anchore/grant/cmd/grant/cli/internal"
"github.com/anchore/grant/event"
"github.com/anchore/grant/grant"
"github.com/anchore/grant/grant/evalutation"
"github.com/anchore/grant/internal/bus"
)

type Report struct {
ReportID string
Results evalutation.Results
Cases []grant.Case
Config ReportConfig
Timestamp string
Monitor *event.ManualStagedProgress
Expand All @@ -23,27 +26,88 @@ type ReportConfig struct {
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 list 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
// If a request is provided, but the sbom cannot be generated, the source will be ignored and an error will be returned
// Where do we render packages that had no licenses?
func NewReport(rc ReportConfig, userRequests ...string) (*Report, error) {
rc.Options.Format = internal.ValidateFormat(rc.Options.Format)
// TODO: we need a builder here that generates cases before the policy is applied
cases := grant.NewCases(userRequests...)
ec := evalutation.EvaluationConfig{
CheckNonSPDX: rc.Options.CheckNonSPDX,
OsiApproved: rc.Options.OsiApproved,
}

results := evalutation.NewResults(ec, cases...)

return &Report{
Results: results,
ReportID: internal.NewReportID(),
Cases: cases,
Config: rc,
Timestamp: time.Now().Format(time.RFC3339),
Monitor: rc.Monitor,
}, nil
}

func (r *Report) Render() error {
switch r.Config.Options.Format {
case internal.Table:
return r.renderList()
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...)
}
}

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

type Result struct {
Input string `json:"input" yaml:"input"`
License string `json:"license" yaml:"license"`
Package string `json:"package" yaml:"package"`
}

func NewResult(input, license, lp string) Result {
return Result{
Input: input,
License: license,
Package: lp,
}
}

func (r *Report) renderJSON() error {
resp := Response{
ReportID: r.ReportID,
Timestamp: r.Timestamp,
Inputs: make([]string, 0),
Results: make([]Result, 0),
}

for _, c := range r.Cases {
resp.Inputs = append(resp.Inputs, c.UserInput)
licenses := c.GetLicenses()
for _, l := range licenses {
resp.Results = append(resp.Results, NewResult(c.UserInput, l.Name, ""))
}
}
jsonData, err := json.Marshal(resp)
if err != nil {
return err
}

bus.Report(string(jsonData))
return nil
}

func (r *Report) renderList() error {
_ = Response{
ReportID: r.ReportID,
Timestamp: r.Timestamp,
Inputs: make([]string, 0),
Results: make([]Result, 0),
}
return nil
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
github.com/gobwas/glob v0.2.3
github.com/google/go-cmp v0.6.0
github.com/google/licenseclassifier/v2 v2.0.0
github.com/google/uuid v1.4.0
github.com/gookit/color v1.5.4
github.com/hashicorp/go-multierror v1.1.1
github.com/jedib0t/go-pretty/v6 v6.4.9
Expand Down Expand Up @@ -96,7 +97,6 @@ require (
github.com/google/go-containerregistry v0.16.1 // indirect
github.com/google/licensecheck v0.3.1 // indirect
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/iancoleman/strcase v0.3.0 // indirect
Expand Down
27 changes: 24 additions & 3 deletions grant/case.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ import (

// Case is a collection of SBOMs and Licenses that are evaluated for a given UserInput
type Case struct {
// SBOMS is a list of SBOMs that have licenses checked against the policy
// SBOMS is a list of SBOMs that were generated for the user input
SBOMS []sbom.SBOM

// Licenses is a list of licenses that are checked against the policy
// Licenses is a list of licenses that were generated for the user input
Licenses []License

// UserInput is the string that was supplied by the user to build the case
Expand All @@ -56,6 +56,28 @@ func NewCases(userInputs ...string) []Case {
return cases
}

func (c Case) GetLicenses() []License {
licenses := make([]License, 0)
for _, sb := range c.SBOMS {
for pkg := range sb.Artifacts.Packages.Enumerate() {
grantPkg := ConvertSyftPackage(pkg)
if len(grantPkg.Licenses) == 0 {
continue
}

for _, l := range grantPkg.Licenses {

Check failure on line 68 in grant/case.go

View workflow job for this annotation

GitHub Actions / Validations

S1011: should replace loop with `licenses = append(licenses, grantPkg.Licenses...)` (gosimple)
licenses = append(licenses, l)
}
}
}

for _, l := range c.Licenses {

Check failure on line 74 in grant/case.go

View workflow job for this annotation

GitHub Actions / Validations

S1011: should replace loop with `licenses = append(licenses, c.Licenses...)` (gosimple)
licenses = append(licenses, l)
}

return licenses
}

type CaseHandler struct {
Backend *backend.ClassifierBackend
}
Expand Down Expand Up @@ -179,7 +201,6 @@ func (ch *CaseHandler) handleFile(path string) (c Case, err error) {

func (ch *CaseHandler) handleLicenseFile(path string) ([]License, error) {
// alright we couldn't get an SBOM, let's see if the bytes are just a LICENSE (google license classifier)
// TODO: this is a little heavy, we might want to generate a backend and reuse it for all the files we're checking

// google license classifier is noisy, so we'll silence it for now
golog.SetOutput(io.Discard)
Expand Down
6 changes: 3 additions & 3 deletions grant/evalutation/license_evaluation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func Test_NewLicenseEvaluations(t *testing.T) {

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
grantCases := fixtureCase(tc.config, tc.caseFixture)
grantCases := fixtureCase(tc.caseFixture)
for _, c := range grantCases {
caseEvaluations := NewLicenseEvaluations(tc.config, c)
if len(caseEvaluations) == 0 {
Expand All @@ -40,6 +40,6 @@ func Test_NewLicenseEvaluations(t *testing.T) {
}
}

func fixtureCase(ec EvaluationConfig, fixturePath string) []grant.Case {
return grant.NewCases(ec.Policy, fixturePath)
func fixtureCase(fixturePath string) []grant.Case {
return grant.NewCases(fixturePath)
}
2 changes: 1 addition & 1 deletion grant/evalutation/license_evalutation.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func checkSBOM(ec EvaluationConfig, sb sbom.SBOM) LicenseEvaluations {
evaluations := make([]LicenseEvaluation, 0)
for pkg := range sb.Artifacts.Packages.Enumerate() {
// since we use syft as a library to generate the sbom we need to convert its packages/licenses to grant types
grantPkg := convertSyftPackage(pkg)
grantPkg := grant.ConvertSyftPackage(pkg)
if len(grantPkg.Licenses) == 0 {
// We need to include an evaluation that shows this package has no licenses
le := NewLicenseEvaluation(grant.License{}, grantPkg, ec.Policy, []Reason{{
Expand Down
Loading

0 comments on commit 33548f1

Please sign in to comment.