Skip to content

Commit

Permalink
feat: connect and add basic smoke tests for evaluation and result typ…
Browse files Browse the repository at this point in the history
…es (#8)

Signed-off-by: Christopher Phillips <[email protected]>
  • Loading branch information
spiffcs authored Dec 8, 2023
1 parent 9288af3 commit 9915d67
Show file tree
Hide file tree
Showing 9 changed files with 177 additions and 89 deletions.
2 changes: 0 additions & 2 deletions cmd/grant/cli/command/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ func Check(app clio.Application) *cobra.Command {
}, cfg)
}

// TODO: upgrade the ui a bit with monitors for SBOM generation and license checking
// Progress can be incremented used on a per package basis when grant.Check is called
func runCheck(cfg CheckConfig, userInput []string) (errs error) {
// check if user provided source by stdin
// note: cat sbom.json | grant check spdx.json - is supported
Expand Down
2 changes: 1 addition & 1 deletion cmd/grant/cli/internal/check/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func (r *Report) renderJSON(out io.Writer) error {
}

func (r *Report) renderTable(out io.Writer) error {
return errors.New("not implemented")

}

// l := list.NewWriter() // TODO: style me
Expand Down
2 changes: 0 additions & 2 deletions grant/case.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ import (
// Case is a collection of SBOMs and Licenses that are evaluated against a policy

type Case struct {
// CaseID is the unique identifier for the case
CaseID string
// SBOMS is a list of SBOMs that have licenses checked against the policy
SBOMS []sbom.SBOM

Expand Down
45 changes: 45 additions & 0 deletions grant/evalutation/license_evaluation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package evalutation

import (
"testing"

"github.com/anchore/grant/grant"
)

func Test_NewLicenseEvaluations(t *testing.T) {
tests := []struct {
name string
config EvaluationConfig
caseFixture string
wantFailed bool
}{
{
name: "NewLicenseEvaluations returns a slice of LicenseEvaluation that fail for the DefaultPolicy",
config: DefaultEvaluationConfig(),
caseFixture: "../../fixtures/multiple",
wantFailed: true,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
grantCases := fixtureCase(tc.config, tc.caseFixture)
for _, c := range grantCases {
caseEvaluations := NewLicenseEvaluations(tc.config, c)
if len(caseEvaluations) == 0 {
t.Fatal("could not build license evaluations")
}
if len(caseEvaluations.Licenses()) == 0 {
t.Fatal("could not build list of licenses from evaluations")
}
if tc.wantFailed && !caseEvaluations.IsFailed() {
t.Fatal("expected license evaluations to fail for default config")
}
}
})
}
}

func fixtureCase(ec EvaluationConfig, fixturePath string) []grant.Case {
return grant.NewCases(&ec.Policy, fixturePath)
}
159 changes: 79 additions & 80 deletions grant/evalutation/license_evalutation.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,84 +2,94 @@ package evalutation

import (
"github.com/anchore/grant/grant"
"github.com/anchore/syft/syft/sbom"
)

func NewLicenseEvaluations(ec EvaluationConfig, c grant.Case) LicenseEvaluations {
evaluations := make([]LicenseEvaluation, 0)
// TODO: probably want to use some concurrency here
for _, sb := range c.SBOMS {
for pkg := range sb.Artifacts.Packages.Enumerate() {
grantPkg := convertSyftPackage(pkg)
// since we use syft as a library to generate the sbom we need to convert its packages/licenses to grant types
if len(grantPkg.Licenses) == 0 {
evaluations = append(evaluations, LicenseEvaluation{
License: grant.License{},
Package: grantPkg,
Policy: ec.Policy,
Reason: []Reason{ReasonNoLicenseFound},
Pass: true,
})
continue
}

for _, l := range grantPkg.Licenses {
if !l.IsSPDX() {
// TODO: check if the config wants us to check for non-SPDX licenses
}
if ec.Policy.IsDenied(l) {
evaluations = append(evaluations, LicenseEvaluation{
License: l,
Package: grantPkg,
Policy: ec.Policy,
Reason: []Reason{ReasonLicenseDenied},
Pass: false,
})
continue
}
// otherwise, the license is allowed
evaluations = append(evaluations, LicenseEvaluation{
License: l,
Package: grantPkg,
Policy: ec.Policy,
Reason: []Reason{ReasonLicenseAllowed},
Pass: true,
})
}
}
evaluations = checkSBOM(ec, c, sb, evaluations)
}

for _, l := range c.Licenses {
if !l.IsSPDX() {
// TODO: check if the config wants us to check for non-SPDX licenses
}
if ec.Policy.IsDenied(l) {
evaluations = append(evaluations, LicenseEvaluation{
License: l,
Package: nil,
Policy: ec.Policy,
Reason: []Reason{ReasonLicenseDenied},
Pass: false,
})
evaluations = checkLicense(ec, nil, l, evaluations)
}

return evaluations
}

func checkSBOM(ec EvaluationConfig, c grant.Case, sb sbom.SBOM, evaluations []LicenseEvaluation) []LicenseEvaluation {
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)
if len(grantPkg.Licenses) == 0 {
le := NewLicenseEvaluation(grant.License{}, grantPkg, ec.Policy, []Reason{ReasonNoLicenseFound}, true)
evaluations = append(evaluations, le)
continue
}
// otherwise, the license is allowed
evaluations = append(evaluations, LicenseEvaluation{
License: l,
Package: nil,
Policy: ec.Policy,
Reason: []Reason{ReasonLicenseAllowed},
Pass: true,
})
}

for _, l := range grantPkg.Licenses {
evaluations = checkLicense(ec, grantPkg, l, evaluations)
}
}
return evaluations
}

