mirror of
https://github.com/anchore/grype
synced 2024-11-10 06:34:13 +00:00
create a presenter structure with a JSON handler
Signed-off-by: Alfredo Deza <adeza@anchore.com>
This commit is contained in:
parent
b484b85890
commit
e42287cd88
6 changed files with 233 additions and 0 deletions
64
vulnscan/presenter/json/presenter.go
Normal file
64
vulnscan/presenter/json/presenter.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/anchore/imgbom/imgbom/pkg"
|
||||
"github.com/anchore/vulnscan/internal/log"
|
||||
"github.com/anchore/vulnscan/vulnscan/result"
|
||||
)
|
||||
|
||||
// Presenter is a generic struct for holding fields needed for reporting
|
||||
type Presenter struct{}
|
||||
|
||||
// NewPresenter is a *Presenter constructor
|
||||
func NewPresenter() *Presenter {
|
||||
return &Presenter{}
|
||||
}
|
||||
|
||||
// ResultObj is a single item for the JSON array reported
|
||||
type ResultObj struct {
|
||||
Cve string `json:"cve"`
|
||||
Package Package `json:"package"`
|
||||
}
|
||||
|
||||
// Package is a nested JSON object from ResultObj
|
||||
type Package struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// Present creates a JSON-based reporting
|
||||
func (pres *Presenter) Present(output io.Writer, catalog *pkg.Catalog, results result.Result) error {
|
||||
doc := make([]ResultObj, 0)
|
||||
|
||||
for match := range results.Enumerate() {
|
||||
pkg := catalog.Package(match.Package.ID())
|
||||
doc = append(
|
||||
doc,
|
||||
ResultObj{
|
||||
Cve: match.Vulnerability.ID,
|
||||
Package: Package{Name: pkg.Name, Version: pkg.Version}},
|
||||
)
|
||||
|
||||
//for _, match := range matches {
|
||||
doc = append(
|
||||
doc,
|
||||
ResultObj{
|
||||
Cve: match.Vulnerability.ID,
|
||||
Package: Package{Name: pkg.Name, Version: pkg.Version}},
|
||||
)
|
||||
|
||||
//}
|
||||
}
|
||||
|
||||
bytes, err := json.Marshal(&doc)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("failed to marshal json (presenter=json): %w", err)
|
||||
}
|
||||
|
||||
_, err = output.Write(bytes)
|
||||
return err
|
||||
}
|
107
vulnscan/presenter/json/presenter_test.go
Normal file
107
vulnscan/presenter/json/presenter_test.go
Normal file
|
@ -0,0 +1,107 @@
|
|||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/go-testutils"
|
||||
"github.com/anchore/imgbom/imgbom/pkg"
|
||||
"github.com/anchore/vulnscan/vulnscan/match"
|
||||
"github.com/anchore/vulnscan/vulnscan/result"
|
||||
"github.com/anchore/vulnscan/vulnscan/vulnerability"
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
)
|
||||
|
||||
var update = flag.Bool("update", false, "update the *.golden files for json presenters")
|
||||
|
||||
var pkg1 = pkg.Package{
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
Type: pkg.DebPkg,
|
||||
}
|
||||
|
||||
var pkg2 = pkg.Package{
|
||||
Name: "package-2",
|
||||
Version: "2.0.1",
|
||||
Type: pkg.DebPkg,
|
||||
}
|
||||
|
||||
var match1 = match.Match{
|
||||
Type: match.ExactDirectMatch,
|
||||
Vulnerability: vulnerability.Vulnerability{ID: "CVE-1999-0001"},
|
||||
// XXX: matches also have a `pkg *pkg.Pkg` field
|
||||
}
|
||||
|
||||
var match2 = match.Match{
|
||||
Type: match.ExactIndirectMatch,
|
||||
Vulnerability: vulnerability.Vulnerability{ID: "CVE-1999-0002"},
|
||||
}
|
||||
|
||||
func TestJsonPresenter(t *testing.T) {
|
||||
|
||||
pres := NewPresenter()
|
||||
var buffer bytes.Buffer
|
||||
|
||||
results := result.NewResult()
|
||||
|
||||
results.Add(&pkg1, match1, match2)
|
||||
|
||||
catalog := pkg.NewCatalog()
|
||||
|
||||
// populate catalog with test data
|
||||
catalog.Add(pkg1)
|
||||
catalog.Add(pkg2)
|
||||
|
||||
// TODO: add a constructor for a match.Match when the data is better shaped
|
||||
|
||||
// run presenter
|
||||
err := pres.Present(&buffer, catalog, results)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual := buffer.Bytes()
|
||||
if *update {
|
||||
testutils.UpdateGoldenFileContents(t, actual)
|
||||
}
|
||||
|
||||
var expected = testutils.GetGoldenFileContents(t)
|
||||
|
||||
if !bytes.Equal(expected, actual) {
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(string(expected), string(actual), true)
|
||||
t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs))
|
||||
}
|
||||
|
||||
// TODO: add me back in when there is a JSON schema
|
||||
// validateAgainstV1Schema(t, string(actual))
|
||||
}
|
||||
|
||||
func TestEmptyJsonPresenter(t *testing.T) {
|
||||
// Expected to have an empty JSON array back
|
||||
pres := NewPresenter()
|
||||
var buffer bytes.Buffer
|
||||
|
||||
results := result.NewResult()
|
||||
|
||||
catalog := pkg.NewCatalog()
|
||||
|
||||
// run presenter
|
||||
err := pres.Present(&buffer, catalog, results)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual := buffer.Bytes()
|
||||
if *update {
|
||||
testutils.UpdateGoldenFileContents(t, actual)
|
||||
}
|
||||
|
||||
var expected = testutils.GetGoldenFileContents(t)
|
||||
|
||||
if !bytes.Equal(expected, actual) {
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(string(expected), string(actual), true)
|
||||
t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs))
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
[]
|
|
@ -0,0 +1 @@
|
|||
[{"cve":"CVE-1999-0001","package":{"name":"package-1","version":"1.0.1"}},{"cve":"CVE-1999-0002","package":{"name":"package-1","version":"1.0.1"}}]
|
36
vulnscan/presenter/option.go
Normal file
36
vulnscan/presenter/option.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
package presenter
|
||||
|
||||
import "strings"
|
||||
|
||||
const (
|
||||
UnknownPresenter Option = iota
|
||||
JSONPresenter
|
||||
)
|
||||
|
||||
var optionStr = []string{
|
||||
"UnknownPresenter",
|
||||
"json",
|
||||
}
|
||||
|
||||
var Options = []Option{
|
||||
JSONPresenter,
|
||||
}
|
||||
|
||||
type Option int
|
||||
|
||||
func ParseOption(userStr string) Option {
|
||||
switch strings.ToLower(userStr) {
|
||||
case strings.ToLower(JSONPresenter.String()):
|
||||
return JSONPresenter
|
||||
default:
|
||||
return UnknownPresenter
|
||||
}
|
||||
}
|
||||
|
||||
func (o Option) String() string {
|
||||
if int(o) >= len(optionStr) || o < 0 {
|
||||
return optionStr[0]
|
||||
}
|
||||
|
||||
return optionStr[o]
|
||||
}
|
24
vulnscan/presenter/presenter.go
Normal file
24
vulnscan/presenter/presenter.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package presenter
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/anchore/imgbom/imgbom/pkg"
|
||||
"github.com/anchore/vulnscan/vulnscan/presenter/json"
|
||||
"github.com/anchore/vulnscan/vulnscan/result"
|
||||
)
|
||||
|
||||
// Presenter is the main interface other Presenters need to implement
|
||||
type Presenter interface {
|
||||
Present(io.Writer, *pkg.Catalog, result.Result) error
|
||||
}
|
||||
|
||||
// GetPresenter retrieves a Presenter that matches a CLI option
|
||||
func GetPresenter(option Option) Presenter {
|
||||
switch option {
|
||||
case JSONPresenter:
|
||||
return json.NewPresenter()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue