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 739b61487..fff8311e4 100644 Binary files a/imgbom/presenter/json/imgs/test-fixtures/snapshot/anchore-fixture-image-simple.golden and b/imgbom/presenter/json/test-fixtures/snapshot/anchore-fixture-image-simple.golden differ diff --git a/imgbom/presenter/presenter.go b/imgbom/presenter/presenter.go index 2e1da0db3..f39e3748a 100644 --- a/imgbom/presenter/presenter.go +++ b/imgbom/presenter/presenter.go @@ -4,10 +4,8 @@ import ( "io" "github.com/anchore/imgbom/imgbom/pkg" - json_dirs "github.com/anchore/imgbom/imgbom/presenter/json/dirs" - json_imgs "github.com/anchore/imgbom/imgbom/presenter/json/imgs" - text_dirs "github.com/anchore/imgbom/imgbom/presenter/text/dirs" - text_imgs "github.com/anchore/imgbom/imgbom/presenter/text/imgs" + "github.com/anchore/imgbom/imgbom/presenter/json" + "github.com/anchore/imgbom/imgbom/presenter/text" "github.com/anchore/imgbom/imgbom/scope" ) @@ -17,41 +15,11 @@ type Presenter interface { // GetPresenter returns a presenter for images or directories func GetPresenter(option Option, s scope.Scope, catalog *pkg.Catalog) Presenter { - src := s.Source() - - switch src.(type) { - case scope.DirSource: - return GetDirPresenter(option, s, catalog) - case scope.ImageSource: - return GetImgPresenter(option, s, catalog) - default: - return nil - } -} - -// GetImgPresenter returns a Json or Text presenter for images -func GetImgPresenter(option Option, s scope.Scope, c *pkg.Catalog) Presenter { - src := s.Source() - img := src.(scope.ImageSource).Img switch option { case JSONPresenter: - return json_imgs.NewPresenter(img, c) + return json.NewPresenter(catalog, s) case TextPresenter: - return text_imgs.NewPresenter(img, c) - default: - return nil - } -} - -// GetDirPresenter returns a Json or Text presenter for directories -func GetDirPresenter(option Option, s scope.Scope, c *pkg.Catalog) Presenter { - src := s.Source() - path := src.(scope.DirSource).Path - switch option { - case JSONPresenter: - return json_dirs.NewPresenter(c, path) - case TextPresenter: - return text_dirs.NewPresenter(c, path) + return text.NewPresenter(catalog, s) default: return nil } diff --git a/imgbom/presenter/text/imgs/presenter.go b/imgbom/presenter/text/presenter.go similarity index 53% rename from imgbom/presenter/text/imgs/presenter.go rename to imgbom/presenter/text/presenter.go index 6a51b2186..2767558cb 100644 --- a/imgbom/presenter/text/imgs/presenter.go +++ b/imgbom/presenter/text/presenter.go @@ -1,50 +1,53 @@ -package imgs +package text import ( "fmt" + "io" "text/tabwriter" "github.com/anchore/imgbom/imgbom/pkg" - stereoscopeImg "github.com/anchore/stereoscope/pkg/image" + "github.com/anchore/imgbom/imgbom/scope" ) 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, } } // Present is a method that is in charge of writing to an output buffer 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() - } - // init the tabular writer w := new(tabwriter.Writer) w.Init(output, 0, 8, 0, '\t', tabwriter.AlignRight) + srcObj := pres.scope.Source() - fmt.Fprintln(w, "[Image]") + switch src := srcObj.(type) { + case scope.DirSource: + fmt.Fprintln(w, fmt.Sprintf("[Path: %s]", src.Path)) + case scope.ImageSource: + fmt.Fprintln(w, "[Image]") - for idx, l := range pres.img.Layers { - fmt.Fprintln(w, " Layer:\t", idx) - fmt.Fprintln(w, " Digest:\t", l.Metadata.Digest) - fmt.Fprintln(w, " Size:\t", l.Metadata.Size) - fmt.Fprintln(w, " MediaType:\t", l.Metadata.MediaType) - fmt.Fprintln(w) - w.Flush() + for idx, l := range src.Img.Layers { + fmt.Fprintln(w, " Layer:\t", idx) + fmt.Fprintln(w, " Digest:\t", l.Metadata.Digest) + fmt.Fprintln(w, " Size:\t", l.Metadata.Size) + fmt.Fprintln(w, " MediaType:\t", l.Metadata.MediaType) + fmt.Fprintln(w) + w.Flush() + } + default: + return fmt.Errorf("unsupported source: %T", src) } // populate artifacts... - // TODO: move this into a common package so that other text presenters can reuse for p := range pres.catalog.Enumerate() { fmt.Fprintln(w, fmt.Sprintf("[%s]", p.Name)) fmt.Fprintln(w, " Version:\t", p.Version) diff --git a/imgbom/presenter/text/imgs/presenter_test.go b/imgbom/presenter/text/presenter_test.go similarity index 59% rename from imgbom/presenter/text/imgs/presenter_test.go rename to imgbom/presenter/text/presenter_test.go index ef0b0e75a..f81a467a3 100644 --- a/imgbom/presenter/text/imgs/presenter_test.go +++ b/imgbom/presenter/text/presenter_test.go @@ -1,4 +1,4 @@ -package imgs +package text import ( "bytes" @@ -7,18 +7,63 @@ 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") +func TestTextDirPresenter(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, + }) + + s, err := scope.NewScopeFromDir("/some/path", scope.AllLayersScope) + if err != nil { + t.Fatalf("unable to create scope: %+v", 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)) + } + +} + type PackageInfo struct { Name string Version string } -func TestTextPresenter(t *testing.T) { +func TestTextImgPresenter(t *testing.T) { var buffer bytes.Buffer catalog := pkg.NewCatalog() @@ -52,9 +97,13 @@ func TestTextPresenter(t *testing.T) { l.Metadata.Digest = "sha256:ad8ecdc058976c07e7e347cb89fa9ad86a294b5ceaae6d09713fb035f84115abf3c4a2388a4af3aa60f13b94f4c6846930bdf53" } - pres := NewPresenter(img, catalog) + s, err := scope.NewScopeFromImage(img, scope.AllLayersScope) + if err != nil { + t.Fatal(err) + } + 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/dirs/test-fixtures/snapshot/TestJsonPresenter.golden b/imgbom/presenter/text/test-fixtures/snapshot/TestJsonPresenter.golden similarity index 100% rename from imgbom/presenter/json/dirs/test-fixtures/snapshot/TestJsonPresenter.golden rename to imgbom/presenter/text/test-fixtures/snapshot/TestJsonPresenter.golden diff --git a/imgbom/presenter/text/test-fixtures/snapshot/TestTextDirPresenter.golden b/imgbom/presenter/text/test-fixtures/snapshot/TestTextDirPresenter.golden new file mode 100644 index 000000000..f41bf461d --- /dev/null +++ b/imgbom/presenter/text/test-fixtures/snapshot/TestTextDirPresenter.golden @@ -0,0 +1,11 @@ +[Path: /some/path] +[package-1] + Version: 1.0.1 + Type: deb + Found by: + +[package-2] + Version: 2.0.1 + Type: deb + Found by: + diff --git a/imgbom/presenter/text/test-fixtures/snapshot/TestTextPresenter.golden b/imgbom/presenter/text/test-fixtures/snapshot/TestTextPresenter.golden new file mode 100644 index 000000000..f41bf461d --- /dev/null +++ b/imgbom/presenter/text/test-fixtures/snapshot/TestTextPresenter.golden @@ -0,0 +1,11 @@ +[Path: /some/path] +[package-1] + Version: 1.0.1 + Type: deb + Found by: + +[package-2] + Version: 2.0.1 + Type: deb + Found by: +