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: simple list output with new colors #14

Merged
merged 4 commits into from
Dec 12, 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
15 changes: 8 additions & 7 deletions .grant.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
#.grant.yaml
show-packages: true
show-packages: false
check-non-spdx: true
format: table
rules:
- pattern: "*gpl*"
name: "gpl-denied"
mode: "deny"
reason: "GPL licenses are not allowed"
exceptions:
- "lib*"
- pattern: "*gfdl*"
mode: "deny"
reason: "GPL licenses are not allowed"
reason: "GPL licenses are not allowed per xxx-xx company policy"
exclusions:
- "alpine-base-layout" # We don't link against this package so we don't care about its license

7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,14 @@ $ grant check ubuntu:latest, alpine:latest
```yaml
#.grant.yaml
config: ".grant.yaml"
quite: false # only print status code 1 or 0 for success or failure
format: table # table, json
show-packages: false # show the packages which contain the licenses
check-non-spdx: false # check licenses that could not be matched to an SPDX identifier
quite: false # only print status code 1 or 0 for success or failure on check
rules:
- pattern: "gpl-*"
mode: "deny"
reason: "GPL licenses are not allowed"
reason: "GPL licenses are not allowed per xxx-xx company policy"
exclusions:
- "alpine-base-layout" # We don't link against this package so we don't care about its license
```
46 changes: 19 additions & 27 deletions cmd/grant/cli/command/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,14 @@ import (
)

type CheckConfig struct {
Config string `json:"config" yaml:"config" mapstructure:"config"`
ShowPackages bool `json:"show-packages" yaml:"show-packages" mapstructure:"show-packages"`
Format string `json:"format" yaml:"format" mapstructure:"format"`
Rules []option.Rule `json:"rules" yaml:"rules" mapstructure:"rules"`
}

func DefaultCheck() *CheckConfig {
return &CheckConfig{
Config: "",
ShowPackages: false,
Rules: []option.Rule{
{
Name: "deny-all",
Reason: "grant by default will deny all licenses",
Pattern: "*",
Severity: "high",
},
},
}
Config string `json:"config" yaml:"config" mapstructure:"config"`
option.Check `json:"" yaml:",inline" mapstructure:",squash"`
}

