Skip to content

Commit

Permalink
feat: simple list output with new colors (#14)
Browse files Browse the repository at this point in the history
---------

Signed-off-by: Christopher Phillips <[email protected]>
  • Loading branch information
spiffcs authored Dec 12, 2023
1 parent 452dbba commit e1b1caa
Show file tree
Hide file tree
Showing 12 changed files with 216 additions and 137 deletions.
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

0 comments on commit e1b1caa

Please sign in to comment.