mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 16:33:21 +01:00
Merge pull request #68 from anchore/issue-31-1
Add support for file-based contents
This commit is contained in:
commit
fe338760b0
82
cmd/root.go
82
cmd/root.go
@ -17,13 +17,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
Use: fmt.Sprintf("%s [IMAGE]", internal.ApplicationName),
|
Use: fmt.Sprintf("%s [SOURCE]", internal.ApplicationName),
|
||||||
Short: "A container image BOM tool", // TODO: add copy
|
Short: "A tool that generates a Software Build Of Materials (SBOM)",
|
||||||
Long: internal.Tprintf(`\
|
Long: internal.Tprintf(`\
|
||||||
Supports the following image sources:
|
Supports the following image sources:
|
||||||
{{.appName}} yourrepo/yourimage:tag defaults to using images from a docker daemon
|
{{.appName}} yourrepo/yourimage:tag defaults to using images from a docker daemon
|
||||||
{{.appName}} docker://yourrepo/yourimage:tag explicitly use the docker daemon
|
{{.appName}} docker://yourrepo/yourimage:tag explicitly use the docker daemon
|
||||||
{{.appName}} tar://path/to/yourimage.tar use a tarball from disk
|
{{.appName}} tar://path/to/yourimage.tar use a tarball from disk
|
||||||
|
{{.appName}} dir://path/to/yourproject read directly from a path in disk
|
||||||
`, map[string]interface{}{
|
`, map[string]interface{}{
|
||||||
"appName": internal.ApplicationName,
|
"appName": internal.ApplicationName,
|
||||||
}),
|
}),
|
||||||
@ -44,38 +45,61 @@ func init() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func startWorker(userImage string) <-chan error {
|
func startWorker(userInput string) <-chan error {
|
||||||
errs := make(chan error)
|
errs := make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
defer close(errs)
|
defer close(errs)
|
||||||
|
protocol := imgbom.NewProtocol(userInput)
|
||||||
|
fmt.Printf("protocol: %+v", protocol)
|
||||||
|
|
||||||
log.Infof("Fetching image '%s'", userImage)
|
switch protocol.Type {
|
||||||
img, err := stereoscope.GetImage(userImage)
|
case imgbom.DirProtocol:
|
||||||
if err != nil {
|
|
||||||
errs <- fmt.Errorf("could not fetch image '%s': %w", userImage, err)
|
log.Info("Cataloging directory")
|
||||||
return
|
catalog, err := imgbom.CatalogDir(protocol.Value, appConfig.ScopeOpt)
|
||||||
|
if err != nil {
|
||||||
|
errs <- fmt.Errorf("could not produce catalog: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bus.Publish(partybus.Event{
|
||||||
|
Type: event.CatalogerFinished,
|
||||||
|
Value: presenter.GetDirPresenter(appConfig.PresenterOpt, protocol.Value, catalog),
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
log.Infof("Fetching image '%s'", userInput)
|
||||||
|
img, err := stereoscope.GetImage(userInput)
|
||||||
|
|
||||||
|
if err != nil || img == nil {
|
||||||
|
errs <- fmt.Errorf("could not fetch image '%s': %w", userInput, err)
|
||||||
|
|
||||||
|
// TODO: this needs to be handled better
|
||||||
|
bus.Publish(partybus.Event{
|
||||||
|
Type: event.CatalogerFinished,
|
||||||
|
Value: nil,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer stereoscope.Cleanup()
|
||||||
|
|
||||||
|
log.Info("Identifying Distro")
|
||||||
|
distro := imgbom.IdentifyDistro(img)
|
||||||
|
if distro == nil {
|
||||||
|
log.Errorf("error identifying distro")
|
||||||
|
} else {
|
||||||
|
log.Infof(" Distro: %s", distro)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Cataloging Image")
|
||||||
|
catalog, err := imgbom.CatalogImg(img, appConfig.ScopeOpt)
|
||||||
|
if err != nil {
|
||||||
|
errs <- fmt.Errorf("could not produce catalog: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bus.Publish(partybus.Event{
|
||||||
|
Type: event.CatalogerFinished,
|
||||||
|
Value: presenter.GetImgPresenter(appConfig.PresenterOpt, img, catalog),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
defer stereoscope.Cleanup()
|
|
||||||
|
|
||||||
log.Info("Identifying Distro")
|
|
||||||
distro := imgbom.IdentifyDistro(img)
|
|
||||||
if distro == nil {
|
|
||||||
log.Errorf("error identifying distro")
|
|
||||||
} else {
|
|
||||||
log.Infof(" Distro: %s", distro)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Cataloging image")
|
|
||||||
catalog, err := imgbom.CatalogImage(img, appConfig.ScopeOpt)
|
|
||||||
if err != nil {
|
|
||||||
errs <- fmt.Errorf("could not catalog image: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Complete!")
|
|
||||||
bus.Publish(partybus.Event{
|
|
||||||
Type: event.CatalogerFinished,
|
|
||||||
Value: presenter.GetPresenter(appConfig.PresenterOpt, img, catalog),
|
|
||||||
})
|
|
||||||
}()
|
}()
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,7 +29,7 @@ func Catalogers() []string {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func Catalog(s scope.Scope) (*pkg.Catalog, error) {
|
func Catalog(s scope.FileContentResolver) (*pkg.Catalog, error) {
|
||||||
return controllerInstance.catalog(s)
|
return controllerInstance.catalog(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +71,7 @@ func (c *controller) trackCataloger() (*progress.Manual, *progress.Manual) {
|
|||||||
return &filesProcessed, &packagesDiscovered
|
return &filesProcessed, &packagesDiscovered
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *controller) catalog(s scope.Scope) (*pkg.Catalog, error) {
|
func (c *controller) catalog(s scope.FileContentResolver) (*pkg.Catalog, error) {
|
||||||
catalog := pkg.NewCatalog()
|
catalog := pkg.NewCatalog()
|
||||||
fileSelection := make([]file.Reference, 0)
|
fileSelection := make([]file.Reference, 0)
|
||||||
|
|
||||||
@ -79,13 +79,13 @@ func (c *controller) catalog(s scope.Scope) (*pkg.Catalog, error) {
|
|||||||
|
|
||||||
// ask catalogers for files to extract from the image tar
|
// ask catalogers for files to extract from the image tar
|
||||||
for _, a := range c.catalogers {
|
for _, a := range c.catalogers {
|
||||||
fileSelection = append(fileSelection, a.SelectFiles(&s)...)
|
fileSelection = append(fileSelection, a.SelectFiles(s)...)
|
||||||
log.Debugf("cataloger '%s' selected '%d' files", a.Name(), len(fileSelection))
|
log.Debugf("cataloger '%s' selected '%d' files", a.Name(), len(fileSelection))
|
||||||
filesProcessed.N += int64(len(fileSelection))
|
filesProcessed.N += int64(len(fileSelection))
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetch contents for requested selection by catalogers
|
// fetch contents for requested selection by catalogers
|
||||||
contents, err := s.Image.MultipleFileContentsByRef(fileSelection...)
|
contents, err := s.MultipleFileContentsByRef(fileSelection...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,7 +24,7 @@ func Identify(img *image.Image) *Distro {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for path, fn := range identityFiles {
|
for path, fn := range identityFiles {
|
||||||
contents, err := img.FileContentsFromSquash(path)
|
contents, err := img.FileContentsFromSquash(path) // TODO: this call replaced with "MultipleFileContents"
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("unable to get contents from %s: %s", path, err)
|
log.Debugf("unable to get contents from %s: %s", path, err)
|
||||||
|
|||||||
@ -16,12 +16,19 @@ func IdentifyDistro(img *image.Image) *distro.Distro {
|
|||||||
return distro.Identify(img)
|
return distro.Identify(img)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CatalogImage(img *image.Image, o scope.Option) (*pkg.Catalog, error) {
|
func CatalogDir(d string, o scope.Option) (*pkg.Catalog, error) {
|
||||||
s, err := scope.NewScope(img, o)
|
s, err := scope.NewDirScope(d, o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return cataloger.Catalog(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CatalogImg(img *image.Image, o scope.Option) (*pkg.Catalog, error) {
|
||||||
|
s, err := scope.NewImageScope(img, o)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return cataloger.Catalog(s)
|
return cataloger.Catalog(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
77
imgbom/presenter/json/dirs/presenter.go
Normal file
77
imgbom/presenter/json/dirs/presenter.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
53
imgbom/presenter/json/dirs/presenter_test.go
Normal file
53
imgbom/presenter/json/dirs/presenter_test.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -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}],"Source":"/some/path"}
|
||||||
@ -1,7 +1,8 @@
|
|||||||
package json
|
package imgs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/anchore/imgbom/imgbom/pkg"
|
"github.com/anchore/imgbom/imgbom/pkg"
|
||||||
@ -82,6 +83,7 @@ func (pres *Presenter) Present(output io.Writer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// populate artifacts...
|
// populate artifacts...
|
||||||
|
// TODO: move this into a common package so that other text presenters can reuse
|
||||||
for p := range pres.catalog.Enumerate() {
|
for p := range pres.catalog.Enumerate() {
|
||||||
art := artifact{
|
art := artifact{
|
||||||
Name: p.Name,
|
Name: p.Name,
|
||||||
@ -93,14 +95,17 @@ func (pres *Presenter) Present(output io.Writer) error {
|
|||||||
|
|
||||||
for idx, src := range p.Source {
|
for idx, src := range p.Source {
|
||||||
fileMetadata, err := pres.img.FileCatalog.Get(src)
|
fileMetadata, err := pres.img.FileCatalog.Get(src)
|
||||||
|
var layer int
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: test case
|
// TODO: test case
|
||||||
log.Errorf("could not get metadata from catalog (presenter=json): %+v", src)
|
return fmt.Errorf("could not get metadata from catalog (presenter=json src=%v): %w", src, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
layer = int(fileMetadata.Source.Metadata.Index)
|
||||||
|
|
||||||
srcObj := source{
|
srcObj := source{
|
||||||
FoundBy: p.FoundBy,
|
FoundBy: p.FoundBy,
|
||||||
Layer: int(fileMetadata.Source.Metadata.Index),
|
Layer: layer,
|
||||||
Effects: []string{}, // TODO
|
Effects: []string{}, // TODO
|
||||||
}
|
}
|
||||||
art.Sources[idx] = srcObj
|
art.Sources[idx] = srcObj
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package json
|
package imgs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -4,8 +4,10 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/anchore/imgbom/imgbom/pkg"
|
"github.com/anchore/imgbom/imgbom/pkg"
|
||||||
"github.com/anchore/imgbom/imgbom/presenter/json"
|
json_dirs "github.com/anchore/imgbom/imgbom/presenter/json/dirs"
|
||||||
"github.com/anchore/imgbom/imgbom/presenter/text"
|
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/stereoscope/pkg/image"
|
"github.com/anchore/stereoscope/pkg/image"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -13,13 +15,23 @@ type Presenter interface {
|
|||||||
Present(io.Writer) error
|
Present(io.Writer) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPresenter(option Option, img *image.Image, catalog *pkg.Catalog) Presenter {
|
func GetImgPresenter(option Option, img *image.Image, catalog *pkg.Catalog) Presenter {
|
||||||
switch option {
|
switch option {
|
||||||
case JSONPresenter:
|
case JSONPresenter:
|
||||||
return json.NewPresenter(img, catalog)
|
return json_imgs.NewPresenter(img, catalog)
|
||||||
case TextPresenter:
|
case TextPresenter:
|
||||||
return text.NewPresenter(img, catalog)
|
return text_imgs.NewPresenter(img, catalog)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDirPresenter(option Option, path string, catalog *pkg.Catalog) Presenter {
|
||||||
|
switch option {
|
||||||
|
case JSONPresenter:
|
||||||
|
return json_dirs.NewPresenter(catalog, path)
|
||||||
|
case TextPresenter:
|
||||||
|
return text_dirs.NewPresenter(catalog, path)
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
45
imgbom/presenter/text/dirs/presenter.go
Normal file
45
imgbom/presenter/text/dirs/presenter.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package text
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"text/tabwriter"
|
||||||
|
|
||||||
|
"github.com/anchore/imgbom/imgbom/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Presenter struct {
|
||||||
|
catalog *pkg.Catalog
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPresenter(catalog *pkg.Catalog, path string) *Presenter {
|
||||||
|
return &Presenter{
|
||||||
|
catalog: catalog,
|
||||||
|
path: path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Present is a method that is in charge of writing to an output buffer
|
||||||
|
func (pres *Presenter) Present(output io.Writer) error {
|
||||||
|
// init the tabular writer
|
||||||
|
w := new(tabwriter.Writer)
|
||||||
|
w.Init(output, 0, 8, 0, '\t', tabwriter.AlignRight)
|
||||||
|
fmt.Fprintln(w, fmt.Sprintf("[Path: %s]", pres.path))
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
fmt.Fprintln(w, " Type:\t", p.Type.String())
|
||||||
|
if p.Metadata != nil {
|
||||||
|
fmt.Fprintf(w, " Metadata:\t%+v\n", p.Metadata)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(w, " Found by:\t", p.FoundBy)
|
||||||
|
fmt.Fprintln(w)
|
||||||
|
w.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
53
imgbom/presenter/text/dirs/presenter_test.go
Normal file
53
imgbom/presenter/text/dirs/presenter_test.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package text
|
||||||
|
|
||||||
|
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 TestTextPresenter(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))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -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}],"Source":"/some/path"}
|
||||||
@ -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:
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package text
|
package imgs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -9,13 +9,11 @@ import (
|
|||||||
stereoscopeImg "github.com/anchore/stereoscope/pkg/image"
|
stereoscopeImg "github.com/anchore/stereoscope/pkg/image"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Presenter holds the Present method to produce output
|
|
||||||
type Presenter struct {
|
type Presenter struct {
|
||||||
img *stereoscopeImg.Image
|
img *stereoscopeImg.Image
|
||||||
catalog *pkg.Catalog
|
catalog *pkg.Catalog
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPresenter is a constructor for a Presenter
|
|
||||||
func NewPresenter(img *stereoscopeImg.Image, catalog *pkg.Catalog) *Presenter {
|
func NewPresenter(img *stereoscopeImg.Image, catalog *pkg.Catalog) *Presenter {
|
||||||
return &Presenter{
|
return &Presenter{
|
||||||
img: img,
|
img: img,
|
||||||
@ -46,6 +44,7 @@ func (pres *Presenter) Present(output io.Writer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// populate artifacts...
|
// populate artifacts...
|
||||||
|
// TODO: move this into a common package so that other text presenters can reuse
|
||||||
for p := range pres.catalog.Enumerate() {
|
for p := range pres.catalog.Enumerate() {
|
||||||
fmt.Fprintln(w, fmt.Sprintf("[%s]", p.Name))
|
fmt.Fprintln(w, fmt.Sprintf("[%s]", p.Name))
|
||||||
fmt.Fprintln(w, " Version:\t", p.Version)
|
fmt.Fprintln(w, " Version:\t", p.Version)
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package text
|
package imgs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
58
imgbom/protocol.go
Normal file
58
imgbom/protocol.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package imgbom
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// Potentially consider moving this out into a generic package that parses user input.
|
||||||
|
// Aside from scope, this is the 2nd package that looks at a string to parse the input
|
||||||
|
// and return an Option type.
|
||||||
|
|
||||||
|
const (
|
||||||
|
UnknownProtocol ProtocolType = iota
|
||||||
|
ImageProtocol
|
||||||
|
DirProtocol
|
||||||
|
)
|
||||||
|
|
||||||
|
var optionStr = []string{
|
||||||
|
"UnknownProtocol",
|
||||||
|
"image",
|
||||||
|
"dir",
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProtocolType int
|
||||||
|
|
||||||
|
type Protocol struct {
|
||||||
|
Type ProtocolType
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProtocol(userStr string) Protocol {
|
||||||
|
candidates := strings.Split(userStr, "://")
|
||||||
|
|
||||||
|
switch len(candidates) {
|
||||||
|
case 2:
|
||||||
|
if strings.HasPrefix(userStr, "dir://") {
|
||||||
|
return Protocol{
|
||||||
|
Type: DirProtocol,
|
||||||
|
Value: strings.TrimPrefix(userStr, "dir://"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// default to an Image for anything else since stereoscope can handle this
|
||||||
|
return Protocol{
|
||||||
|
Type: ImageProtocol,
|
||||||
|
Value: userStr,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return Protocol{
|
||||||
|
Type: ImageProtocol,
|
||||||
|
Value: userStr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o ProtocolType) String() string {
|
||||||
|
if int(o) >= len(optionStr) || o < 0 {
|
||||||
|
return optionStr[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return optionStr[o]
|
||||||
|
}
|
||||||
43
imgbom/protocol_test.go
Normal file
43
imgbom/protocol_test.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package imgbom
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestNewProtocol(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
input string
|
||||||
|
expType ProtocolType
|
||||||
|
expValue string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "directory protocol",
|
||||||
|
input: "dir:///opt/",
|
||||||
|
expType: DirProtocol,
|
||||||
|
expValue: "/opt/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "unknown protocol",
|
||||||
|
input: "s4:///opt/",
|
||||||
|
expType: ImageProtocol,
|
||||||
|
expValue: "s4:///opt/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "docker protocol",
|
||||||
|
input: "docker://ubuntu:20.04",
|
||||||
|
expType: ImageProtocol,
|
||||||
|
expValue: "docker://ubuntu:20.04",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
p := NewProtocol(test.input)
|
||||||
|
if p.Type != test.expType {
|
||||||
|
t.Errorf("mismatched type in protocol: '%v' != '%v'", p.Type, test.expType)
|
||||||
|
}
|
||||||
|
if p.Value != test.expValue {
|
||||||
|
t.Errorf("mismatched protocol value: '%s' != '%s'", p.Value, test.expValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,6 +8,15 @@ import (
|
|||||||
"github.com/anchore/stereoscope/pkg/image"
|
"github.com/anchore/stereoscope/pkg/image"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type FileContentResolver interface {
|
||||||
|
ContentResolver
|
||||||
|
FileResolver
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContentResolver interface {
|
||||||
|
MultipleFileContentsByRef(f ...file.Reference) (map[file.Reference]string, error)
|
||||||
|
}
|
||||||
|
|
||||||
type FileResolver interface {
|
type FileResolver interface {
|
||||||
FilesByPath(paths ...file.Path) ([]file.Reference, error)
|
FilesByPath(paths ...file.Path) ([]file.Reference, error)
|
||||||
FilesByGlob(patterns ...string) ([]file.Reference, error)
|
FilesByGlob(patterns ...string) ([]file.Reference, error)
|
||||||
|
|||||||
@ -2,38 +2,127 @@ package scope
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/anchore/imgbom/internal/log"
|
||||||
"github.com/anchore/stereoscope/pkg/file"
|
"github.com/anchore/stereoscope/pkg/file"
|
||||||
"github.com/anchore/stereoscope/pkg/image"
|
"github.com/anchore/stereoscope/pkg/image"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Scope struct {
|
type DirectoryScope struct {
|
||||||
|
Option Option
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s DirectoryScope) String() string {
|
||||||
|
return fmt.Sprintf("dir://%s", s.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s DirectoryScope) FilesByPath(userPaths ...file.Path) ([]file.Reference, error) {
|
||||||
|
var references = make([]file.Reference, 0)
|
||||||
|
|
||||||
|
for _, userPath := range userPaths {
|
||||||
|
resolvedPath := path.Join(s.Path, string(userPath))
|
||||||
|
_, err := os.Stat(resolvedPath)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
|
log.Errorf("path (%s) is not valid: %v", resolvedPath, err)
|
||||||
|
}
|
||||||
|
filePath := file.Path(resolvedPath)
|
||||||
|
references = append(references, file.NewFileReference(filePath))
|
||||||
|
}
|
||||||
|
|
||||||
|
return references, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileContents(path file.Path) ([]byte, error) {
|
||||||
|
contents, err := ioutil.ReadFile(string(path))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return contents, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s DirectoryScope) FilesByGlob(patterns ...string) ([]file.Reference, error) {
|
||||||
|
result := make([]file.Reference, 0)
|
||||||
|
|
||||||
|
for _, pattern := range patterns {
|
||||||
|
pathPattern := path.Join(s.Path, pattern)
|
||||||
|
matches, err := filepath.Glob(pathPattern)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
for _, match := range matches {
|
||||||
|
fileMeta, err := os.Stat(match)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if fileMeta.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
matchedPath := file.Path(match)
|
||||||
|
result = append(result, file.NewFileReference(matchedPath))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s DirectoryScope) MultipleFileContentsByRef(f ...file.Reference) (map[file.Reference]string, error) {
|
||||||
|
refContents := make(map[file.Reference]string)
|
||||||
|
for _, fileRef := range f {
|
||||||
|
contents, err := fileContents(fileRef.Path)
|
||||||
|
if err != nil {
|
||||||
|
return refContents, fmt.Errorf("could not read contents of file: %s", fileRef.Path)
|
||||||
|
}
|
||||||
|
refContents[fileRef] = string(contents)
|
||||||
|
}
|
||||||
|
return refContents, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImageScope struct {
|
||||||
Option Option
|
Option Option
|
||||||
resolver FileResolver
|
resolver FileResolver
|
||||||
Image *image.Image
|
Image *image.Image
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewScope(img *image.Image, option Option) (Scope, error) {
|
func NewDirScope(path string, option Option) (DirectoryScope, error) {
|
||||||
|
return DirectoryScope{
|
||||||
|
Option: option,
|
||||||
|
Path: path,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewImageScope(img *image.Image, option Option) (ImageScope, error) {
|
||||||
if img == nil {
|
if img == nil {
|
||||||
return Scope{}, fmt.Errorf("no image given")
|
return ImageScope{}, fmt.Errorf("no image given")
|
||||||
}
|
}
|
||||||
|
|
||||||
resolver, err := getFileResolver(img, option)
|
resolver, err := getFileResolver(img, option)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Scope{}, fmt.Errorf("could not determine file resolver: %w", err)
|
return ImageScope{}, fmt.Errorf("could not determine file resolver: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scope{
|
return ImageScope{
|
||||||
Option: option,
|
Option: option,
|
||||||
resolver: resolver,
|
resolver: resolver,
|
||||||
Image: img,
|
Image: img,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Scope) FilesByPath(paths ...file.Path) ([]file.Reference, error) {
|
func (s ImageScope) FilesByPath(paths ...file.Path) ([]file.Reference, error) {
|
||||||
return s.resolver.FilesByPath(paths...)
|
return s.resolver.FilesByPath(paths...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Scope) FilesByGlob(patterns ...string) ([]file.Reference, error) {
|
func (s ImageScope) FilesByGlob(patterns ...string) ([]file.Reference, error) {
|
||||||
return s.resolver.FilesByGlob(patterns...)
|
return s.resolver.FilesByGlob(patterns...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s ImageScope) MultipleFileContentsByRef(f ...file.Reference) (map[file.Reference]string, error) {
|
||||||
|
return s.Image.MultipleFileContentsByRef(f...)
|
||||||
|
}
|
||||||
|
|||||||
147
imgbom/scope/scope_test.go
Normal file
147
imgbom/scope/scope_test.go
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
package scope
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/anchore/stereoscope/pkg/file"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDirectoryScope(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
input string
|
||||||
|
expString string
|
||||||
|
inputPaths []file.Path
|
||||||
|
expRefs int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "no paths exist",
|
||||||
|
input: "foobar/",
|
||||||
|
expString: "dir://foobar/",
|
||||||
|
inputPaths: []file.Path{file.Path("/opt/"), file.Path("/other")},
|
||||||
|
expRefs: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "path detected",
|
||||||
|
input: "test-fixtures",
|
||||||
|
expString: "dir://test-fixtures",
|
||||||
|
inputPaths: []file.Path{file.Path("path-detected")},
|
||||||
|
expRefs: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no files-by-path detected",
|
||||||
|
input: "test-fixtures",
|
||||||
|
expString: "dir://test-fixtures",
|
||||||
|
inputPaths: []file.Path{file.Path("no-path-detected")},
|
||||||
|
expRefs: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
p, err := NewDirScope(test.input, AllLayersScope)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not create NewDirScope: %w", err)
|
||||||
|
}
|
||||||
|
if p.String() != test.expString {
|
||||||
|
t.Errorf("mismatched stringer: '%s' != '%s'", p.String(), test.expString)
|
||||||
|
}
|
||||||
|
|
||||||
|
refs, err := p.FilesByPath(test.inputPaths...)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("FilesByPath call produced an error: %w", err)
|
||||||
|
}
|
||||||
|
if len(refs) != test.expRefs {
|
||||||
|
t.Errorf("unexpected number of refs returned: %d != %d", len(refs), test.expRefs)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultipleFileContentsByRef(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
input string
|
||||||
|
path string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: "test-fixtures/path-detected",
|
||||||
|
desc: "empty file",
|
||||||
|
path: "empty",
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "test-fixtures/path-detected",
|
||||||
|
desc: "path does not exist",
|
||||||
|
path: "foo",
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "test-fixtures/path-detected",
|
||||||
|
desc: "file has contents",
|
||||||
|
path: "test-fixtures/path-detected/.vimrc",
|
||||||
|
expected: "\" A .vimrc file\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
p, err := NewDirScope(test.input, AllLayersScope)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not create NewDirScope: %w", err)
|
||||||
|
}
|
||||||
|
ref := file.NewFileReference(file.Path(test.path))
|
||||||
|
contents, err := p.MultipleFileContentsByRef(ref)
|
||||||
|
content := contents[ref]
|
||||||
|
|
||||||
|
if content != test.expected {
|
||||||
|
t.Errorf("unexpected contents from file: '%s' != '%s'", content, test.expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilesByGlob(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
input string
|
||||||
|
glob string
|
||||||
|
expected int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: "test-fixtures",
|
||||||
|
desc: "no matches",
|
||||||
|
glob: "bar/foo",
|
||||||
|
expected: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "test-fixtures/path-detected",
|
||||||
|
desc: "a single match",
|
||||||
|
glob: "*vimrc",
|
||||||
|
expected: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "test-fixtures/path-detected",
|
||||||
|
desc: "multiple matches",
|
||||||
|
glob: "*",
|
||||||
|
expected: 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
p, err := NewDirScope(test.input, AllLayersScope)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not create NewDirScope: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
contents, err := p.FilesByGlob(test.glob)
|
||||||
|
|
||||||
|
if len(contents) != test.expected {
|
||||||
|
t.Errorf("unexpected number of files found by glob (%s): %d != %d", test.glob, len(contents), test.expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
1
imgbom/scope/test-fixtures/path-detected/.vimrc
Normal file
1
imgbom/scope/test-fixtures/path-detected/.vimrc
Normal file
@ -0,0 +1 @@
|
|||||||
|
" A .vimrc file
|
||||||
0
imgbom/scope/test-fixtures/path-detected/empty
Normal file
0
imgbom/scope/test-fixtures/path-detected/empty
Normal file
77
integration/dir_presenters_test.go
Normal file
77
integration/dir_presenters_test.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/anchore/go-testutils"
|
||||||
|
"github.com/anchore/imgbom/imgbom"
|
||||||
|
"github.com/anchore/imgbom/imgbom/presenter"
|
||||||
|
"github.com/anchore/imgbom/imgbom/scope"
|
||||||
|
"github.com/sergi/go-diff/diffmatchpatch"
|
||||||
|
)
|
||||||
|
|
||||||
|
var update = flag.Bool("update", false, "update the *.golden files for json presenters")
|
||||||
|
|
||||||
|
func TestDirTextPresenter(t *testing.T) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
protocol := imgbom.NewProtocol("dir://test-fixtures")
|
||||||
|
if protocol.Type != imgbom.DirProtocol {
|
||||||
|
t.Errorf("unexpected protocol returned: %v != %v", protocol.Type, imgbom.DirProtocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
catalog, err := imgbom.CatalogDir(protocol.Value, scope.AllLayersScope)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not produce catalog: %w", err)
|
||||||
|
}
|
||||||
|
presenterOpt := presenter.ParseOption("text")
|
||||||
|
dirPresenter := presenter.GetDirPresenter(presenterOpt, protocol.Value, catalog)
|
||||||
|
|
||||||
|
dirPresenter.Present(&buffer)
|
||||||
|
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 TestDirJsonPresenter(t *testing.T) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
protocol := imgbom.NewProtocol("dir://test-fixtures")
|
||||||
|
if protocol.Type != imgbom.DirProtocol {
|
||||||
|
t.Errorf("unexpected protocol returned: %v != %v", protocol.Type, imgbom.DirProtocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
catalog, err := imgbom.CatalogDir(protocol.Value, scope.AllLayersScope)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not produce catalog: %w", err)
|
||||||
|
}
|
||||||
|
presenterOpt := presenter.ParseOption("json")
|
||||||
|
dirPresenter := presenter.GetDirPresenter(presenterOpt, protocol.Value, catalog)
|
||||||
|
|
||||||
|
dirPresenter.Present(&buffer)
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -6,7 +6,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/anchore/go-testutils"
|
"github.com/anchore/go-testutils"
|
||||||
"github.com/anchore/imgbom/imgbom"
|
|
||||||
"github.com/anchore/imgbom/imgbom/cataloger"
|
"github.com/anchore/imgbom/imgbom/cataloger"
|
||||||
"github.com/anchore/imgbom/imgbom/pkg"
|
"github.com/anchore/imgbom/imgbom/pkg"
|
||||||
"github.com/anchore/imgbom/imgbom/scope"
|
"github.com/anchore/imgbom/imgbom/scope"
|
||||||
@ -16,7 +15,8 @@ func TestLanguageImage(t *testing.T) {
|
|||||||
img, cleanup := testutils.GetFixtureImage(t, "docker-archive", "image-language-pkgs")
|
img, cleanup := testutils.GetFixtureImage(t, "docker-archive", "image-language-pkgs")
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
catalog, err := imgbom.CatalogImage(img, scope.AllLayersScope)
|
s, err := scope.NewImageScope(img, scope.AllLayersScope)
|
||||||
|
catalog, err := cataloger.Catalog(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to catalog image: %+v", err)
|
t.Fatalf("failed to catalog image: %+v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
256
integration/test-fixtures/snapshot/TestDirTextPresenter.golden
Normal file
256
integration/test-fixtures/snapshot/TestDirTextPresenter.golden
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
[Path: test-fixtures]
|
||||||
|
[actionmailer]
|
||||||
|
Version: 4.1.1
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[actionpack]
|
||||||
|
Version: 4.1.1
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[actionview]
|
||||||
|
Version: 4.1.1
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[activemodel]
|
||||||
|
Version: 4.1.1
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[activerecord]
|
||||||
|
Version: 4.1.1
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[activesupport]
|
||||||
|
Version: 4.1.1
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[arel]
|
||||||
|
Version: 5.0.1.20140414130214
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[bootstrap-sass]
|
||||||
|
Version: 3.1.1.1
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[builder]
|
||||||
|
Version: 3.2.2
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[coffee-rails]
|
||||||
|
Version: 4.0.1
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[coffee-script]
|
||||||
|
Version: 2.2.0
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[coffee-script-source]
|
||||||
|
Version: 1.7.0
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[erubis]
|
||||||
|
Version: 2.7.0
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[execjs]
|
||||||
|
Version: 2.0.2
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[hike]
|
||||||
|
Version: 1.2.3
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[i18n]
|
||||||
|
Version: 0.6.9
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[jbuilder]
|
||||||
|
Version: 2.0.7
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[jquery-rails]
|
||||||
|
Version: 3.1.0
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[json]
|
||||||
|
Version: 1.8.1
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[kgio]
|
||||||
|
Version: 2.9.2
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[libv8]
|
||||||
|
Version: 3.16.14.3
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[mail]
|
||||||
|
Version: 2.5.4
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[mime-types]
|
||||||
|
Version: 1.25.1
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[minitest]
|
||||||
|
Version: 5.3.4
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[multi_json]
|
||||||
|
Version: 1.10.1
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[mysql2]
|
||||||
|
Version: 0.3.16
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[polyglot]
|
||||||
|
Version: 0.3.4
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[rack]
|
||||||
|
Version: 1.5.2
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[rack-test]
|
||||||
|
Version: 0.6.2
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[rails]
|
||||||
|
Version: 4.1.1
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[railties]
|
||||||
|
Version: 4.1.1
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[raindrops]
|
||||||
|
Version: 0.13.0
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[rake]
|
||||||
|
Version: 10.3.2
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[rdoc]
|
||||||
|
Version: 4.1.1
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[ref]
|
||||||
|
Version: 1.0.5
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[sass]
|
||||||
|
Version: 3.2.19
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[sass-rails]
|
||||||
|
Version: 4.0.3
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[sdoc]
|
||||||
|
Version: 0.4.0
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[spring]
|
||||||
|
Version: 1.1.3
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[sprockets]
|
||||||
|
Version: 2.11.0
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[sprockets-rails]
|
||||||
|
Version: 2.1.3
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[sqlite3]
|
||||||
|
Version: 1.3.9
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[therubyracer]
|
||||||
|
Version: 0.12.1
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[thor]
|
||||||
|
Version: 0.19.1
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[thread_safe]
|
||||||
|
Version: 0.3.3
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[tilt]
|
||||||
|
Version: 1.4.1
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[treetop]
|
||||||
|
Version: 1.4.15
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[turbolinks]
|
||||||
|
Version: 2.2.2
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[tzinfo]
|
||||||
|
Version: 1.2.0
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[uglifier]
|
||||||
|
Version: 2.5.0
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
|
[unicorn]
|
||||||
|
Version: 4.8.3
|
||||||
|
Type: bundle
|
||||||
|
Found by: bundler-cataloger
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user