func (cfg *CheckConfig) RulesFromConfig() (rules grant.Rules, err error) {
rules = make(grant.Rules, 0)
for _, rule := range cfg.Rules {
pattern := strings.ToLower(rule.Pattern)
pattern := strings.ToLower(rule.Pattern) // all patterns are case insensitive
patternGlob, err := glob.Compile(pattern)
if err != nil {
return rules, err
Expand All @@ -57,17 +40,24 @@ func (cfg *CheckConfig) RulesFromConfig() (rules grant.Rules, err error) {
exceptions = append(exceptions, exceptionGlob)
}
rules = append(rules, grant.Rule{
Glob: patternGlob,
Exceptions: exceptions,
Mode: grant.RuleMode(rule.Mode),
Reason: rule.Reason,
Name: rule.Name,
Glob: patternGlob,
OriginalPattern: rule.Pattern,
Exceptions: exceptions,
OriginalExceptions: rule.Exceptions,
Mode: grant.RuleMode(rule.Mode),
Severity: grant.RuleSeverity(rule.Severity),
Reason: rule.Reason,
})
}
return rules, nil
}

func Check(app clio.Application) *cobra.Command {
cfg := DefaultCheck()
cfg := &CheckConfig{
Check: option.DefaultCheck(),
}

// sources are the oci images, sboms, or directories/files to check
var sources []string
return app.SetupCommand(&cobra.Command{
Expand All @@ -92,16 +82,18 @@ func runCheck(cfg *CheckConfig, userInput []string) (errs error) {
if isStdin && !slices.Contains(userInput, "-") {
userInput = append(userInput, "-")
}

rules, err := cfg.RulesFromConfig()
if err != nil {
return errors.Wrap(err, fmt.Sprintf("could not check licenses; could not build rules from config: %s", cfg.Config))
}
policy, err := grant.NewPolicy(rules...)

policy, err := grant.NewPolicy(cfg.CheckNonSPDX, rules...)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("could not check licenses; could not build policy from config: %s", cfg.Config))
}

rep, err := check.NewReport(check.Table, policy, cfg.ShowPackages, userInput...)
rep, err := check.NewReport(check.Format(cfg.Format), policy, cfg.ShowPackages, cfg.CheckNonSPDX, userInput...)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("unable to create report for inputs %s", userInput))
}
Expand Down
92 changes: 42 additions & 50 deletions cmd/grant/cli/internal/check/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io"
"time"

"github.com/gookit/color"
list "github.com/jedib0t/go-pretty/v6/list"

"github.com/anchore/grant/grant"
Expand Down Expand Up @@ -32,7 +33,8 @@ type Report struct {
// 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
func NewReport(f Format, rp grant.Policy, showPackages bool, userRequests ...string) (*Report, error) {
// Where do we render packages that had no licenses?
func NewReport(f Format, rp grant.Policy, showPackages, checkNonSPDX bool, userRequests ...string) (*Report, error) {
if rp.IsEmpty() {
rp = grant.DefaultPolicy()
}
Expand All @@ -41,7 +43,7 @@ func NewReport(f Format, rp grant.Policy, showPackages bool, userRequests ...str
cases := grant.NewCases(rp, userRequests...)
ec := evalutation.EvaluationConfig{
Policy: rp,
CheckNonSPDX: true, // TODO: how do we design the configuration here to inject this value?
CheckNonSPDX: checkNonSPDX,
}

results := evalutation.NewResults(ec, cases...)
Expand All @@ -66,74 +68,64 @@ func (r *Report) Render(out io.Writer) error {
}

func (r *Report) renderTable(out io.Writer) error {
if !r.Results.IsFailed() {
l := newList()
l.AppendItem("No License Violations Found: ✅")
bus.Report(l.Render())
return nil
}

var uiLists []list.Writer
failedEvaluations := r.Results.GetFailedEvaluations()
for _, res := range r.Results {
resulList := newList()
uiLists = append(uiLists, resulList)
resulList.AppendItem(color.Primary.Sprintf("%s", res.Case.UserInput))

for _, rule := range res.Case.Policy.Rules {
failedEvaluations := r.Results.GetFailedEvaluations(res.Case.UserInput, rule)
if len(failedEvaluations) == 0 {
resulList.Indent()
resulList.AppendItem(color.Success.Sprintf("%s", "No License Violations Found"))
resulList.UnIndent()
continue
}
renderEvaluations(rule, r.ShowPackages, resulList, failedEvaluations)
}

}

// segment the results into lists by user input
// lists can optionally show the packages that were evaluated
for input, eval := range failedEvaluations {
l := newList()
uiLists = append(uiLists, l)
l.Indent()
l.AppendItem(input)
renderLicenses(l, eval, r.ShowPackages)
}
for _, l := range uiLists {
bus.Report(l.Render())
}
return nil
}

func renderLicenses(l list.Writer, evals evalutation.LicenseEvaluations, showPackages bool) {
duplicates := make(map[string]struct{})
for _, e := range evals {
var licenseRender string
if e.License.IsSPDX() {
licenseRender = e.License.SPDXExpression
func renderEvaluations(rule grant.Rule, showPackages bool, l list.Writer, e evalutation.LicenseEvaluations) {
l.Indent()
l.AppendItem(color.Secondary.Sprintf("license matches for rule: %s; matched with pattern %s", rule.Name, rule.OriginalPattern))

licenseTracker := make(map[string]struct{})
for _, eval := range e {
var license string
if eval.License.SPDXExpression != "" {
license = eval.License.SPDXExpression
} else {
licenseRender = e.License.Name
license = eval.License.Name
}
if _, ok := duplicates[licenseRender]; ok {
continue
}
duplicates[licenseRender] = struct{}{}
l.Indent()
l.AppendItem(licenseRender)
if showPackages {
packages := evals.Packages(licenseRender)
for _, pkg := range packages {
if _, ok := licenseTracker[license]; !ok {
licenseTracker[license] = struct{}{}
l.Indent()
l.AppendItem(color.Danger.Sprintf("%s", license))
if showPackages {
packages := e.Packages(license)
l.Indent()
l.AppendItem(pkg)
for _, pkg := range packages {
l.AppendItem(color.Light.Sprintf("%s", pkg))
}
l.UnIndent()
}
l.UnIndent()
}
l.UnIndent()
}
return
}

func newList() list.Writer {
l := list.NewWriter()
style := list.StyleDefault
style.CharItemSingle = "▶"
return l
}

type ResultSummary struct {
CompliantPackages int `json:"compliant_packages" yaml:"compliant_packages"`
PackageViolations int `json:"package_violations" yaml:"package_violations"`
IgnoredPackages int `json:"ignored_packages" yaml:"ignored_packages"`
LicenseViolations int `json:"license_violations" yaml:"license_violations"`
CompliantLicenses int `json:"compliant_licenses" yaml:"compliant_licenses"`
IgnoredLicenses int `json:"ignored_licenses" yaml:"ignored_licenses"`
}

func Summary() ResultSummary {
return ResultSummary{}
}
32 changes: 32 additions & 0 deletions cmd/grant/cli/option/check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package option

import "github.com/anchore/clio"

type Check struct {
Format string `json:"format" yaml:"format" mapstructure:"format"`
ShowPackages bool `json:"show-packages" yaml:"show-packages" mapstructure:"show-packages"`
CheckNonSPDX bool `json:"check-non-spdx" yaml:"check-non-spdx" mapstructure:"check-non-spdx"`
Quiet bool `json:"quiet" yaml:"quiet" mapstructure:"quiet"`
Rules []Rule `json:"rules" yaml:"rules" mapstructure:"rules"`
}

func (o *Check) AddFlags(flags clio.FlagSet) {
flags.BoolVarP(&o.ShowPackages, "show-packages", "", "expand the license lists to show packages that contained the license violation")
flags.BoolVarP(&o.CheckNonSPDX, "check-non-spdx", "", "run the configured rules against licenses that could not be matched to the SPDX license list")
}

func DefaultCheck() Check {
return Check{
ShowPackages: false,
CheckNonSPDX: false,
Quiet: false,
Rules: []Rule{
{
Name: "deny-all",
Reason: "grant by default will deny all licenses",
Pattern: "*",
Severity: "high",
},
},
}
}
Loading