From d6db34f4239b0327bf2284e04cace4005b195ffa Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Thu, 21 Mar 2024 03:48:00 -0400 Subject: [PATCH] feat: add CSV output option Signed-off-by: Christopher Phillips --- cmd/grant/cli/internal/format.go | 7 ++++- cmd/grant/cli/internal/list/report.go | 45 +++++++++++++++++++++++++-- grant/package.go | 2 ++ 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/cmd/grant/cli/internal/format.go b/cmd/grant/cli/internal/format.go index 6ab86c7..801afae 100644 --- a/cmd/grant/cli/internal/format.go +++ b/cmd/grant/cli/internal/format.go @@ -11,9 +11,10 @@ type Format string const ( JSON Format = "json" Table Format = "table" + CSV Format = "csv" ) -var ValidFormats = []Format{JSON, Table} +var ValidFormats = []Format{JSON, Table, CSV} // ValidateFormat returns a valid format or the default format if the given format is invalid func ValidateFormat(f Format) Format { @@ -22,6 +23,8 @@ func ValidateFormat(f Format) Format { return JSON case "table": return Table + case "csv": + return CSV default: return Table } @@ -58,6 +61,7 @@ func NewLicense(l grant.License) License { type Package struct { Name string `json:"name" yaml:"name"` Version string `json:"version" yaml:"version"` + Type string `json:"type" yaml:"type"` Locations []string `json:"locations" yaml:"locations"` } @@ -69,6 +73,7 @@ func NewPackage(p *grant.Package) Package { Name: p.Name, Version: p.Version, Locations: p.Locations, + Type: p.Type, } } diff --git a/cmd/grant/cli/internal/list/report.go b/cmd/grant/cli/internal/list/report.go index e851d76..60999ad 100644 --- a/cmd/grant/cli/internal/list/report.go +++ b/cmd/grant/cli/internal/list/report.go @@ -1,9 +1,11 @@ package list import ( + "encoding/csv" "encoding/json" "errors" "fmt" + "os" "slices" "time" @@ -55,6 +57,8 @@ func (r *Report) Render() error { return r.renderList() case internal.JSON: return r.renderJSON() + case internal.CSV: + return r.renderCSV() default: r.errors = append(r.errors, fmt.Errorf("invalid format: %s; valid formats are: %s", r.Config.Options.Format, internal.ValidFormats)) return errors.Join(r.errors...) @@ -84,7 +88,40 @@ func NewResult(input string, gl grant.License, gp ...*grant.Package) Result { } } -func (r *Report) renderJSON() error { +func (r *Report) renderCSV() error { + response := getResponse(r) + headers := []string{"component", "component_version", "license", "website", "type"} + data := [][]string{ + headers, + } + + for _, rslt := range response.Results { + for _, pkg := range rslt.Packages { + data = append(data, []string{ + pkg.Name, + pkg.Version, + rslt.License.Name, + rslt.License.Reference, + pkg.Type, + }) + } + } + + writer := csv.NewWriter(os.Stdout) + defer writer.Flush() + + for _, record := range data { + if err := writer.Write(record); err != nil { + return err + } + } + if err := writer.Error(); err != nil { + return err + } + return nil +} + +func getResponse(r *Report) Response { resp := Response{ ReportID: r.ReportID, Timestamp: r.Timestamp, @@ -94,7 +131,6 @@ func (r *Report) renderJSON() error { for _, c := range r.Cases { resp.Inputs = append(resp.Inputs, c.UserInput) - // TODO: is it better to invert this here and grab packages -> licenses since package is the cases first class licensePackages, licenses, _ := c.GetLicenses() for key, l := range licenses { packages := licensePackages[key] @@ -102,6 +138,11 @@ func (r *Report) renderJSON() error { resp.Results = append(resp.Results, result) } } + return resp +} + +func (r *Report) renderJSON() error { + resp := getResponse(r) jsonData, err := json.Marshal(resp) if err != nil { return err diff --git a/grant/package.go b/grant/package.go index e2378b5..49cbb43 100644 --- a/grant/package.go +++ b/grant/package.go @@ -10,6 +10,7 @@ type PackageID string type Package struct { ID PackageID `json:"id" yaml:"id"` Name string `json:"name" yaml:"name"` + Type string `json:"type" yaml:"type"` Version string `json:"version" yaml:"version"` Licenses []License `json:"licenses" yaml:"licenses"` Locations []string `json:"locations" yaml:"locations"` @@ -25,6 +26,7 @@ func ConvertSyftPackage(p syftPkg.Package) *Package { return &Package{ Name: p.Name, Version: p.Version, + Type: string(p.Type), Licenses: ConvertSyftLicenses(p.Licenses), Locations: packageLocations, }