Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 82 additions & 18 deletions syft/pkg/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ loopNewIDs:
}
}

func (s *orderedIDSet) delete(id artifact.ID) {
for i, existingID := range s.slice {
if existingID == id {
s.slice = append(s.slice[:i], s.slice[i+1:]...)
return
}
}
}

// Catalog represents a collection of Packages.
type Catalog struct {
byID map[artifact.ID]Package
Expand Down Expand Up @@ -92,30 +101,32 @@ func (c *Catalog) Packages(ids []artifact.ID) (result []Package) {
return result
}

// Add a package to the Catalog.
func (c *Catalog) Add(p Package) {
// Add n packages to the catalog.
func (c *Catalog) Add(pkgs ...Package) {
c.lock.Lock()
defer c.lock.Unlock()

id := p.ID()
if id == "" {
log.Warnf("found package with empty ID while adding to the catalog: %+v", p)
p.SetID()
id = p.ID()
}
for _, p := range pkgs {
id := p.ID()
if id == "" {
log.Warnf("found package with empty ID while adding to the catalog: %+v", p)
p.SetID()
id = p.ID()
}

if existing, exists := c.byID[id]; exists {
// there is already a package with this fingerprint merge the existing record with the new one
if err := existing.merge(p); err != nil {
log.Warnf("failed to merge packages: %+v", err)
} else {
c.byID[id] = existing
c.addPathsToIndex(p)
if existing, exists := c.byID[id]; exists {
// there is already a package with this fingerprint merge the existing record with the new one
if err := existing.merge(p); err != nil {
log.Warnf("failed to merge packages: %+v", err)
} else {
c.byID[id] = existing
c.addPathsToIndex(p)
}
return
}
return
}

c.addToIndex(p)
c.addToIndex(p)
}
}

func (c *Catalog) addToIndex(p Package) {
Expand Down Expand Up @@ -157,6 +168,59 @@ func (c *Catalog) addPathToIndex(id artifact.ID, path string) {
c.idsByPath[path] = pathIndex
}

func (c *Catalog) Delete(ids ...artifact.ID) {
c.lock.Lock()
defer c.lock.Unlock()

for _, id := range ids {
p, exists := c.byID[id]
if !exists {
return
}

delete(c.byID, id)
c.deleteNameFromIndex(p)
c.deleteTypeFromIndex(p)
c.deletePathsFromIndex(p)
}
}

func (c *Catalog) deleteNameFromIndex(p Package) {
nameIndex := c.idsByName[p.Name]
nameIndex.delete(p.id)
c.idsByName[p.Name] = nameIndex
}

func (c *Catalog) deleteTypeFromIndex(p Package) {
typeIndex := c.idsByType[p.Type]
typeIndex.delete(p.id)
c.idsByType[p.Type] = typeIndex
}

func (c *Catalog) deletePathsFromIndex(p Package) {
observedPaths := internal.NewStringSet()
for _, l := range p.Locations.ToSlice() {
if l.RealPath != "" && !observedPaths.Contains(l.RealPath) {
c.deletePathFromIndex(p.id, l.RealPath)
observedPaths.Add(l.RealPath)
}
if l.VirtualPath != "" && l.RealPath != l.VirtualPath && !observedPaths.Contains(l.VirtualPath) {
c.deletePathFromIndex(p.id, l.VirtualPath)
observedPaths.Add(l.VirtualPath)
}
}
}

func (c *Catalog) deletePathFromIndex(id artifact.ID, path string) {
pathIndex := c.idsByPath[path]
pathIndex.delete(id)
if len(pathIndex.slice) == 0 {
delete(c.idsByPath, path)
} else {
c.idsByPath[path] = pathIndex
}
}

// 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)
Expand Down
142 changes: 142 additions & 0 deletions syft/pkg/catalog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,148 @@ type expectedIndexes struct {
byPath map[string]*strset.Set
}

func TestCatalogDeleteRemovesPackages(t *testing.T) {
tests := []struct {
name string
pkgs []Package
deleteIDs []artifact.ID
expectedIndexes expectedIndexes
}{
{
name: "delete one package",
pkgs: []Package{
{
id: "pkg:deb/debian/1",
Name: "debian",
Version: "1",
Type: DebPkg,
Locations: source.NewLocationSet(
source.NewVirtualLocation("/c/path", "/another/path1"),
),
},
{
id: "pkg:deb/debian/2",
Name: "debian",
Version: "2",
Type: DebPkg,
Locations: source.NewLocationSet(
source.NewVirtualLocation("/d/path", "/another/path2"),
),
},
},
deleteIDs: []artifact.ID{
artifact.ID("pkg:deb/debian/1"),
},
expectedIndexes: expectedIndexes{
byType: map[Type]*strset.Set{
DebPkg: strset.New("pkg:deb/debian/2"),
},
byPath: map[string]*strset.Set{
"/d/path": strset.New("pkg:deb/debian/2"),
"/another/path2": strset.New("pkg:deb/debian/2"),
},
},
},
{
name: "delete multiple packages",
pkgs: []Package{
{
id: "pkg:deb/debian/1",
Name: "debian",
Version: "1",
Type: DebPkg,
Locations: source.NewLocationSet(
source.NewVirtualLocation("/c/path", "/another/path1"),
),
},
{
id: "pkg:deb/debian/2",
Name: "debian",
Version: "2",
Type: DebPkg,
Locations: source.NewLocationSet(
source.NewVirtualLocation("/d/path", "/another/path2"),
),
},
{
id: "pkg:deb/debian/3",
Name: "debian",
Version: "3",
Type: DebPkg,
Locations: source.NewLocationSet(
source.NewVirtualLocation("/e/path", "/another/path3"),
),
},
},
deleteIDs: []artifact.ID{
artifact.ID("pkg:deb/debian/1"),
artifact.ID("pkg:deb/debian/3"),
},
expectedIndexes: expectedIndexes{
byType: map[Type]*strset.Set{
DebPkg: strset.New("pkg:deb/debian/2"),
},
byPath: map[string]*strset.Set{
"/d/path": strset.New("pkg:deb/debian/2"),
"/another/path2": strset.New("pkg:deb/debian/2"),
},
},
},
{
name: "delete non-existent package",
pkgs: []Package{
{
id: artifact.ID("pkg:deb/debian/1"),
Name: "debian",
Version: "1",
Type: DebPkg,
Locations: source.NewLocationSet(
source.NewVirtualLocation("/c/path", "/another/path1"),
),
},
{
id: artifact.ID("pkg:deb/debian/2"),
Name: "debian",
Version: "2",
Type: DebPkg,
Locations: source.NewLocationSet(
source.NewVirtualLocation("/d/path", "/another/path2"),
),
},
},
deleteIDs: []artifact.ID{
artifact.ID("pkg:deb/debian/3"),
},
expectedIndexes: expectedIndexes{
byType: map[Type]*strset.Set{
DebPkg: strset.New("pkg:deb/debian/1", "pkg:deb/debian/2"),
},
byPath: map[string]*strset.Set{
"/c/path": strset.New("pkg:deb/debian/1"),
"/another/path1": strset.New("pkg:deb/debian/1"),
"/d/path": strset.New("pkg:deb/debian/2"),
"/another/path2": strset.New("pkg:deb/debian/2"),
},
},
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c := NewCatalog()
for _, p := range test.pkgs {
c.Add(p)
}

for _, id := range test.deleteIDs {
c.Delete(id)
}

assertIndexes(t, c, test.expectedIndexes)
})
}
}

func TestCatalogAddPopulatesIndex(t *testing.T) {

var pkgs = []Package{
Expand Down