Skip to content

Commit

Permalink
feat: add catalog delete (#1377)
Browse files Browse the repository at this point in the history
  • Loading branch information
spiffcs authored Dec 12, 2022
1 parent 17aa828 commit 23a3173
Show file tree
Hide file tree
Showing 2 changed files with 224 additions and 18 deletions.
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

0 comments on commit 23a3173

Please sign in to comment.