diff --git a/cmd/root.go b/cmd/root.go index b6705775d..c86f8e82b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -68,5 +68,6 @@ func doRunCmd(cmd *cobra.Command, args []string) int { log.Errorf("could not format catalog results: %w", err) return 1 } + return 0 } diff --git a/go.mod b/go.mod index 100c637d3..0cdce0b6d 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/anchore/stereoscope v0.0.0-20200523232006-be5f3c18958f github.com/go-test/deep v1.0.6 github.com/google/go-containerregistry v0.0.0-20200521151920-a873a21aff23 // indirect + github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 // indirect github.com/hashicorp/go-multierror v1.1.0 github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/mapstructure v1.3.1 @@ -16,6 +17,6 @@ require ( github.com/spf13/viper v1.7.0 go.uber.org/zap v1.15.0 golang.org/x/sys v0.0.0-20200523222454-059865788121 // indirect - google.golang.org/genproto v0.0.0-20200521103424-e9a78aa275b7 // indirect + google.golang.org/protobuf v1.24.0 // indirect gopkg.in/yaml.v2 v2.3.0 ) diff --git a/go.sum b/go.sum index 31fe0e62b..ed50c7c70 100644 --- a/go.sum +++ b/go.sum @@ -210,6 +210,8 @@ github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTV github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKpxb/jFExr4HGq6on2dEOmnL6FV+fgPw= +github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -628,8 +630,8 @@ google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84 h1:pSLkPbrjnPyLDYU google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200519141106-08726f379972 h1:6ydLqG65DIMNJf6p97WudGsmd1w3Ickm/LiZnBrREPI= google.golang.org/genproto v0.0.0-20200519141106-08726f379972/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200521103424-e9a78aa275b7 h1:JUs1uIDQ46c7iI0QuMPzAHqXaSmqKF0f9freFMk2ivs= -google.golang.org/genproto v0.0.0-20200521103424-e9a78aa275b7/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -650,6 +652,9 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/imgbom/analyzer/controller.go b/imgbom/analyzer/controller.go index ed9c4c99f..31edb2829 100644 --- a/imgbom/analyzer/controller.go +++ b/imgbom/analyzer/controller.go @@ -18,7 +18,7 @@ func init() { controllerInstance.add(dpkg.NewAnalyzer()) } -func Analyze(s scope.Scope) (pkg.Catalog, error) { +func Analyze(s scope.Scope) (*pkg.Catalog, error) { return controllerInstance.analyze(s) } @@ -31,7 +31,7 @@ func (c *controller) add(a Analyzer) { c.analyzers = append(c.analyzers, a) } -func (c *controller) analyze(s scope.Scope) (pkg.Catalog, error) { +func (c *controller) analyze(s scope.Scope) (*pkg.Catalog, error) { catalog := pkg.NewCatalog() fileSelection := make([]file.Reference, 0) @@ -44,7 +44,7 @@ func (c *controller) analyze(s scope.Scope) (pkg.Catalog, error) { // fetch contents for requested selection by analyzers contents, err := s.Image.MultipleFileContentsByRef(fileSelection...) if err != nil { - return pkg.Catalog{}, err + return nil, err } // perform analysis, accumulating errors for each failed analysis @@ -65,7 +65,7 @@ func (c *controller) analyze(s scope.Scope) (pkg.Catalog, error) { } if errs != nil { - return pkg.Catalog{}, errs + return nil, errs } return catalog, nil diff --git a/imgbom/analyzer/dpkg/parser.go b/imgbom/analyzer/dpkg/parser.go index c1442b453..9fbc7e3d6 100644 --- a/imgbom/analyzer/dpkg/parser.go +++ b/imgbom/analyzer/dpkg/parser.go @@ -10,7 +10,7 @@ import ( "github.com/mitchellh/mapstructure" ) -var endOfPackages = fmt.Errorf("no more packages to read") +var errEndOfPackages = fmt.Errorf("no more packages to read") func ParseEntries(reader io.Reader) ([]pkg.DpkgMetadata, error) { buffedReader := bufio.NewReader(reader) @@ -19,7 +19,7 @@ func ParseEntries(reader io.Reader) ([]pkg.DpkgMetadata, error) { for { entry, err := parseEntry(buffedReader) if err != nil { - if err == endOfPackages { + if err == errEndOfPackages { break } return nil, err @@ -38,7 +38,7 @@ func parseEntry(reader *bufio.Reader) (entry pkg.DpkgMetadata, err error) { line, err := reader.ReadString('\n') if err != nil { if err == io.EOF { - return pkg.DpkgMetadata{}, endOfPackages + return pkg.DpkgMetadata{}, errEndOfPackages } return pkg.DpkgMetadata{}, err } diff --git a/imgbom/catalog_image.go b/imgbom/catalog_image.go index 09257c4e0..458b3594a 100644 --- a/imgbom/catalog_image.go +++ b/imgbom/catalog_image.go @@ -8,10 +8,10 @@ import ( ) // TODO: add os detection results as return value -func CatalogImage(img *image.Image, o scope.Option) (pkg.Catalog, error) { +func CatalogImage(img *image.Image, o scope.Option) (*pkg.Catalog, error) { s, err := scope.NewScope(img, o) if err != nil { - return pkg.Catalog{}, err + return nil, err } // TODO: add OS detection here... diff --git a/imgbom/pkg/catalog.go b/imgbom/pkg/catalog.go index 159a8fef1..c020a2fae 100644 --- a/imgbom/pkg/catalog.go +++ b/imgbom/pkg/catalog.go @@ -1,31 +1,75 @@ package pkg +import ( + "sync" + + "github.com/anchore/imgbom/internal/log" + "github.com/anchore/stereoscope/pkg/file" +) + // TODO: add reader methods (by type, id, fuzzy search, etc) +var nextPackageID int64 + type Catalog struct { - // TODO: catalog by package ID for potential indexing - packages map[Type][]Package + byID map[ID]*Package + byType map[Type][]*Package + byFile map[file.Reference][]*Package + lock sync.RWMutex } -func NewCatalog() Catalog { - return Catalog{ - packages: make(map[Type][]Package), +func NewCatalog() *Catalog { + return &Catalog{ + byID: make(map[ID]*Package), + byType: make(map[Type][]*Package), + byFile: make(map[file.Reference][]*Package), } } +func (c *Catalog) Package(id ID) *Package { + return c.byID[id] +} + +func (c *Catalog) PackagesByFile(ref file.Reference) []*Package { + return c.byFile[ref] +} + func (c *Catalog) Add(p Package) { - _, ok := c.packages[p.Type] - if !ok { - c.packages[p.Type] = make([]Package, 0) + if p.id != 0 { + log.Errorf("package already added to catalog: %s", p) + return + } + c.lock.Lock() + defer c.lock.Unlock() + + p.id = ID(nextPackageID) + nextPackageID++ + + // store by package ID + c.byID[p.id] = &p + + // store by package type + _, ok := c.byType[p.Type] + if !ok { + c.byType[p.Type] = make([]*Package, 0) + } + c.byType[p.Type] = append(c.byType[p.Type], &p) + + // store by file references + for _, s := range p.Source { + _, ok := c.byFile[s] + if !ok { + c.byFile[s] = make([]*Package, 0) + } + c.byFile[s] = append(c.byFile[s], &p) } - c.packages[p.Type] = append(c.packages[p.Type], p) } -func (c *Catalog) Enumerate(types ...Type) <-chan Package { - channel := make(chan Package) +func (c *Catalog) Enumerate(types ...Type) <-chan *Package { + channel := make(chan *Package) go func() { defer close(channel) - for ty, packages := range c.packages { + for ty, packages := range c.byType { if len(types) != 0 { found := false typeCheck: diff --git a/imgbom/pkg/package.go b/imgbom/pkg/package.go index fc1dbb030..95594312d 100644 --- a/imgbom/pkg/package.go +++ b/imgbom/pkg/package.go @@ -1,11 +1,16 @@ package pkg -import "github.com/anchore/stereoscope/pkg/file" +import ( + "fmt" -// TODO: add package ID (random/incremental) + "github.com/anchore/stereoscope/pkg/file" +) + +type ID int64 // TODO: add field to trace which analyzer detected this type Package struct { + id ID Name string Version string Source []file.Reference @@ -14,4 +19,10 @@ type Package struct { Metadata interface{} } -// TODO: stringer... +func (p Package) ID() ID { + return p.id +} + +func (p Package) String() string { + return fmt.Sprintf("Pkg(type=%s, name=%s, version=%s)", p.Type, p.Name, p.Version) +} diff --git a/imgbom/pkg/type.go b/imgbom/pkg/type.go index 69ae13f45..761e1ee22 100644 --- a/imgbom/pkg/type.go +++ b/imgbom/pkg/type.go @@ -5,7 +5,7 @@ const ( ApkPkg DebPkg JavaPkg - NodePkg + JavaScriptPkg PacmanPkg PythonPkg RpmPkg @@ -19,7 +19,7 @@ var typeStr = []string{ "apk", "deb", "java", - "node", + "javascript", "pacman", "python", "rpm", diff --git a/imgbom/presenter/json/presenter.go b/imgbom/presenter/json/presenter.go index ddd3f35db..d9813c3ac 100644 --- a/imgbom/presenter/json/presenter.go +++ b/imgbom/presenter/json/presenter.go @@ -49,7 +49,7 @@ type artifact struct { Metadata interface{} `json:"metadata"` } -func (pres *Presenter) Present(output io.Writer, img *stereoscopeImg.Image, catalog pkg.Catalog) error { +func (pres *Presenter) Present(output io.Writer, img *stereoscopeImg.Image, catalog *pkg.Catalog) error { tags := make([]string, len(img.Metadata.Tags)) for idx, tag := range img.Metadata.Tags { tags[idx] = tag.String() diff --git a/imgbom/presenter/option.go b/imgbom/presenter/option.go index 10e74fc50..354e84754 100644 --- a/imgbom/presenter/option.go +++ b/imgbom/presenter/option.go @@ -16,7 +16,7 @@ var Options = []Option{ JSONPresenter, } -type Option uint +type Option int func ParseOption(userStr string) Option { switch strings.ToLower(userStr) { @@ -28,7 +28,7 @@ func ParseOption(userStr string) Option { } func (o Option) String() string { - if int(o) >= len(optionStr) { + if int(o) >= len(optionStr) || o < 0 { return optionStr[0] } diff --git a/imgbom/presenter/presenter.go b/imgbom/presenter/presenter.go index caaca26ab..ed2eab414 100644 --- a/imgbom/presenter/presenter.go +++ b/imgbom/presenter/presenter.go @@ -9,7 +9,7 @@ import ( ) type Presenter interface { - Present(io.Writer, *image.Image, pkg.Catalog) error + Present(io.Writer, *image.Image, *pkg.Catalog) error } func GetPresenter(option Option) Presenter { diff --git a/imgbom/scope/option.go b/imgbom/scope/option.go index 43d9cfae3..b67363039 100644 --- a/imgbom/scope/option.go +++ b/imgbom/scope/option.go @@ -8,7 +8,7 @@ const ( AllLayersScope ) -type Option uint +type Option int var optionStr = []string{ "UnknownScope", @@ -32,7 +32,7 @@ func ParseOption(userStr string) Option { } func (o Option) String() string { - if int(o) >= len(optionStr) { + if int(o) >= len(optionStr) || o < 0 { return optionStr[0] }