mirror of
https://github.com/anchore/syft.git
synced 2025-11-18 17:03:17 +01:00
Also adds artifact location to sort key for Sorted() to ensure consistent sorts when artifacts of same name, version, and type are found in different locations in the image. Location should be sufficient since we assume only one package of a given name and version can exist in one location, even if that location is an package-db like rpmdb. Signed-off-by: Zach Hill <zach@anchore.com>
188 lines
4.1 KiB
Go
188 lines
4.1 KiB
Go
package pkg
|
|
|
|
import (
|
|
"sort"
|
|
"sync"
|
|
|
|
"github.com/anchore/syft/internal"
|
|
|
|
"github.com/anchore/syft/internal/log"
|
|
)
|
|
|
|
// Catalog represents a collection of Packages.
|
|
type Catalog struct {
|
|
byID map[ID]*Package
|
|
idsByType map[Type][]ID
|
|
idsByPath map[string][]ID // note: this is real path or virtual path
|
|
lock sync.RWMutex
|
|
}
|
|
|
|
// NewCatalog returns a new empty Catalog
|
|
func NewCatalog(pkgs ...Package) *Catalog {
|
|
catalog := Catalog{
|
|
byID: make(map[ID]*Package),
|
|
idsByType: make(map[Type][]ID),
|
|
idsByPath: make(map[string][]ID),
|
|
}
|
|
|
|
for _, p := range pkgs {
|
|
catalog.Add(p)
|
|
}
|
|
|
|
return &catalog
|
|
}
|
|
|
|
// PackageCount returns the total number of packages that have been added.
|
|
func (c *Catalog) PackageCount() int {
|
|
return len(c.byID)
|
|
}
|
|
|
|
// Package returns the package with the given ID.
|
|
func (c *Catalog) Package(id ID) *Package {
|
|
v, exists := c.byID[id]
|
|
if !exists {
|
|
return nil
|
|
}
|
|
return v
|
|
}
|
|
|
|
// PackagesByPath returns all packages that were discovered from the given path.
|
|
func (c *Catalog) PackagesByPath(path string) []*Package {
|
|
return c.Packages(c.idsByPath[path])
|
|
}
|
|
|
|
// Packages returns all packages for the given ID.
|
|
func (c *Catalog) Packages(ids []ID) (result []*Package) {
|
|
for _, i := range ids {
|
|
p, exists := c.byID[i]
|
|
if exists {
|
|
result = append(result, p)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Add a package to the Catalog.
|
|
func (c *Catalog) Add(p Package) {
|
|
c.lock.Lock()
|
|
defer c.lock.Unlock()
|
|
|
|
_, exists := c.byID[p.ID]
|
|
if exists {
|
|
log.Errorf("package ID already exists in the catalog : id=%+v %+v", p.ID, p)
|
|
return
|
|
}
|
|
|
|
if p.ID == "" {
|
|
p.ID = newID()
|
|
}
|
|
|
|
// store by package ID
|
|
c.byID[p.ID] = &p
|
|
|
|
// store by package type
|
|
c.idsByType[p.Type] = append(c.idsByType[p.Type], p.ID)
|
|
|
|
// store by file location paths
|
|
observedPaths := internal.NewStringSet()
|
|
for _, l := range p.Locations {
|
|
if l.RealPath != "" && !observedPaths.Contains(l.RealPath) {
|
|
c.idsByPath[l.RealPath] = append(c.idsByPath[l.RealPath], p.ID)
|
|
observedPaths.Add(l.RealPath)
|
|
}
|
|
if l.VirtualPath != "" && l.RealPath != l.VirtualPath && !observedPaths.Contains(l.VirtualPath) {
|
|
c.idsByPath[l.VirtualPath] = append(c.idsByPath[l.VirtualPath], p.ID)
|
|
observedPaths.Add(l.VirtualPath)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Catalog) Remove(id ID) {
|
|
c.lock.Lock()
|
|
defer c.lock.Unlock()
|
|
|
|
_, exists := c.byID[id]
|
|
if !exists {
|
|
log.Errorf("package ID does not exist in the catalog : id=%+v", id)
|
|
return
|
|
}
|
|
|
|
// Remove all index references to this package ID
|
|
for t, ids := range c.idsByType {
|
|
c.idsByType[t] = removeID(id, ids)
|
|
if len(c.idsByType[t]) == 0 {
|
|
delete(c.idsByType, t)
|
|
}
|
|
}
|
|
|
|
for p, ids := range c.idsByPath {
|
|
c.idsByPath[p] = removeID(id, ids)
|
|
if len(c.idsByPath[p]) == 0 {
|
|
delete(c.idsByPath, p)
|
|
}
|
|
}
|
|
|
|
// Remove package
|
|
delete(c.byID, id)
|
|
}
|
|
|
|
// Enumerate all packages for the given type(s), enumerating all packages if no type is specified.
|
|
func (c *Catalog) Enumerate(types ...Type) <-chan *Package {
|
|
channel := make(chan *Package)
|
|
go func() {
|
|
defer close(channel)
|
|
for ty, ids := range c.idsByType {
|
|
if len(types) != 0 {
|
|
found := false
|
|
typeCheck:
|
|
for _, t := range types {
|
|
if t == ty {
|
|
found = true
|
|
break typeCheck
|
|
}
|
|
}
|
|
if !found {
|
|
continue
|
|
}
|
|
}
|
|
for _, id := range ids {
|
|
channel <- c.Package(id)
|
|
}
|
|
}
|
|
}()
|
|
return channel
|
|
}
|
|
|
|
// Sorted enumerates all packages for the given types sorted by package name. Enumerates all packages if no type
|
|
// is specified.
|
|
func (c *Catalog) Sorted(types ...Type) []*Package {
|
|
pkgs := make([]*Package, 0)
|
|
for p := range c.Enumerate(types...) {
|
|
pkgs = append(pkgs, p)
|
|
}
|
|
|
|
sort.SliceStable(pkgs, func(i, j int) bool {
|
|
if pkgs[i].Name == pkgs[j].Name {
|
|
if pkgs[i].Version == pkgs[j].Version {
|
|
if pkgs[i].Type == pkgs[j].Type {
|
|
return pkgs[i].Locations[0].String() < pkgs[j].Locations[0].String()
|
|
}
|
|
return pkgs[i].Type < pkgs[j].Type
|
|
}
|
|
return pkgs[i].Version < pkgs[j].Version
|
|
}
|
|
return pkgs[i].Name < pkgs[j].Name
|
|
})
|
|
|
|
return pkgs
|
|
}
|
|
|
|
func removeID(id ID, target []ID) (result []ID) {
|
|
for _, value := range target {
|
|
if value != id {
|
|
result = append(result, value)
|
|
}
|
|
}
|
|
return result
|
|
}
|