Skip to content

Commit

Permalink
chore: refactor policy outside of case
Browse files Browse the repository at this point in the history
Signed-off-by: Christopher Phillips <[email protected]>
  • Loading branch information
spiffcs committed Jan 23, 2024
1 parent 39dc1ae commit 6f4ed82
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 51 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
3 changes: 2 additions & 1 deletion 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 @@ -115,7 +116,7 @@ func runCheck(cfg *CheckConfig, userInput []string) (errs error) {

reportConfig := check.ReportConfig{
Policy: policy,
Format: check.Format(cfg.Output),
Format: internal.Format(cfg.Output),
ShowPackages: cfg.ShowPackages,
CheckNonSPDX: cfg.CheckNonSPDX,
OsiApproved: cfg.OsiApproved,
Expand Down
3 changes: 2 additions & 1 deletion cmd/grant/cli/command/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,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 @@ -70,7 +71,7 @@ func runList(cfg *ListConfig, userInput []string) (errs error) {
}()

reportConfig := check.ReportConfig{
Format: check.Format(cfg.Output),
Format: internal.Format(cfg.Output),
ShowPackages: cfg.ShowPackages,
CheckNonSPDX: cfg.CheckNonSPDX,
Policy: grant.DefaultPolicy(),
Expand Down
62 changes: 30 additions & 32 deletions cmd/grant/cli/internal/check/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"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,12 +32,9 @@ 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.
Expand All @@ -50,12 +48,12 @@ func NewReport(rc ReportConfig, userRequests ...string) (*Report, error) {
rc.Policy = grant.DefaultPolicy()
}

rc.Format = validateFormat(rc.Format)
rc.Options.Format = internal.ValidateFormat(rc.Options.Format)
cases := grant.NewCases(rc.Policy, 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 +68,41 @@ 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()
}
return errors.Join(r.errors...)
}

func (r *Report) RenderList() error {
switch r.Config.Format {
case Table:
switch r.Config.Options.Format {
case internal.Table:
return r.renderList()
case JSON:
case internal.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 +113,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 +124,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 Down Expand Up @@ -165,9 +163,9 @@ func (r *Report) renderCheckTree() error {
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 +176,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 @@ -207,7 +205,7 @@ func (r *Report) renderList() error {
resulList.Indent()
resulList.AppendItem(color.Light.Sprintf("%s", license))
resulList.UnIndent()
if r.Config.ShowPackages {
if r.Config.Options.ShowPackages {
packages := res.Evaluations.Packages(license)
resulList.Indent()
resulList.Indent()
Expand All @@ -218,7 +216,7 @@ func (r *Report) renderList() error {
resulList.UnIndent()
}
}
if r.Config.ShowPackages {
if r.Config.Options.ShowPackages {
renderOrphanPackages(resulList, res, true)
}
}
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
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package check
package internal

type Format string

Expand All @@ -8,7 +8,7 @@ const (
)

// validFormat returns a valid format or the default format if the given format is invalid
func validateFormat(f Format) Format {
func ValidateFormat(f Format) Format {
switch f {
case "json":
return JSON
Expand Down
49 changes: 49 additions & 0 deletions cmd/grant/cli/internal/list/report.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package list

import (
"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"
)

type Report struct {
ReportID string
Results evalutation.Results
Config ReportConfig
Timestamp string
Monitor *event.ManualStagedProgress
errors []error
}

type ReportConfig struct {
Options internal.ReportOptions
Monitor *event.ManualStagedProgress
}

// NewReport will generate a new report for the given format.
// 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,
Config: rc,
Timestamp: time.Now().Format(time.RFC3339),
Monitor: rc.Monitor,
}, nil
}
2 changes: 1 addition & 1 deletion fixtures/multiple/gpl
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ Termination of your rights under this section does not terminate the licenses of
You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.
An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work classifierResults from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.

You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it.

Expand Down
21 changes: 8 additions & 13 deletions grant/case.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ import (
"github.com/anchore/syft/syft/source"
)

// Case is a collection of SBOMs and Licenses that are evaluated against a policy

// 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 []sbom.SBOM
Expand All @@ -35,12 +34,9 @@ type Case struct {

// UserInput is the string that was supplied by the user to build the case
UserInput string

// Policy is the policy that is evaluated against the case
Policy Policy
}

func NewCases(p Policy, userInputs ...string) []Case {
func NewCases(userInputs ...string) []Case {
cases := make([]Case, 0)
ch, err := NewCaseHandler()
if err != nil {
Expand All @@ -54,7 +50,6 @@ func NewCases(p Policy, userInputs ...string) []Case {
log.Errorf("unable to determine case for %s: %+v", userInput, err)
continue
}
c.Policy = p
c.UserInput = userInput
cases = append(cases, c)
}
Expand Down Expand Up @@ -153,13 +148,13 @@ func (ch *CaseHandler) handleFile(path string) (c Case, err error) {
}

// let's see if it's an SBOM
bytes, err := getReadSeeker(path)
sbomBytes, err := getReadSeeker(path)
if err != nil {
// We bail here since we can't get a reader for the file
return c, err
}

sb, _, _, err := format.NewDecoderCollection(format.Decoders()...).Decode(bytes)
sb, _, _, err := format.NewDecoderCollection(format.Decoders()...).Decode(sbomBytes)
if err != nil {
log.Debugf("unable to determine SBOM or licenses for %s: %+v", path, err)
// we want to log the error, but we don't want to return yet
Expand Down Expand Up @@ -203,12 +198,12 @@ func (ch *CaseHandler) handleLicenseFile(path string) ([]License, error) {
// re-enable logging for the rest of the application
golog.SetOutput(os.Stdout)

results := ch.Backend.GetResults()
if len(results) == 0 {
return nil, fmt.Errorf("no results from license classifier")
classifierResults := ch.Backend.GetResults()
if len(classifierResults) == 0 {
return nil, fmt.Errorf("no classifierResults from license classifier")
}

licenses := grantLicenseFromClassifierResults(results)
licenses := grantLicenseFromClassifierResults(classifierResults)
return licenses, nil
}

Expand Down

0 comments on commit 6f4ed82

Please sign in to comment.