func checkLicense(ec EvaluationConfig, pkg *grant.Package, l grant.License, evaluations []LicenseEvaluation) []LicenseEvaluation {
if !l.IsSPDX() {
// TODO: check if the config wants us to check for non-SPDX licenses
}
if ec.Policy.IsDenied(l) {
le := NewLicenseEvaluation(l, pkg, ec.Policy, []Reason{ReasonLicenseDenied}, false)
return append(evaluations, le)
}
le := NewLicenseEvaluation(l, pkg, ec.Policy, []Reason{ReasonLicenseAllowed}, true)
return append(evaluations, le)
}

type LicenseEvaluations []LicenseEvaluation

type LicenseEvaluation struct {
RequestID string
func (le LicenseEvaluations) Packages() []grant.Package {
packages := make([]grant.Package, 0)
// get the set of unique packages from the list...
for _, e := range le {
if e.Package != nil {
packages = append(packages, *e.Package)
}
}
return packages
}

func (le LicenseEvaluations) Licenses() []grant.License {
licenses := make([]grant.License, 0)
// get the set of unique licenses from the list...
for _, e := range le {
licenses = append(licenses, e.License)
}
return licenses
}

func (le LicenseEvaluations) FailedLicenses() []grant.License {
licenses := make([]grant.License, 0)
// get the set of unique licenses from the list...
for _, e := range le {
if !e.Pass {
licenses = append(licenses, e.License)
}
}
return licenses
}

func (le LicenseEvaluations) IsFailed() bool {
for _, e := range le {
if !e.Pass {
return true
}
}
return false
}

type LicenseEvaluation struct {
// inputs into evaluation...
License grant.License // the license that we evaluated
Package *grant.Package // any artifact license is evaluated with
Expand All @@ -92,23 +102,12 @@ type LicenseEvaluation struct {
Pass bool // The final evaluation
}

func (ds LicenseEvaluations) Packages() []grant.Package {
// get the set of unique packages from the list...
panic("not implemented")

}

func (ds LicenseEvaluations) Licenses() []grant.License {
// get the set of unique license from the list...
panic("not implemented")

}

func (ds LicenseEvaluations) Policies() []grant.Policy {
// get the set of unique policies from the list...
panic("not implemented")
}

func (ds LicenseEvaluations) IsFailed() bool {
panic("not implemented")
func NewLicenseEvaluation(license grant.License, pkg *grant.Package, policy grant.Policy, reasons []Reason, pass bool) LicenseEvaluation {
return LicenseEvaluation{
License: license,
Package: pkg,
Policy: policy,
Reason: reasons,
Pass: pass,
}
}
7 changes: 7 additions & 0 deletions grant/evalutation/license_evalutation_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,10 @@ type EvaluationConfig struct {
// CheckNonSPDX is true if non-SPDX licenses should be checked
CheckNonSPDX bool
}

func DefaultEvaluationConfig() EvaluationConfig {
return EvaluationConfig{
Policy: grant.DefaultPolicy(),
CheckNonSPDX: false,
}
}
7 changes: 6 additions & 1 deletion grant/evalutation/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,10 @@ func NewResults(ec EvaluationConfig, cases ...grant.Case) (r Results) {
// Pass T/F + reasons for failure
// Validate() error ([]string reasons)
func (rs Results) Pass() bool {
panic("not implemented")
for _, r := range rs {
if r.Evaluations.IsFailed() {
return false
}
}
return true
}
35 changes: 35 additions & 0 deletions grant/evalutation/result_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package evalutation

import (
"testing"

"github.com/anchore/grant/grant"
)

func Test_NewResults(t *testing.T) {
tests := []struct {
name string
ec EvaluationConfig
fixtures []string
wantPass bool
}{
{
name: "NewResults returns results from a group of cases that cannot pass the default config",
ec: DefaultEvaluationConfig(),
fixtures: []string{
"../../fixtures/multiple",
"../../fixtures/licenses/MIT",
},
wantPass: false,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
cases := grant.NewCases(&tc.ec.Policy, tc.fixtures...)
results := NewResults(tc.ec, cases...)
if tc.wantPass != results.Pass() {
t.Errorf("NewResults() = %v, want %v", results.Pass(), tc.wantPass)
}
})
}
}
7 changes: 4 additions & 3 deletions grant/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ type Policy struct {
}

// DefaultPolicy returns a policy that denies all licenses
func DefaultPolicy() *Policy {
return &Policy{
func DefaultPolicy() Policy {
return Policy{
AllowLicenses: make([]glob.Glob, 0),
DenyLicenses: []glob.Glob{
glob.MustCompile("*"),
Expand All @@ -31,7 +31,8 @@ func DefaultPolicy() *Policy {
// It lower cases all patterns to make matching against the spdx license set case-insensitive
func NewPolicy(allowLicenses, denyLicenses, ignoreLicenses []string) (p *Policy, err error) {
if len(allowLicenses) == 0 && len(denyLicenses) == 0 {
return DefaultPolicy(), nil
defaultPolicy := DefaultPolicy()
return &defaultPolicy, nil
}

var (
Expand Down

0 comments on commit 9915d67

Please sign in to comment.