From f71f180293ca5767e5d65bb292a7cfb076673751 Mon Sep 17 00:00:00 2001 From: Christopher Angelo Phillips <32073428+spiffcs@users.noreply.github.com> Date: Fri, 8 Dec 2023 14:00:06 -0500 Subject: [PATCH] refactor: reorient packages for easier library use (#6) refactor: reorient packages for easier library use (#6) --------- Signed-off-by: Christopher Phillips --- DEVELOPING.md | 38 ++- cmd/grant/cli/command/check.go | 23 +- .../grant/cli/internal/check}/format.go | 2 +- .../grant/cli/internal/check}/report.go | 82 +++--- cmd/grant/cli/option/check.go | 7 +- grant/{report/input.go => case.go} | 141 +++++++--- grant/evalutation/license_evalutation.go | 47 ++++ .../evalutation/license_evalutation_config.go | 11 + grant/evalutation/result.go | 35 +++ grant/evalutation/sbom.go | 113 ++++++++ grant/evalutation/syft.go | 108 ++++++++ grant/policy.go | 14 +- grant/report/eval_license.go | 39 --- grant/report/eval_sbom.go | 250 ------------------ grant/report/evaluation.go | 40 --- grant/report/evaluation_test.go | 1 - grant/report/report_test.go | 62 ----- grant/report/request.go | 118 --------- grant/report/request_test.go | 64 ----- grant/report/result_test.go | 112 -------- 20 files changed, 518 insertions(+), 789 deletions(-) rename {grant/report => cmd/grant/cli/internal/check}/format.go (95%) rename {grant/report => cmd/grant/cli/internal/check}/report.go (53%) rename grant/{report/input.go => case.go} (53%) create mode 100644 grant/evalutation/license_evalutation.go create mode 100644 grant/evalutation/license_evalutation_config.go create mode 100644 grant/evalutation/result.go create mode 100644 grant/evalutation/sbom.go create mode 100644 grant/evalutation/syft.go delete mode 100644 grant/report/eval_license.go delete mode 100644 grant/report/eval_sbom.go delete mode 100644 grant/report/evaluation.go delete mode 100644 grant/report/evaluation_test.go delete mode 100644 grant/report/report_test.go delete mode 100644 grant/report/request.go delete mode 100644 grant/report/request_test.go delete mode 100644 grant/report/result_test.go diff --git a/DEVELOPING.md b/DEVELOPING.md index 510fa66..23931e7 100644 --- a/DEVELOPING.md +++ b/DEVELOPING.md @@ -1 +1,37 @@ -## Developing \ No newline at end of file +## Developing + +### Today +``` +cmd/grant/ + command/ + check + list + +grant/ + license + package + policy + + + report/ + ... + +``` + +### Future +``` +cmd/grant/ + command/ + check + list + +grant/ + license + package + policy + + + report/ + ... + +``` \ No newline at end of file diff --git a/cmd/grant/cli/command/check.go b/cmd/grant/cli/command/check.go index ba846fd..8c1a66e 100644 --- a/cmd/grant/cli/command/check.go +++ b/cmd/grant/cli/command/check.go @@ -9,9 +9,9 @@ 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/option" "github.com/anchore/grant/grant" - "github.com/anchore/grant/grant/report" "github.com/anchore/grant/internal/input" ) @@ -44,22 +44,29 @@ func Check(app clio.Application) *cobra.Command { // 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, sources []string) (errs error) { +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 // it will generate results for both stdin and spdx.json isStdin, _ := input.IsStdinPipeOrRedirect() - if isStdin && !slices.Contains(sources, "-") { - sources = append(sources, "-") + if isStdin && !slices.Contains(userInput, "-") { + userInput = append(userInput, "-") } - policy, err := grant.NewPolicy(cfg.AllowLicenses, cfg.DenyLicenses) + policy, err := grant.NewPolicy(cfg.AllowLicenses, cfg.DenyLicenses, cfg.IgnoreLicenses) if err != nil { return errors.Wrap(err, fmt.Sprintf("could not check licenses; could not build policy from config: %s", cfg.Config)) } // TODO: we need to support the ability to write the report to a file without redirecting stdout - return report.NewReport(report.Format(cfg.Format), policy, sources...). - Run(). - Render(os.Stdout) + checkConfig := check.Config{ + Policy: policy, + } + + rep, err := check.NewReport(check.Format(cfg.Format), checkConfig, userInput...) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("unable to create report for inputs %s", userInput)) + } + + return rep.Render(os.Stdout) } diff --git a/grant/report/format.go b/cmd/grant/cli/internal/check/format.go similarity index 95% rename from grant/report/format.go rename to cmd/grant/cli/internal/check/format.go index 3fac189..6fea576 100644 --- a/grant/report/format.go +++ b/cmd/grant/cli/internal/check/format.go @@ -1,4 +1,4 @@ -package report +package check type Format string diff --git a/grant/report/report.go b/cmd/grant/cli/internal/check/report.go similarity index 53% rename from grant/report/report.go rename to cmd/grant/cli/internal/check/report.go index 9cd9441..2d0ad09 100644 --- a/grant/report/report.go +++ b/cmd/grant/cli/internal/check/report.go @@ -1,76 +1,56 @@ -package report +package check import ( - "encoding/json" "errors" "io" "time" "github.com/anchore/grant/grant" + "github.com/anchore/grant/grant/evalutation" ) -// Report tracks the requests/results of a license check. -// `grant alpine:latest ./foo` is a single report with two requests -// The first request is easy. Generate an SBOM for alpine:latest and run the policy against it. -// The second request is a little more complicated. Generate an SBOM for ./foo and run the policy against it. -// This is complex because the directory could contain multiple SBOMs, so we need to run the policy against each one. -// Requests have the string that generated the request along with a list of results for that request. -// -// Here is the summary of what multiple source inputs can be configured for a report. -// A source can be one of the following... -// Single Sources Provider: -// - a path to an sbom file (uses the given SBOM (spdx, cyclonedx, etc)) -// - a path to a directory (generates an SBOM for the given directory) (no other sbom files in the directory) -// TODO: - a path to some archive (generates an SBOM for the given archive) -// TODO: - a path to a container image (generates an SBOM for the given image) -// -// Multiple Source Provider: -// - multiple paths to sbom files -// TODO: - a path to a directory containing sbom files -// TODO: - a path to a container image with sbom files -// TODO: - a path to a directory containing container images (1.tar.gz 2.tar.gz 3.tar.gz) -// TODO: - a path to a directory containing container images and sbom files +// Report presents the results of a grant check command `grant alpine:latest ./foo` +// The above command will have two results. +// The first result is easy. Generate an SBOM for alpine:latest and run the policy against it. +// The second result is a little more complicated. Visit each leaf of ./foo and check for licenses, sbom, or archives. +// Results are composed of a case its evaluations. The case is the total of SBOM/Licenses generated from the user request. +// The evaluations are the individual assessments of the policy against the packages/licenses in the case. type Report struct { - ReportID string - Requests []Request `json:"results" yaml:"results"` + ReportID string + Results []evalutation.Result `json:"results" yaml:"results"` + Format Format `json:"format" yaml:"format"` + Timestamp string `json:"timestamp" yaml:"timestamp"` + errors []error +} - // Evaluation is a pass/fail for the entire report; - // It rolls up violations from all the requests - Evaluation Evaluation `json:"evaluation" yaml:"evaluation"` - Format Format `json:"format" yaml:"format"` - Timestamp string `json:"timestamp" yaml:"timestamp"` - errors []error +type Config struct { + Policy *grant.Policy } // 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 -// Results will be generated and evaluated for each user request that is successfully processed -func NewReport(f Format, p grant.Policy, userRequests ...string) *Report { - if p.IsEmpty() { - p = grant.DefaultPolicy() +// If a request is provided, but the sbom cannot be generated, the source will be ignored and an error will be returned +func NewReport(f Format, cc Config, userRequests ...string) (*Report, error) { + if cc.Policy.IsEmpty() { + cc.Policy = grant.DefaultPolicy() } - format := validateFormat(f) - requests := make([]Request, 0) - errs := make([]error, 0) - for _, r := range userRequests { - request, err := NewRequest(r, p) - if err != nil { - errs = append(errs, err) - continue - } - requests = append(requests, request) + format := validateFormat(f) + cases := grant.NewCases(cc.Policy, userRequests...) + ec := evalutation.EvaluationConfig{ + Policy: cc.Policy, + CheckNonSPDX: true, } + results := evalutation.NewResults(ec, cases...) + return &Report{ - Requests: requests, + Results: results, Format: format, Timestamp: time.Now().Format(time.RFC3339), - errors: errs, - } + }, nil } // Render will call Render on each result in the report and return the report @@ -85,11 +65,11 @@ func (r *Report) Render(out io.Writer) error { } func (r *Report) renderJSON(out io.Writer) error { - return json.NewEncoder(out).Encode(r) + return errors.New("not implemented") } func (r *Report) renderTable(out io.Writer) error { - return nil + return errors.New("not implemented") } // l := list.NewWriter() // TODO: style me diff --git a/cmd/grant/cli/option/check.go b/cmd/grant/cli/option/check.go index ac65e40..3cfa0b7 100644 --- a/cmd/grant/cli/option/check.go +++ b/cmd/grant/cli/option/check.go @@ -5,8 +5,9 @@ import ( ) type Check struct { - AllowLicenses []string `json:"allow-licenses" yaml:"allow-licenses" mapstructure:"allow-licenses"` - DenyLicenses []string `json:"deny-licenses" yaml:"deny-licenses" mapstructure:"deny-licenses"` + AllowLicenses []string `json:"allow-licenses" yaml:"allow-licenses" mapstructure:"allow-licenses"` + DenyLicenses []string `json:"deny-licenses" yaml:"deny-licenses" mapstructure:"deny-licenses"` + IgnoreLicenses []string `json:"ignore-licenses" yaml:"ignore-licenses" mapstructure:"ignore-licenses"` } func DefaultCheck() Check { @@ -17,5 +18,5 @@ func DefaultCheck() Check { } func (c Check) ToPolicy() (*grant.Policy, error) { - return grant.NewPolicy(c.AllowLicenses, c.DenyLicenses) + return grant.NewPolicy(c.AllowLicenses, c.DenyLicenses, c.IgnoreLicenses) } diff --git a/grant/report/input.go b/grant/case.go similarity index 53% rename from grant/report/input.go rename to grant/case.go index 7bf13f9..d5eaa9a 100644 --- a/grant/report/input.go +++ b/grant/case.go @@ -1,4 +1,4 @@ -package report +package grant import ( "context" @@ -11,8 +11,8 @@ import ( "strings" "github.com/google/licenseclassifier/v2/tools/identify_license/backend" + "github.com/google/licenseclassifier/v2/tools/identify_license/results" - "github.com/anchore/grant/grant" "github.com/anchore/grant/internal" "github.com/anchore/grant/internal/log" "github.com/anchore/syft/syft" @@ -22,20 +22,75 @@ import ( "github.com/anchore/syft/syft/source" ) -func handleFile(path string) (r *requestBreakdown, err error) { +// 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 + + // Licenses is a list of licenses that are checked against the policy + Licenses []License + + // 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 { + cases := make([]Case, 0) + for _, userInput := range userInputs { + c, err := determineRequestCase(userInput) + if err != nil { + log.Errorf("unable to determine case for %s: %+v", userInput, err) + continue + } + c.Policy = p + c.UserInput = userInput + cases = append(cases, c) + } + return cases +} + +// A valid userRequest can be: +// - a path to an SBOM file +// - a path to a license +// - a path to a directory +// - a path to an archive +// - a path to a directory (with any of the above) +// - a container image (ubuntu:latest) +func determineRequestCase(userRequest string) (c Case, err error) { + switch { + case isFile(userRequest): + return handleFile(userRequest) + case isDirectory(userRequest): + return handleDir(userRequest) + default: + return handleContainer(userRequest) + } + + // alright you got us here, we don't know what to do with this + return c, fmt.Errorf("unable to determine SBOM or licenses for %s", userRequest) +} + +// TODO: probably need to return a multi error here +func handleFile(path string) (c Case, err error) { // let's see if it's an archive (isArchive) if isArchive(path) { sb, err := generateSyftSBOM(path) if err != nil { // We bail here since we can't generate an SBOM for the archive - return nil, err + return c, err } // if there are licenses in the archive, syft should be enhanced to include them in the SBOM // this overlap is a little weird, but grant should be able to take license files as input - return &requestBreakdown{ - sboms: []sbom.SBOM{sb}, - licenses: make([]grant.License, 0), + return Case{ + SBOMS: []sbom.SBOM{sb}, + Licenses: make([]License, 0), }, nil } @@ -43,22 +98,22 @@ func handleFile(path string) (r *requestBreakdown, err error) { bytes, err := getReadSeeker(path) if err != nil { // We bail here since we can't get a reader for the file - return nil, err + return c, err } sb, _, _, err := format.NewDecoderCollection(format.Decoders()...).Decode(bytes) if sb != nil { - return &requestBreakdown{ - sboms: []sbom.SBOM{*sb}, - licenses: make([]grant.License, 0), + return Case{ + SBOMS: []sbom.SBOM{*sb}, + Licenses: make([]License, 0), }, nil } - // TODO: some log for the error here? // 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 be, err := backend.New() if err != nil { - return nil, err + return c, err } defer be.Close() @@ -74,31 +129,31 @@ func handleFile(path string) (r *requestBreakdown, err error) { for _, err := range errs { log.Errorf("unable to classify license: %+v", err) } - return nil, fmt.Errorf("unable to classify license: %+v", err) + return c, fmt.Errorf("unable to classify license: %+v", err) } // re-enable logging for the rest of the application golog.SetOutput(os.Stdout) results := be.GetResults() if len(results) == 0 { - return nil, fmt.Errorf("unable to determine SBOM or licenses for %s", path) + return c, fmt.Errorf("unable to determine SBOM or licenses for %s", path) } licenses := grantLicenseFromClassifierResults(results) - return &requestBreakdown{ - sboms: make([]sbom.SBOM, 0), - licenses: licenses, + return Case{ + SBOMS: make([]sbom.SBOM, 0), + Licenses: licenses, }, nil } -func handleDir(root string) (r *requestBreakdown, err error) { - totalBreakdown := &requestBreakdown{ - sboms: make([]sbom.SBOM, 0), - licenses: make([]grant.License, 0), +func handleDir(root string) (c Case, err error) { + dirCase := Case{ + SBOMS: make([]sbom.SBOM, 0), + Licenses: make([]License, 0), } - // Define the closure that will be used as the visit function + // the closure that will be used to visit each file node visit := func(s string, d fs.DirEntry, err error) error { if err != nil { return err @@ -110,32 +165,54 @@ func handleDir(root string) (r *requestBreakdown, err error) { // TODO: some log for the error here? return nil } - totalBreakdown.sboms = append(totalBreakdown.sboms, r.sboms...) - totalBreakdown.licenses = append(totalBreakdown.licenses, r.licenses...) + dirCase.SBOMS = append(dirCase.SBOMS, r.SBOMS...) + dirCase.Licenses = append(dirCase.Licenses, r.Licenses...) } return nil } err = filepath.WalkDir(root, visit) if err != nil { - return nil, err + return c, err } - return totalBreakdown, nil + return dirCase, nil } -func handleContainer(image string) (r *requestBreakdown, err error) { +func handleContainer(image string) (c Case, err error) { sb, err := generateSyftSBOM(image) if err != nil { // We bail here since we can't generate an SBOM for the image - return nil, err + return c, err } - return &requestBreakdown{ - sboms: []sbom.SBOM{sb}, - licenses: make([]grant.License, 0), + return Case{ + SBOMS: []sbom.SBOM{sb}, + Licenses: make([]License, 0), }, nil } +func getReadSeeker(path string) (io.ReadSeeker, error) { + file, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("unable to open file: %w", err) + } + return file, nil +} + +func grantLicenseFromClassifierResults(r results.LicenseTypes) []License { + licenses := make([]License, 0) + for _, license := range r { + // TODO: sometimes the license classifier gives us more information than just the name. + // How do we want to handle this or include it in the grant.License? + if license.MatchType == "License" { + licenses = append(licenses, License{ + Name: license.Name, + }) + } + } + return licenses +} + // TODO: is the default syft config good enough here? // we definitely need at least all the non default license magic turned on func generateSyftSBOM(path string) (sb sbom.SBOM, err error) { diff --git a/grant/evalutation/license_evalutation.go b/grant/evalutation/license_evalutation.go new file mode 100644 index 0000000..e817a54 --- /dev/null +++ b/grant/evalutation/license_evalutation.go @@ -0,0 +1,47 @@ +package evalutation + +import ( + "github.com/anchore/grant/grant" +) + +func NewLicenseEvaluations(ec EvaluationConfig, c grant.Case) LicenseEvaluations { + panic("not implemented") +} + +type LicenseEvaluations []LicenseEvaluation + +type LicenseEvaluation struct { + RequestID string + + // inputs into evaluation... + License grant.License // the license that we evaluated + Package *grant.Package // any artifact license is evaluated with + + // what's used to evaluate... + Policy *grant.Policy // what the determination was made against + + // the output of an evaluation... + Reason []string // reasons that the evaluation value the way it is + 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") +} diff --git a/grant/evalutation/license_evalutation_config.go b/grant/evalutation/license_evalutation_config.go new file mode 100644 index 0000000..72700f0 --- /dev/null +++ b/grant/evalutation/license_evalutation_config.go @@ -0,0 +1,11 @@ +package evalutation + +import "github.com/anchore/grant/grant" + +type EvaluationConfig struct { + // Policy is the policy to evaluate against + // if non is supplied, the default policy is used (grant.DefaultPolicy()) + Policy *grant.Policy + // CheckNonSPDX is true if non-SPDX licenses should be checked + CheckNonSPDX bool +} diff --git a/grant/evalutation/result.go b/grant/evalutation/result.go new file mode 100644 index 0000000..c346163 --- /dev/null +++ b/grant/evalutation/result.go @@ -0,0 +1,35 @@ +package evalutation + +import ( + "github.com/anchore/grant/grant" +) + +// Result is the result of a policy evaluation +// It combines the supplied case with the evaluation results +type Result struct { + Case grant.Case + Evaluations LicenseEvaluations + Pass bool +} + +type Results []Result + +func NewResults(ec EvaluationConfig, cases ...grant.Case) (r Results) { + r = make(Results, 0) + for _, c := range cases { + e := NewLicenseEvaluations(ec, c) + res := Result{ + Case: c, + Evaluations: e, + Pass: !e.IsFailed(), + } + r = append(r, res) + } + return r +} + +// Pass T/F + reasons for failure +// Validate() error ([]string reasons) +func (rs Results) Pass() bool { + panic("not implemented") +} diff --git a/grant/evalutation/sbom.go b/grant/evalutation/sbom.go new file mode 100644 index 0000000..855d95a --- /dev/null +++ b/grant/evalutation/sbom.go @@ -0,0 +1,113 @@ +package evalutation + +import ( + "github.com/anchore/grant/grant" + "github.com/anchore/syft/syft/sbom" +) + +func newSbomLicenseEvalIndex(ec EvaluationConfig, s sbom.SBOM) *sbomLicenseEvalIndex { + index := evalIndex(ec.Policy) + for pkg := range s.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 { + // no licenses found for this package + index.addCompliant(grantPkg, nil) + } + for _, license := range grantPkg.Licenses { + // TODO: check if the license is in the config ignore list + // TODO: check if the config wants us to check for non-SPDX licenses + if !license.IsSPDX() { + index.addIgnored(grantPkg, license) + continue + } + + if ec.Policy.IsDenied(license) { + index.addViolation(grantPkg, license) + continue + } + // otherwise, the license is allowed + index.addCompliant(grantPkg, &license) + } + } + + return index +} + +func evalIndex(p grant.Policy) *sbomLicenseEvalIndex { + return &sbomLicenseEvalIndex{ + packages: make(map[grant.PackageID]grant.Package), + packageViolations: make(map[grant.PackageID][]grant.License), + compliantPackages: make(map[grant.PackageID][]grant.License), + ignoredPackages: make(map[grant.PackageID][]grant.License), + + licenses: make(map[grant.LicenseID]grant.License), + licenseViolations: make(map[grant.LicenseID][]grant.Package), + compliantLicenses: make(map[grant.LicenseID][]grant.Package), + ignoredLicenses: make(map[grant.LicenseID][]grant.Package), + policy: p, + } +} + +// sboms map packages and licenses together +// sbomLicenseEvalIndex is a more complex Evaluation that tracks packages and licenses +type sbomLicenseEvalIndex struct { + licenses map[grant.LicenseID]grant.License + + // licenseViolations not allowed by the policy and the packages that contained them + // The key for these is the all lowercase SPDX license ID found in internal/spdxlicense/license.go + licenseViolations map[grant.LicenseID][]grant.Package + // compliantLicenses that were allowed by the policy and the packages that contained them + compliantLicenses map[grant.LicenseID][]grant.Package + // ignoredLicenses that were not SPDX compliant and the packages that contained them + ignoredLicenses map[grant.LicenseID][]grant.Package + + packages map[grant.PackageID]grant.Package + + // packageViolations and their licenses that violated the policy + packageViolations map[grant.PackageID][]grant.License + // compliantPackages and their licenses that were compliant to the policy + compliantPackages map[grant.PackageID][]grant.License + // ignoredPackages tracks packages with licenses that were not SPDX compliant + ignoredPackages map[grant.PackageID][]grant.License + + // policy is the policy used to generate this evaluation + policy grant.Policy +} + +func (i *sbomLicenseEvalIndex) isFailed() bool { + return len(i.licenseViolations) > 0 +} + +func (i *sbomLicenseEvalIndex) addViolation(grantPkg grant.Package, license grant.License) { + if _, ok := i.packages[grantPkg.ID]; !ok { + i.packages[grantPkg.ID] = grantPkg + } + if _, ok := i.licenses[license.ID]; !ok { + i.licenses[license.ID] = license + } + + i.packageViolations[grantPkg.ID] = append(i.packageViolations[grantPkg.ID], license) + i.licenseViolations[license.ID] = append(i.licenseViolations[license.ID], grantPkg) +} + +func (i *sbomLicenseEvalIndex) addCompliant(grantPkg grant.Package, license *grant.License) { + i.packages[grantPkg.ID] = grantPkg + if license != nil { + i.licenses[license.ID] = *license + i.compliantPackages[grantPkg.ID] = append(i.compliantPackages[grantPkg.ID], *license) + i.compliantLicenses[license.ID] = append(i.compliantLicenses[license.ID], grantPkg) + return + } + + // no license was provided, so we'll just add the package + if _, ok := i.compliantPackages[grantPkg.ID]; !ok { + i.compliantPackages[grantPkg.ID] = make([]grant.License, 0) + } +} + +// Note: if a license has been ignored, it means the SPDX expression was invalid +// or the user config specified to ignore the specific license +func (i *sbomLicenseEvalIndex) addIgnored(grantPkg grant.Package, license grant.License) { + return +} diff --git a/grant/evalutation/syft.go b/grant/evalutation/syft.go new file mode 100644 index 0000000..d7a3a80 --- /dev/null +++ b/grant/evalutation/syft.go @@ -0,0 +1,108 @@ +package evalutation + +import ( + "github.com/github/go-spdx/v2/spdxexp" + + "github.com/anchore/grant/grant" + "github.com/anchore/grant/internal/log" + "github.com/anchore/grant/internal/spdxlicense" + syftPkg "github.com/anchore/syft/syft/pkg" +) + +func convertSyftPackage(p syftPkg.Package) grant.Package { + locations := p.Locations.ToSlice() + packageLocations := make([]string, 0) + for _, location := range locations { + packageLocations = append(packageLocations, location.RealPath) + } + + return grant.Package{ + Name: p.Name, + Version: p.Version, + Licenses: convertSyftLicenses(p.Licenses), + Locations: packageLocations, + } +} + +// convertSyftLicenses converts a syft LicenseSet to a grant License slice +// note: syft licenses can sometimes have complex SPDX expressions. +// Grant licenses break down these expressions into individual licenses. +// Because license expressions could potentially contain multiple licenses +// that are already represented in the syft license set we need to de-duplicate +// syft licenses have a "Value" field which is the name of the license +// given to an invalid SPDX expression; grant licenses store this field as "Name" +func convertSyftLicenses(set syftPkg.LicenseSet) (licenses []grant.License) { + licenses = make([]grant.License, 0) + checked := make(map[string]bool) + for _, license := range set.ToSlice() { + locations := license.Locations.ToSlice() + licenseLocations := make([]string, 0) + for _, location := range locations { + licenseLocations = append(licenseLocations, location.RealPath) + } + + if license.SPDXExpression != "" { + licenses = handleSPDXLicense(license, licenses, licenseLocations, checked) + continue + } + + licenses = addNonSPDXLicense(licenses, license, licenseLocations) + } + return licenses +} + +func handleSPDXLicense(license syftPkg.License, licenses []grant.License, licenseLocations []string, checked map[string]bool) []grant.License { + extractedLicenses, err := spdxexp.ExtractLicenses(license.SPDXExpression) + if err != nil { + log.Errorf("unable to extract licenses from SPDX expression: %s", license.SPDXExpression) + return addNonSPDXLicense(licenses, license, licenseLocations) + } + + // process each extracted license from the SPDX expression + for _, extractedLicense := range extractedLicenses { + // prevent duplicates from being added when using SPDX expressions + // EG: "MIT AND MIT" is valid, but we want to de-duplicate these + if check(checked, extractedLicense) { + continue + } + + // we have what seems to be a valid SPDX license ID, let's try and get more info about it + spdxLicense, err := spdxlicense.GetLicenseByID(extractedLicense) + if err != nil { + log.Errorf("unable to get license by ID: %s; no matching spdx id found", extractedLicense) + // if we can't find a matching SPDX license, just add the license as-is + // TODO: best matching against the spdx list index + addNonSPDXLicense(licenses, license, licenseLocations) + continue + } + + licenses = append(licenses, grant.License{ + SPDXExpression: extractedLicense, + Name: spdxLicense.Name, + Locations: licenseLocations, + Reference: spdxLicense.Reference, + IsDeprecatedLicenseID: spdxLicense.IsDeprecatedLicenseID, + DetailsURL: spdxLicense.DetailsURL, + ReferenceNumber: spdxLicense.ReferenceNumber, + LicenseID: spdxLicense.LicenseID, + SeeAlso: spdxLicense.SeeAlso, + IsOsiApproved: spdxLicense.IsOsiApproved, + }) + } + return licenses +} + +func addNonSPDXLicense(licenses []grant.License, license syftPkg.License, locations []string) []grant.License { + return append(licenses, grant.License{ + Name: license.Value, + Locations: locations, + }) +} + +func check(checked map[string]bool, license string) bool { + if _, ok := checked[license]; !ok { + checked[license] = true + return false + } + return true +} diff --git a/grant/policy.go b/grant/policy.go index 5f5ee5b..67c5f86 100644 --- a/grant/policy.go +++ b/grant/policy.go @@ -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("*"), @@ -29,7 +29,7 @@ func DefaultPolicy() Policy { // NewPolicy builds a policy from lists of allow and deny glob patterns // It lower cases all patterns to make matching against the spdx license set case-insensitive -func NewPolicy(allowLicenses, denyLicenses, ignoreLicenses []string) (Policy, error) { +func NewPolicy(allowLicenses, denyLicenses, ignoreLicenses []string) (p *Policy, err error) { if len(allowLicenses) == 0 && len(denyLicenses) == 0 { return DefaultPolicy(), nil } @@ -44,7 +44,7 @@ func NewPolicy(allowLicenses, denyLicenses, ignoreLicenses []string) (Policy, er deny = strings.ToLower(deny) denyGlob, err := glob.Compile(deny) if err != nil { - return Policy{}, err + return p, err } denyGlobs = append(denyGlobs, denyGlob) if deny == "*" { @@ -57,7 +57,7 @@ func NewPolicy(allowLicenses, denyLicenses, ignoreLicenses []string) (Policy, er allow = strings.ToLower(allow) allowGlob, err := glob.Compile(allow) if err != nil { - return Policy{}, err + return p, err } allowGlobs = append(allowGlobs, allowGlob) if allow == "*" { @@ -70,12 +70,12 @@ func NewPolicy(allowLicenses, denyLicenses, ignoreLicenses []string) (Policy, er ignore = strings.ToLower(ignore) ignoreGlob, err := glob.Compile(ignore) if err != nil { - return Policy{}, err + return p, err } ignoreGlobs = append(ignoreGlobs, ignoreGlob) } - return Policy{ + return &Policy{ AllowLicenses: allowGlobs, DenyLicenses: denyGlobs, IgnoreLicenses: ignoreGlobs, diff --git a/grant/report/eval_license.go b/grant/report/eval_license.go deleted file mode 100644 index 56777d9..0000000 --- a/grant/report/eval_license.go +++ /dev/null @@ -1,39 +0,0 @@ -package report - -import ( - "github.com/anchore/grant/grant" -) - -type licenseEval struct { - // license is the license that was evaluated - license grant.License - // policy is the policy used to evaluate the license - policy grant.Policy -} - -func (e *licenseEval) IsFailed() bool { - return true -} - -func (e *licenseEval) GetPackages() []grant.Package { - return []grant.Package{} -} - -func (e *licenseEval) GetLicenses() []grant.License { - return []grant.License{e.license} -} - -func (e *licenseEval) GetViolations() []Violation { - return []Violation{} -} - -func (e *licenseEval) GetPolicy() grant.Policy { - return e.policy -} - -func evalFromLicense(ec EvaluationConfig, l grant.License) Evaluation { - return &licenseEval{ - license: l, - policy: ec.Policy, - } -} diff --git a/grant/report/eval_sbom.go b/grant/report/eval_sbom.go deleted file mode 100644 index ded2699..0000000 --- a/grant/report/eval_sbom.go +++ /dev/null @@ -1,250 +0,0 @@ -package report - -import ( - "github.com/github/go-spdx/v2/spdxexp" - - "github.com/anchore/grant/grant" - "github.com/anchore/grant/internal/log" - "github.com/anchore/grant/internal/spdxlicense" - syftPkg "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/sbom" -) - -// sboms map packages and licenses together -// evalIndex is a more complex Evaluation that tracks packages and licenses -type evalIndex struct { - packages map[grant.PackageID]grant.Package - - // packageViolations and their licenses that violated the policy - packageViolations map[grant.PackageID][]grant.License - // compliantPackages and their licenses that were compliant to the policy - compliantPackages map[grant.PackageID][]grant.License - // ignoredPackages tracks packages with licenses that were not SPDX compliant - ignoredPackages map[grant.PackageID][]grant.License - - licenses map[grant.LicenseID]grant.License - - // licenseViolations not allowed by the policy and the packages that contained them - // The key for these is the all lowercase SPDX license ID found in internal/spdxlicense/license.go - licenseViolations map[grant.LicenseID][]grant.Package - // compliantLicenses that were allowed by the policy and the packages that contained them - compliantLicenses map[grant.LicenseID][]grant.Package - // ignoredLicenses that were not SPDX compliant and the packages that contained them - ignoredLicenses map[grant.LicenseID][]grant.Package - - // policy is the policy used to generate this evaluation - policy grant.Policy -} - -func (i *evalIndex) GetPackages() []grant.Package { - packages := make([]grant.Package, 0) - for _, pkg := range i.packages { - packages = append(packages, pkg) - } - return packages -} - -func (i *evalIndex) GetLicenses() []grant.License { - licenses := make([]grant.License, 0) - for _, license := range i.licenses { - licenses = append(licenses, license) - } - return licenses -} - -// GetViolations is oriented by licenseID -func (i *evalIndex) GetViolations() []Violation { - violations := make([]Violation, 0) - for licenseID, packages := range i.licenseViolations { - license := i.licenses[licenseID] - for _, pkg := range packages { - violations = append(violations, Violation{ - License: license, - Package: pkg, - }) - } - } - return violations -} - -func (i *evalIndex) GetPolicy() grant.Policy { - return i.policy -} - -func (i *evalIndex) IsFailed() bool { - return len(i.licenseViolations) > 0 -} - -func newEvalIndex(p grant.Policy) *evalIndex { - return &evalIndex{ - packages: make(map[grant.PackageID]grant.Package), - packageViolations: make(map[grant.PackageID][]grant.License), - compliantPackages: make(map[grant.PackageID][]grant.License), - ignoredPackages: make(map[grant.PackageID][]grant.License), - - licenses: make(map[grant.LicenseID]grant.License), - licenseViolations: make(map[grant.LicenseID][]grant.Package), - compliantLicenses: make(map[grant.LicenseID][]grant.Package), - ignoredLicenses: make(map[grant.LicenseID][]grant.Package), - policy: p, - } -} - -func (i *evalIndex) addViolation(grantPkg grant.Package, license grant.License) { - if _, ok := i.packages[grantPkg.ID]; !ok { - i.packages[grantPkg.ID] = grantPkg - } - if _, ok := i.licenses[license.ID]; !ok { - i.licenses[license.ID] = license - } - - i.packageViolations[grantPkg.ID] = append(i.packageViolations[grantPkg.ID], license) - i.licenseViolations[license.ID] = append(i.licenseViolations[license.ID], grantPkg) -} - -func (i *evalIndex) addCompliant(grantPkg grant.Package, license *grant.License) { - i.packages[grantPkg.ID] = grantPkg - if license != nil { - i.licenses[license.ID] = *license - i.compliantPackages[grantPkg.ID] = append(i.compliantPackages[grantPkg.ID], *license) - i.compliantLicenses[license.ID] = append(i.compliantLicenses[license.ID], grantPkg) - return - } - - // no license was provided, so we'll just add the package - if _, ok := i.compliantPackages[grantPkg.ID]; !ok { - i.compliantPackages[grantPkg.ID] = make([]grant.License, 0) - } -} - -// Note: if a license has been ignored, it means the SPDX expression was invalid -// or the user config specified to ignore the specific license -func (i *evalIndex) addIgnored(grantPkg grant.Package, license grant.License) { -} - -func evalFromSBOM(ec EvaluationConfig, s sbom.SBOM) *evalIndex { - index := newEvalIndex(ec.Policy) - for pkg := range s.Artifacts.Packages.Enumerate() { - // since we use syft to generate the sbom we need to convert packages/licenses to grant types - grantPkg := convertSyftPackage(pkg) - if len(grantPkg.Licenses) == 0 { - // no licenses found for this package - index.addCompliant(grantPkg, nil) - } - for _, license := range grantPkg.Licenses { - // TODO: check if the license is in the config ignore list - // TODO: check if the config wants us to check for non-SPDX licenses - if !license.IsSPDX() { - index.addIgnored(grantPkg, license) - continue - } - - if ec.Policy.IsDenied(license) { - index.addViolation(grantPkg, license) - continue - } - // otherwise, the license is allowed - index.addCompliant(grantPkg, &license) - } - } - - return index -} - -func convertSyftPackage(p syftPkg.Package) grant.Package { - locations := p.Locations.ToSlice() - packageLocations := make([]string, 0) - for _, location := range locations { - packageLocations = append(packageLocations, location.RealPath) - } - - return grant.Package{ - Name: p.Name, - Version: p.Version, - Licenses: convertSyftLicenses(p.Licenses), - Locations: packageLocations, - } -} - -// convertSyftLicenses converts a syft LicenseSet to a grant License slice -// note: syft licenses can sometimes have complex SPDX expressions. -// Grant licenses break down these expressions into individual licenses. -// Because license expressions could potentially contain multiple licenses -// that are already represented in the syft license set we need to de-duplicate -// syft licenses have a "Value" field which is the name of the license -// given to an invalid SPDX expression; grant licenses store this field as "Name" -func convertSyftLicenses(set syftPkg.LicenseSet) (licenses []grant.License) { - licenses = make([]grant.License, 0) - checked := make(map[string]bool) - for _, license := range set.ToSlice() { - locations := license.Locations.ToSlice() - licenseLocations := make([]string, 0) - for _, location := range locations { - licenseLocations = append(licenseLocations, location.RealPath) - } - - if license.SPDXExpression != "" { - licenses = handleSPDXLicense(license, licenses, licenseLocations, checked) - continue - } - - licenses = addNonSPDXLicense(licenses, license, licenseLocations) - } - return licenses -} - -func handleSPDXLicense(license syftPkg.License, licenses []grant.License, licenseLocations []string, checked map[string]bool) []grant.License { - extractedLicenses, err := spdxexp.ExtractLicenses(license.SPDXExpression) - if err != nil { - log.Errorf("unable to extract licenses from SPDX expression: %s", license.SPDXExpression) - return addNonSPDXLicense(licenses, license, licenseLocations) - } - - // process each extracted license from the SPDX expression - for _, extractedLicense := range extractedLicenses { - // prevent duplicates from being added when using SPDX expressions - // EG: "MIT AND MIT" is valid, but we want to de-duplicate these - if check(checked, extractedLicense) { - continue - } - - // we have what seems to be a valid SPDX license ID, let's try and get more info about it - spdxLicense, err := spdxlicense.GetLicenseByID(extractedLicense) - if err != nil { - log.Errorf("unable to get license by ID: %s; no matching spdx id found", extractedLicense) - // if we can't find a matching SPDX license, just add the license as-is - // TODO: best matching against the spdx list index - addNonSPDXLicense(licenses, license, licenseLocations) - continue - } - - licenses = append(licenses, grant.License{ - SPDXExpression: extractedLicense, - Name: spdxLicense.Name, - Locations: licenseLocations, - Reference: spdxLicense.Reference, - IsDeprecatedLicenseID: spdxLicense.IsDeprecatedLicenseID, - DetailsURL: spdxLicense.DetailsURL, - ReferenceNumber: spdxLicense.ReferenceNumber, - LicenseID: spdxLicense.LicenseID, - SeeAlso: spdxLicense.SeeAlso, - IsOsiApproved: spdxLicense.IsOsiApproved, - }) - } - return licenses -} - -func addNonSPDXLicense(licenses []grant.License, license syftPkg.License, locations []string) []grant.License { - return append(licenses, grant.License{ - Name: license.Value, - Locations: locations, - }) -} - -func check(checked map[string]bool, license string) bool { - if _, ok := checked[license]; !ok { - checked[license] = true - return false - } - return true -} diff --git a/grant/report/evaluation.go b/grant/report/evaluation.go deleted file mode 100644 index 5c5d106..0000000 --- a/grant/report/evaluation.go +++ /dev/null @@ -1,40 +0,0 @@ -package report - -import ( - "github.com/anchore/grant/grant" - "github.com/anchore/syft/syft/sbom" -) - -// Evaluation is the result of a policy evaluation -// Grant can evaluate either an SBOM(generated on demand) or an individual license -type Evaluation interface { - GetPackages() []grant.Package - GetLicenses() []grant.License - GetViolations() []Violation - GetPolicy() grant.Policy - IsFailed() bool -} - -type EvaluationConfig struct { - // Policy is the policy to evaluate against - Policy grant.Policy - // CheckNonSPDX is true if non-SPDX licenses should be checked - CheckNonSPDX bool -} - -// Violation is a single license violation for a given evaluation -// Package is optional as not all discovered licenses are associated with a package -type Violation struct { - RequestID string - License grant.License - Package grant.Package - Reason string -} - -func NewEvaluationFromSBOM(ec EvaluationConfig, s sbom.SBOM) Evaluation { - return evalFromSBOM(ec, s) -} - -func NewEvaluationFromLicense(ec EvaluationConfig, l grant.License) Evaluation { - return evalFromLicense(ec, l) -} diff --git a/grant/report/evaluation_test.go b/grant/report/evaluation_test.go deleted file mode 100644 index 80c499f..0000000 --- a/grant/report/evaluation_test.go +++ /dev/null @@ -1 +0,0 @@ -package report diff --git a/grant/report/report_test.go b/grant/report/report_test.go deleted file mode 100644 index ecd7c5f..0000000 --- a/grant/report/report_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package report - -import ( - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - - "github.com/anchore/grant/grant" -) - -func Test_NewReport(t *testing.T) { - tests := []struct { - name string - srcs []string - policy grant.Policy - format Format - want *Report - wantErr bool - compareOptions []cmp.Option - }{ - { - name: "grant report constructor builds a new report, with the default policy, for a single source", - srcs: []string{"fixtures/test.spdx.json"}, - policy: grant.DefaultPolicy(), - format: Table, - want: &Report{}, - wantErr: false, - compareOptions: []cmp.Option{ - cmpopts.IgnoreFields(grant.Policy{}, "denyAll", "allowAll"), - cmpopts.IgnoreFields(Report{}, "Timestamp", "errors"), - }, - }, - { - name: "grant report constructor builds a new report, with the default policy, for multiple sources", - srcs: []string{"fixtures/test.spdx.json", "fixtures/alpine.spdx"}, - policy: grant.DefaultPolicy(), - format: Table, - want: &Report{}, - wantErr: false, - compareOptions: []cmp.Option{ - cmpopts.IgnoreFields(grant.Policy{}, "denyAll", "allowAll"), - cmpopts.IgnoreFields(Report{}, "Timestamp", "errors"), - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := NewReport(tt.format, tt.policy, tt.srcs...) - if got == nil { - t.Errorf("NewReport() = %v, want %v", got, tt.want) - } - if diff := cmp.Diff(tt.want, got, tt.compareOptions...); diff != "" { - t.Errorf("NewReport() mismatch (-want +got):\n%s", diff) - } - if len(got.errors) > 0 && !tt.wantErr { - t.Errorf("NewReport() errors = %v, want %v", got.errors, nil) - } - }) - } -} diff --git a/grant/report/request.go b/grant/report/request.go deleted file mode 100644 index 42bf42e..0000000 --- a/grant/report/request.go +++ /dev/null @@ -1,118 +0,0 @@ -package report - -import ( - "fmt" - "io" - "os" - - "github.com/google/licenseclassifier/v2/tools/identify_license/results" - - "github.com/anchore/grant/grant" - "github.com/anchore/grant/internal/log" - "github.com/anchore/syft/syft/sbom" -) - -type RequestID string - -type Request struct { - RequestID RequestID - - // UserInput is the user input that was broken down into different Evaluations - // can be something like ./; so it can contain multiple Evaluations - // Consider: ./foo.spdx ./MIT ./image.tar.gz - // The UserInput in the above case would be "./" and the Evaluations would be: - // - ./foo.spdx SBOM - // - ./MIT LICENSE - // - ./image.tar.gz Generated SBOM - UserInput string - - // Evaluation is a pass/fail for the entire request; - Evaluations []Evaluation -} - -// NewRequest will generate a new request for the given userInput -// The policy is applied to each determined Evaluation -// A valid userRequest can be: -// - a path to an SBOM file -// - a path to a license -// - a path to an archive -// - a path to a directory (with any of the above) -// - or some container image -func NewRequest(userInput string, p grant.Policy) (r Request, err error) { - evaluations := make([]Evaluation, 0) - - // TODO: we need to inject the user config here and convert it into a evaluation config - ec := EvaluationConfig{Policy: p} - - requestBreakdown, err := determineRequest(userInput) - if err != nil { - log.Errorf("unable to determine SBOM or licenses for %s: %+v", userInput, err) - return r, err - } - - // results are broken down into SBOMs (pkg -> license) - // or raw licenses that were detected with no package association - for _, sb := range requestBreakdown.sboms { - evaluations = append(evaluations, NewEvaluationFromSBOM(ec, sb)) - } - - for _, license := range requestBreakdown.licenses { - evaluations = append(evaluations, NewEvaluationFromLicense(ec, license)) - } - - // TODO: generate stable request ID - return Request{ - RequestID: "", - UserInput: userInput, - Evaluations: evaluations, - }, nil -} - -// easy way to break down a user request into SBOMs (generated or passed) and discovered licenses -type requestBreakdown struct { - sboms []sbom.SBOM // All SBOMs found for the user request - licenses []grant.License // All licenses found for the user request (can be a directory of licenses or if the user request is a license) -} - -// A valid userRequest can be: -// - a path to an SBOM file -// - a path to a license -// - a path to a directory -// - a path to an archive -// - a path to a directory (with any of the above) -// - a container image (ubuntu:latest) -func determineRequest(userRequest string) (r *requestBreakdown, err error) { - switch { - case isFile(userRequest): - return handleFile(userRequest) - case isDirectory(userRequest): - return handleDir(userRequest) - default: - return handleContainer(userRequest) - } - - // alright you got us here, we don't know what to do with this - return nil, fmt.Errorf("unable to determine SBOM or licenses for %s", userRequest) -} - -func getReadSeeker(path string) (io.ReadSeeker, error) { - file, err := os.Open(path) - if err != nil { - return nil, fmt.Errorf("unable to open file: %w", err) - } - return file, nil -} - -func grantLicenseFromClassifierResults(r results.LicenseTypes) []grant.License { - licenses := make([]grant.License, 0) - for _, license := range r { - // TODO: sometimes the license classifier gives us more information than just the name. - // How do we want to handle this or include it in the grant.License? - if license.MatchType == "License" { - licenses = append(licenses, grant.License{ - Name: license.Name, - }) - } - } - return licenses -} diff --git a/grant/report/request_test.go b/grant/report/request_test.go deleted file mode 100644 index 3e1413f..0000000 --- a/grant/report/request_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package report - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_determineRequest(t *testing.T) { - tests := []struct { - name string - userRequest string - expectedSBOM int - expectedLicense int - }{ - { - name: "grant can determine a request for a single SBOM file", - userRequest: "../../fixtures/multiple/alpine.spdx.json", - expectedSBOM: 1, - expectedLicense: 0, - }, - { - name: "grant can determine a request for a single license file", - userRequest: "../../fixtures/licenses/MIT", - expectedSBOM: 0, - expectedLicense: 1, - }, - { - name: "grant can determine a request for a directory (multiple evaluation results)", - userRequest: "../../fixtures/multiple", - expectedSBOM: 1, - expectedLicense: 1, - }, - { - name: "grant can determine a request for an archive (single sbom)", - userRequest: "../../fixtures/archive-builds/packages/example-java-app-maven-0.1.0.zip", - expectedSBOM: 1, - expectedLicense: 0, - }, - { - name: "grant can determine a request for a container image (single sbom)", - userRequest: "alpine:latest", - expectedSBOM: 1, - expectedLicense: 0, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - requestBreakdown, err := determineRequest(tt.userRequest) - if !assert.NoError(t, err) { - t.Fatalf("unexpected error: %+v", err) - } - - if !assert.Equal(t, tt.expectedSBOM, len(requestBreakdown.sboms)) { - t.Fatalf("unexpected number of SBOMs: %d", len(requestBreakdown.sboms)) - } - - if !assert.Equal(t, tt.expectedLicense, len(requestBreakdown.licenses)) { - t.Fatalf("unexpected number of licenses: %d", len(requestBreakdown.licenses)) - } - }) - } -} diff --git a/grant/report/result_test.go b/grant/report/result_test.go deleted file mode 100644 index 64d9156..0000000 --- a/grant/report/result_test.go +++ /dev/null @@ -1,112 +0,0 @@ -package report - -//import ( -// "testing" -// -// "github.com/google/go-cmp/cmp" -// "github.com/google/go-cmp/cmp/cmpopts" -// -// "github.com/anchore/grant/grant" -// "github.com/anchore/grant/grant/report/result" -// "github.com/anchore/grant/internal/input" -// syftFormat "github.com/anchore/syft/syft/format" -// "github.com/anchore/syft/syft/sbom" -//) -// -//func Test_NewResult(t *testing.T) { -// tests := []struct { -// name string -// policy *grant.Policy -// src string -// want result.Result -// cmpignore []cmp.Option -// }{ -// { -// name: "happy path", -// policy: grant.DefaultPolicy(), -// src: "fixtures/test.spdx.json", -// want: result.Result{ -// Source: "fixtures/test.spdx.json", -// Policy: grant.DefaultPolicy(), -// PackageViolations: make(map[string][]grant.License), -// CompliantPackages: make(map[string][]grant.License), -// IgnoredPackages: make(map[string][]grant.License), -// LicenseViolations: make(map[string][]grant.Package), -// CompliantLicenses: make(map[string][]grant.Package), -// IgnoredLicenses: make(map[string][]grant.Package), -// }, -// cmpignore: []cmp.Option{ -// cmpopts.IgnoreFields(result.Result{}, "sbom", "sbomFormat", "sbomFormatVersion"), -// cmpopts.IgnoreFields(grant.Policy{}, "denyAll", "allowAll"), -// }, -// }, -// } -// -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// sb, formatID, version, err := generateSBOMFromFixture(tt.src) -// if err != nil { -// t.Fatalf("unable to generate sbom from fixture: %+v", err) -// } -// result := result.NewResult(tt.policy, tt.src, sb, formatID, version) -// if diff := cmp.Diff(tt.want, result, tt.cmpignore...); diff != "" { -// t.Errorf("NewResult() mismatch (-want +got):\n%s", diff) -// } -// }) -// } -//} -// -//func Test_Result_Generate(t *testing.T) { -// tests := []struct { -// name string -// policy *grant.Policy -// src string -// expectedResultSummary ResultSummary -// }{ -// { -// name: "Result.Generate() denies all packages with licenses for the default policy", -// policy: grant.DefaultPolicy(), -// src: "fixtures/test.spdx.json", -// // cat grant/fixtures/test.spdx.json | jq '.packages | length' -// // 16 packages - 1 package with no licenses (alpine as the source is registered as a package) = 15 violations -// // TODO: currently the source container package is being dropped by the decoder, so we are not seeing it in the result -// expectedResultSummary: ResultSummary{ -// CompliantPackages: 0, -// PackageViolations: 15, -// IgnoredPackages: 0, -// LicenseViolations: 7, -// CompliantLicenses: 0, -// IgnoredLicenses: 0, -// }, -// }, -// } -// -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// sb, formatID, version, err := generateSBOMFromFixture(tt.src) -// if err != nil { -// t.Fatalf("unable to generate sbom from fixture: %+v", err) -// } -// -// result := result.NewResult(tt.policy, tt.src, sb, formatID, version) -// err = result.Generate() -// if err != nil { -// t.Fatalf("unable to generate result: %+v", err) -// } -// gotResultSummary := result.Summary() -// if diff := cmp.Diff(tt.expectedResultSummary, gotResultSummary); diff != "" { -// t.Errorf("Result.Generate() mismatch (-want +got):\n%s", diff) -// } -// }) -// } -//} -// -//func generateSBOMFromFixture(fixture string) (sb *sbom.SBOM, formatID, version string, err error) { -// reader, err := input.GetReader(fixture) -// if err != nil { -// return nil, "", "", err -// } -// sbomDecoders := syftFormat.NewDecoderCollection(syftFormat.Decoders()...) -// sb, fID, version, err := sbomDecoders.Decode(reader) -// return sb, fID.String(), version, err -//}