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: connect evaluation, result, and case types; smoke test #8

Merged
merged 1 commit into from
Dec 8, 2023
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: 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