create a presenter structure with a JSON handler

Signed-off-by: Alfredo Deza <adeza@anchore.com>
This commit is contained in:
Alfredo Deza 2020-06-18 10:44:52 -04:00
parent b484b85890
commit e42287cd88
6 changed files with 233 additions and 0 deletions

View 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
}

View 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))
}
}

View file

@ -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"}}]

View 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]
}

View 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
}
}