From 45b5fa82c8d550abd3574c81eeea6a2671f6a2c8 Mon Sep 17 00:00:00 2001 From: Alfredo Deza Date: Mon, 13 Jul 2020 17:07:02 -0400 Subject: [PATCH] presenters: abstract into text only Signed-off-by: Alfredo Deza --- imgbom/presenter/json/dirs/presenter.go | 77 ----------------- imgbom/presenter/json/dirs/presenter_test.go | 53 ------------ .../snapshot/TestJsonPresenter.golden | 1 - imgbom/presenter/json/{imgs => }/presenter.go | 79 +++++++++--------- .../json/{imgs => }/presenter_test.go | 70 +++++++++++----- .../snapshot/TestJsonDirsPresenter.golden | 1 + .../snapshot/TestJsonImgsPresenter.golden | 1 + .../anchore-fixture-image-simple.golden | Bin 23552 -> 23552 bytes imgbom/presenter/presenter.go | 40 +-------- imgbom/presenter/text/{imgs => }/presenter.go | 41 ++++----- .../text/{imgs => }/presenter_test.go | 57 ++++++++++++- .../snapshot/TestJsonPresenter.golden | 0 .../snapshot/TestTextDirPresenter.golden | 11 +++ .../snapshot/TestTextPresenter.golden | 11 +++ 14 files changed, 189 insertions(+), 253 deletions(-) delete mode 100644 imgbom/presenter/json/dirs/presenter.go delete mode 100644 imgbom/presenter/json/dirs/presenter_test.go delete mode 100644 imgbom/presenter/json/imgs/test-fixtures/snapshot/TestJsonPresenter.golden rename imgbom/presenter/json/{imgs => }/presenter.go (59%) rename imgbom/presenter/json/{imgs => }/presenter_test.go (55%) create mode 100644 imgbom/presenter/json/test-fixtures/snapshot/TestJsonDirsPresenter.golden create mode 100644 imgbom/presenter/json/test-fixtures/snapshot/TestJsonImgsPresenter.golden rename imgbom/presenter/json/{imgs => }/test-fixtures/snapshot/anchore-fixture-image-simple.golden (59%) rename imgbom/presenter/text/{imgs => }/presenter.go (53%) rename imgbom/presenter/text/{imgs => }/presenter_test.go (59%) rename imgbom/presenter/{json/dirs => text}/test-fixtures/snapshot/TestJsonPresenter.golden (100%) create mode 100644 imgbom/presenter/text/test-fixtures/snapshot/TestTextDirPresenter.golden create mode 100644 imgbom/presenter/text/test-fixtures/snapshot/TestTextPresenter.golden diff --git a/imgbom/presenter/json/dirs/presenter.go b/imgbom/presenter/json/dirs/presenter.go deleted file mode 100644 index 25e4d15a6..000000000 --- a/imgbom/presenter/json/dirs/presenter.go +++ /dev/null @@ -1,77 +0,0 @@ -package dirs - -import ( - "encoding/json" - "io" - - "github.com/anchore/imgbom/imgbom/pkg" - "github.com/anchore/imgbom/internal/log" -) - -type Presenter struct { - catalog *pkg.Catalog - path string -} - -func NewPresenter(catalog *pkg.Catalog, path string) *Presenter { - return &Presenter{ - catalog: catalog, - path: path, - } -} - -type document struct { - Artifacts []artifact `json:"artifacts"` - Source string -} - -type source struct { - FoundBy string `json:"foundBy"` - Effects []string `json:"effects"` -} - -type artifact struct { - Name string `json:"name"` - Version string `json:"version"` - Type string `json:"type"` - Cataloger string `json:"cataloger"` - Sources []source `json:"sources"` - Metadata interface{} `json:"metadata"` -} - -func (pres *Presenter) Present(output io.Writer) error { - doc := document{ - Artifacts: make([]artifact, 0), - Source: pres.path, - } - - // populate artifacts... - // TODO: move this into a common package so that other text presenters can reuse - for p := range pres.catalog.Enumerate() { - art := artifact{ - Name: p.Name, - Version: p.Version, - Type: p.Type.String(), - Sources: make([]source, len(p.Source)), - Metadata: p.Metadata, - } - - for idx := range p.Source { - srcObj := source{ - FoundBy: p.FoundBy, - Effects: []string{}, // TODO - } - art.Sources[idx] = srcObj - } - - doc.Artifacts = append(doc.Artifacts, art) - } - - bytes, err := json.Marshal(&doc) - if err != nil { - log.Errorf("failed to marshal json (presenter=json): %w", err) - } - - _, err = output.Write(bytes) - return err -} diff --git a/imgbom/presenter/json/dirs/presenter_test.go b/imgbom/presenter/json/dirs/presenter_test.go deleted file mode 100644 index fbb22564d..000000000 --- a/imgbom/presenter/json/dirs/presenter_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package dirs - -import ( - "bytes" - "flag" - "testing" - - "github.com/anchore/go-testutils" - "github.com/anchore/imgbom/imgbom/pkg" - "github.com/sergi/go-diff/diffmatchpatch" -) - -var update = flag.Bool("update", false, "update the *.golden files for json presenters") - -func TestJsonPresenter(t *testing.T) { - var buffer bytes.Buffer - - catalog := pkg.NewCatalog() - - // populate catalog with test data - catalog.Add(pkg.Package{ - Name: "package-1", - Version: "1.0.1", - Type: pkg.DebPkg, - }) - catalog.Add(pkg.Package{ - Name: "package-2", - Version: "2.0.1", - Type: pkg.DebPkg, - }) - - pres := NewPresenter(catalog, "/some/path") - - // run presenter - err := pres.Present(&buffer) - 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(actual), string(expected), true) - t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs)) - } - -} diff --git a/imgbom/presenter/json/imgs/test-fixtures/snapshot/TestJsonPresenter.golden b/imgbom/presenter/json/imgs/test-fixtures/snapshot/TestJsonPresenter.golden deleted file mode 100644 index 83ab3374d..000000000 --- a/imgbom/presenter/json/imgs/test-fixtures/snapshot/TestJsonPresenter.golden +++ /dev/null @@ -1 +0,0 @@ -{"image":{"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:056c0789fa9ad629ceae6d09713fb035f84115af3c4a88a43aa60f13bc683053","size":22},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:b461c48116592c570a66fed71d5b09662a8172e168b7938cf317af47872cdc9b","size":16},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:00b80053e05c01da485015610d288ce3185fac00d251e2ada02b45a7a7c5f589","size":27}],"size":65,"digest":"sha256:3c53d2d891940f8d8e95acb77b58752f54dc5de9d91d19dd90ced2db76256cea","mediaType":"application/vnd.docker.distribution.manifest.v2+json","tags":["anchore-fixture-image-simple:04e16e44161c8888a1a963720fd0443cbf7eef8101434c431de8725cd98cc9f7"]},"artifacts":[{"name":"package-1","version":"1.0.1","type":"deb","cataloger":"","sources":[{"foundBy":"","layer":0,"effects":[]}],"metadata":null},{"name":"package-2","version":"2.0.1","type":"deb","cataloger":"","sources":[{"foundBy":"","layer":1,"effects":[]}],"metadata":null}]} \ No newline at end of file diff --git a/imgbom/presenter/json/imgs/presenter.go b/imgbom/presenter/json/presenter.go similarity index 59% rename from imgbom/presenter/json/imgs/presenter.go rename to imgbom/presenter/json/presenter.go index d53aeaee7..0f75e0706 100644 --- a/imgbom/presenter/json/imgs/presenter.go +++ b/imgbom/presenter/json/presenter.go @@ -1,4 +1,4 @@ -package imgs +package json import ( "encoding/json" @@ -6,25 +6,39 @@ import ( "io" "github.com/anchore/imgbom/imgbom/pkg" + "github.com/anchore/imgbom/imgbom/scope" "github.com/anchore/imgbom/internal/log" - stereoscopeImg "github.com/anchore/stereoscope/pkg/image" ) type Presenter struct { - img *stereoscopeImg.Image catalog *pkg.Catalog + scope scope.Scope } -func NewPresenter(img *stereoscopeImg.Image, catalog *pkg.Catalog) *Presenter { +func NewPresenter(catalog *pkg.Catalog, s scope.Scope) *Presenter { return &Presenter{ - img: img, catalog: catalog, + scope: s, + } +} + +// Source returns a DirSrc or ImgSrc +func (pres *Presenter) Source() interface{} { + srcObj := pres.scope.Source() + switch src := srcObj.(type) { + case scope.ImageSource: + return pres.scope.ImgSrc + case scope.DirSource: + return pres.scope.DirSrc + default: + return fmt.Errorf("unsupported source: %T", src) } } type document struct { - Image image `json:"image"` Artifacts []artifact `json:"artifacts"` + Image image `json:"image"` + Source string } type image struct { @@ -43,7 +57,6 @@ type layer struct { type source struct { FoundBy string `json:"foundBy"` - Layer int `json:"layer"` Effects []string `json:"effects"` } @@ -56,35 +69,31 @@ type artifact struct { Metadata interface{} `json:"metadata"` } -// nolint:funlen func (pres *Presenter) Present(output io.Writer) error { - tags := make([]string, len(pres.img.Metadata.Tags)) - for idx, tag := range pres.img.Metadata.Tags { - tags[idx] = tag.String() - } - doc := document{ - Image: image{ - Digest: pres.img.Metadata.Digest, - Size: pres.img.Metadata.Size, - MediaType: string(pres.img.Metadata.MediaType), - Tags: tags, - Layers: make([]layer, len(pres.img.Layers)), - }, Artifacts: make([]artifact, 0), } - // populate image... - for idx, l := range pres.img.Layers { - doc.Image.Layers[idx] = layer{ - MediaType: string(l.Metadata.MediaType), - Digest: l.Metadata.Digest, - Size: l.Metadata.Size, - } - } + src := pres.Source() + imgSrc, ok := src.(scope.ImageSource) // populate artifacts... - // TODO: move this into a common package so that other text presenters can reuse + if ok { + tags := make([]string, len(imgSrc.Img.Metadata.Tags)) + for idx, tag := range imgSrc.Img.Metadata.Tags { + tags[idx] = tag.String() + } + doc.Image = image{ + Digest: imgSrc.Img.Metadata.Digest, + Size: imgSrc.Img.Metadata.Size, + MediaType: string(imgSrc.Img.Metadata.MediaType), + Tags: tags, + Layers: make([]layer, len(imgSrc.Img.Layers)), + } + } else { + doc.Source = pres.scope.DirSrc.Path + } + for p := range pres.catalog.Enumerate() { art := artifact{ Name: p.Name, @@ -94,19 +103,9 @@ func (pres *Presenter) Present(output io.Writer) error { Metadata: p.Metadata, } - for idx, src := range p.Source { - fileMetadata, err := pres.img.FileCatalog.Get(src) - var layer int - if err != nil { - // TODO: test case - return fmt.Errorf("could not get metadata from catalog (presenter=json src=%v): %w", src, err) - } - - layer = int(fileMetadata.Source.Metadata.Index) - + for idx := range p.Source { srcObj := source{ FoundBy: p.FoundBy, - Layer: layer, Effects: []string{}, // TODO } art.Sources[idx] = srcObj diff --git a/imgbom/presenter/json/imgs/presenter_test.go b/imgbom/presenter/json/presenter_test.go similarity index 55% rename from imgbom/presenter/json/imgs/presenter_test.go rename to imgbom/presenter/json/presenter_test.go index 1f174bb1f..8e890be2c 100644 --- a/imgbom/presenter/json/imgs/presenter_test.go +++ b/imgbom/presenter/json/presenter_test.go @@ -1,4 +1,4 @@ -package imgs +package json import ( "bytes" @@ -7,35 +7,58 @@ import ( "github.com/anchore/go-testutils" "github.com/anchore/imgbom/imgbom/pkg" + "github.com/anchore/imgbom/imgbom/scope" "github.com/anchore/stereoscope/pkg/file" "github.com/sergi/go-diff/diffmatchpatch" ) var update = flag.Bool("update", false, "update the *.golden files for json presenters") -// TODO: add a JSON schema and write a test that validates output against the schema -// func validateAgainstV1Schema(t *testing.T, json string) { -// fullSchemaPath, err := filepath.Abs("v1-schema.json") -// if err != nil { -// t.Fatal("could not get path to schema:", err) -// } -// schemaLoader := gojsonschema.NewReferenceLoader(fmt.Sprintf("file://%s", fullSchemaPath)) -// documentLoader := gojsonschema.NewStringLoader(json) +func TestJsonDirsPresenter(t *testing.T) { + var buffer bytes.Buffer -// result, err := gojsonschema.Validate(schemaLoader, documentLoader) -// if err != nil { -// t.Fatal("unable to validate json schema:", err.Error()) -// } + catalog := pkg.NewCatalog() -// if !result.Valid() { -// t.Errorf("failed json schema validation:") -// for _, desc := range result.Errors() { -// t.Errorf(" - %s\n", desc) -// } -// } -// } + // populate catalog with test data + catalog.Add(pkg.Package{ + Name: "package-1", + Version: "1.0.1", + Type: pkg.DebPkg, + }) + catalog.Add(pkg.Package{ + Name: "package-2", + Version: "2.0.1", + Type: pkg.DebPkg, + }) -func TestJsonPresenter(t *testing.T) { + s, err := scope.NewScopeFromDir("/some/path", scope.AllLayersScope) + if err != nil { + t.Fatal(err) + } + pres := NewPresenter(catalog, s) + + // run presenter + err = pres.Present(&buffer) + 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(actual), string(expected), true) + t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs)) + } + +} + +func TestJsonImgsPresenter(t *testing.T) { var buffer bytes.Buffer testImage := "image-simple" @@ -65,10 +88,11 @@ func TestJsonPresenter(t *testing.T) { Type: pkg.DebPkg, }) - pres := NewPresenter(img, catalog) + s, err := scope.NewScopeFromImage(img, scope.AllLayersScope) + pres := NewPresenter(catalog, s) // run presenter - err := pres.Present(&buffer) + err = pres.Present(&buffer) if err != nil { t.Fatal(err) } diff --git a/imgbom/presenter/json/test-fixtures/snapshot/TestJsonDirsPresenter.golden b/imgbom/presenter/json/test-fixtures/snapshot/TestJsonDirsPresenter.golden new file mode 100644 index 000000000..f8c261758 --- /dev/null +++ b/imgbom/presenter/json/test-fixtures/snapshot/TestJsonDirsPresenter.golden @@ -0,0 +1 @@ +{"artifacts":[{"name":"package-1","version":"1.0.1","type":"deb","cataloger":"","sources":[],"metadata":null},{"name":"package-2","version":"2.0.1","type":"deb","cataloger":"","sources":[],"metadata":null}],"image":{"layers":null,"size":0,"digest":"","mediaType":"","tags":null},"Source":"/some/path"} \ No newline at end of file diff --git a/imgbom/presenter/json/test-fixtures/snapshot/TestJsonImgsPresenter.golden b/imgbom/presenter/json/test-fixtures/snapshot/TestJsonImgsPresenter.golden new file mode 100644 index 000000000..2ef23aa84 --- /dev/null +++ b/imgbom/presenter/json/test-fixtures/snapshot/TestJsonImgsPresenter.golden @@ -0,0 +1 @@ +{"artifacts":[{"name":"package-1","version":"1.0.1","type":"deb","cataloger":"","sources":[{"foundBy":"","effects":[]}],"metadata":null},{"name":"package-2","version":"2.0.1","type":"deb","cataloger":"","sources":[{"foundBy":"","effects":[]}],"metadata":null}],"image":{"layers":[{"mediaType":"","digest":"","size":0},{"mediaType":"","digest":"","size":0},{"mediaType":"","digest":"","size":0}],"size":65,"digest":"sha256:26e4732b961662cd066976b6cadc25f2cedee52db90be26ee7c120d2ff468ef2","mediaType":"application/vnd.docker.distribution.manifest.v2+json","tags":["anchore-fixture-image-simple:04e16e44161c8888a1a963720fd0443cbf7eef8101434c431de8725cd98cc9f7"]},"Source":""} \ No newline at end of file diff --git a/imgbom/presenter/json/imgs/test-fixtures/snapshot/anchore-fixture-image-simple.golden b/imgbom/presenter/json/test-fixtures/snapshot/anchore-fixture-image-simple.golden similarity index 59% rename from imgbom/presenter/json/imgs/test-fixtures/snapshot/anchore-fixture-image-simple.golden rename to imgbom/presenter/json/test-fixtures/snapshot/anchore-fixture-image-simple.golden index 739b614870d13721615fb355e03552da1525bc68..fff8311e47627d0e27594db608d5669fc07e805e 100644 GIT binary patch literal 23552 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 Z{