github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/syft/pkg/catalog.go (about)

     1  package pkg
     2  
     3  import (
     4  	"sync"
     5  
     6  	"github.com/jinzhu/copier"
     7  
     8  	"github.com/anchore/syft/internal"
     9  	"github.com/anchore/syft/internal/log"
    10  	"github.com/anchore/syft/syft/artifact"
    11  )
    12  
    13  // Collection represents a collection of Packages.
    14  type Collection struct {
    15  	byID      map[artifact.ID]Package
    16  	idsByName map[string]orderedIDSet
    17  	idsByType map[Type]orderedIDSet
    18  	idsByPath map[string]orderedIDSet // note: this is real path or virtual path
    19  	lock      sync.RWMutex
    20  }
    21  
    22  // NewCollection returns a new empty Collection
    23  func NewCollection(pkgs ...Package) *Collection {
    24  	catalog := Collection{
    25  		byID:      make(map[artifact.ID]Package),
    26  		idsByName: make(map[string]orderedIDSet),
    27  		idsByType: make(map[Type]orderedIDSet),
    28  		idsByPath: make(map[string]orderedIDSet),
    29  	}
    30  
    31  	for _, p := range pkgs {
    32  		catalog.Add(p)
    33  	}
    34  
    35  	return &catalog
    36  }
    37  
    38  // PackageCount returns the total number of packages that have been added.
    39  func (c *Collection) PackageCount() int {
    40  	return len(c.byID)
    41  }
    42  
    43  // Package returns the package with the given ID.
    44  func (c *Collection) Package(id artifact.ID) *Package {
    45  	v, exists := c.byID[id]
    46  	if !exists {
    47  		return nil
    48  	}
    49  	var p Package
    50  	if err := copier.Copy(&p, &v); err != nil {
    51  		log.Warnf("unable to copy package id=%q name=%q: %+v", id, v.Name, err)
    52  		return nil
    53  	}
    54  	p.id = v.id
    55  	return &p
    56  }
    57  
    58  // PackagesByPath returns all packages that were discovered from the given path.
    59  func (c *Collection) PackagesByPath(path string) []Package {
    60  	return c.Packages(c.idsByPath[path].slice)
    61  }
    62  
    63  // PackagesByName returns all packages that were discovered with a matching name.
    64  func (c *Collection) PackagesByName(name string) []Package {
    65  	return c.Packages(c.idsByName[name].slice)
    66  }
    67  
    68  // Packages returns all packages for the given ID.
    69  func (c *Collection) Packages(ids []artifact.ID) (result []Package) {
    70  	for _, i := range ids {
    71  		p, exists := c.byID[i]
    72  		if exists {
    73  			result = append(result, p)
    74  		}
    75  	}
    76  	return result
    77  }
    78  
    79  // Add n packages to the catalog.
    80  func (c *Collection) Add(pkgs ...Package) {
    81  	c.lock.Lock()
    82  	defer c.lock.Unlock()
    83  
    84  	for _, p := range pkgs {
    85  		id := p.ID()
    86  		if id == "" {
    87  			log.Warnf("found package with empty ID while adding to the catalog: %+v", p)
    88  			p.SetID()
    89  			id = p.ID()
    90  		}
    91  
    92  		if existing, exists := c.byID[id]; exists {
    93  			// there is already a package with this fingerprint merge the existing record with the new one
    94  			if err := existing.merge(p); err != nil {
    95  				log.Warnf("failed to merge packages: %+v", err)
    96  			} else {
    97  				c.byID[id] = existing
    98  				c.addPathsToIndex(p)
    99  			}
   100  			return
   101  		}
   102  
   103  		c.addToIndex(p)
   104  	}
   105  }
   106  
   107  func (c *Collection) addToIndex(p Package) {
   108  	c.byID[p.id] = p
   109  	c.addNameToIndex(p)
   110  	c.addTypeToIndex(p)
   111  	c.addPathsToIndex(p)
   112  }
   113  
   114  func (c *Collection) addNameToIndex(p Package) {
   115  	nameIndex := c.idsByName[p.Name]
   116  	nameIndex.add(p.id)
   117  	c.idsByName[p.Name] = nameIndex
   118  }
   119  
   120  func (c *Collection) addTypeToIndex(p Package) {
   121  	typeIndex := c.idsByType[p.Type]
   122  	typeIndex.add(p.id)
   123  	c.idsByType[p.Type] = typeIndex
   124  }
   125  
   126  func (c *Collection) addPathsToIndex(p Package) {
   127  	observedPaths := internal.NewStringSet()
   128  	for _, l := range p.Locations.ToSlice() {
   129  		if l.RealPath != "" && !observedPaths.Contains(l.RealPath) {
   130  			c.addPathToIndex(p.id, l.RealPath)
   131  			observedPaths.Add(l.RealPath)
   132  		}
   133  		if l.VirtualPath != "" && l.RealPath != l.VirtualPath && !observedPaths.Contains(l.VirtualPath) {
   134  			c.addPathToIndex(p.id, l.VirtualPath)
   135  			observedPaths.Add(l.VirtualPath)
   136  		}
   137  	}
   138  }
   139  
   140  func (c *Collection) addPathToIndex(id artifact.ID, path string) {
   141  	pathIndex := c.idsByPath[path]
   142  	pathIndex.add(id)
   143  	c.idsByPath[path] = pathIndex
   144  }
   145  
   146  func (c *Collection) Delete(ids ...artifact.ID) {
   147  	c.lock.Lock()
   148  	defer c.lock.Unlock()
   149  
   150  	for _, id := range ids {
   151  		p, exists := c.byID[id]
   152  		if !exists {
   153  			return
   154  		}
   155  
   156  		delete(c.byID, id)
   157  		c.deleteNameFromIndex(p)
   158  		c.deleteTypeFromIndex(p)
   159  		c.deletePathsFromIndex(p)
   160  	}
   161  }
   162  
   163  func (c *Collection) deleteNameFromIndex(p Package) {
   164  	nameIndex := c.idsByName[p.Name]
   165  	nameIndex.delete(p.id)
   166  	c.idsByName[p.Name] = nameIndex
   167  }
   168  
   169  func (c *Collection) deleteTypeFromIndex(p Package) {
   170  	typeIndex := c.idsByType[p.Type]
   171  	typeIndex.delete(p.id)
   172  	c.idsByType[p.Type] = typeIndex
   173  }
   174  
   175  func (c *Collection) deletePathsFromIndex(p Package) {
   176  	observedPaths := internal.NewStringSet()
   177  	for _, l := range p.Locations.ToSlice() {
   178  		if l.RealPath != "" && !observedPaths.Contains(l.RealPath) {
   179  			c.deletePathFromIndex(p.id, l.RealPath)
   180  			observedPaths.Add(l.RealPath)
   181  		}
   182  		if l.VirtualPath != "" && l.RealPath != l.VirtualPath && !observedPaths.Contains(l.VirtualPath) {
   183  			c.deletePathFromIndex(p.id, l.VirtualPath)
   184  			observedPaths.Add(l.VirtualPath)
   185  		}
   186  	}
   187  }
   188  
   189  func (c *Collection) deletePathFromIndex(id artifact.ID, path string) {
   190  	pathIndex := c.idsByPath[path]
   191  	pathIndex.delete(id)
   192  	if len(pathIndex.slice) == 0 {
   193  		delete(c.idsByPath, path)
   194  	} else {
   195  		c.idsByPath[path] = pathIndex
   196  	}
   197  }
   198  
   199  // Enumerate all packages for the given type(s), enumerating all packages if no type is specified.
   200  func (c *Collection) Enumerate(types ...Type) <-chan Package {
   201  	channel := make(chan Package)
   202  	go func() {
   203  		defer close(channel)
   204  		if c == nil {
   205  			// we should allow enumerating from a catalog that was never created (which will result in no packages enumerated)
   206  			return
   207  		}
   208  		for ty, ids := range c.idsByType {
   209  			if len(types) != 0 {
   210  				found := false
   211  			typeCheck:
   212  				for _, t := range types {
   213  					if t == ty {
   214  						found = true
   215  						break typeCheck
   216  					}
   217  				}
   218  				if !found {
   219  					continue
   220  				}
   221  			}
   222  			for _, id := range ids.slice {
   223  				p := c.Package(id)
   224  				if p != nil {
   225  					channel <- *p
   226  				}
   227  			}
   228  		}
   229  	}()
   230  	return channel
   231  }
   232  
   233  // Sorted enumerates all packages for the given types sorted by package name. Enumerates all packages if no type
   234  // is specified.
   235  func (c *Collection) Sorted(types ...Type) (pkgs []Package) {
   236  	for p := range c.Enumerate(types...) {
   237  		pkgs = append(pkgs, p)
   238  	}
   239  
   240  	Sort(pkgs)
   241  
   242  	return pkgs
   243  }
   244  
   245  type orderedIDSet struct {
   246  	slice []artifact.ID
   247  }
   248  
   249  func (s *orderedIDSet) add(ids ...artifact.ID) {
   250  loopNewIDs:
   251  	for _, newID := range ids {
   252  		for _, existingID := range s.slice {
   253  			if existingID == newID {
   254  				continue loopNewIDs
   255  			}
   256  		}
   257  		s.slice = append(s.slice, newID)
   258  	}
   259  }
   260  
   261  func (s *orderedIDSet) delete(id artifact.ID) {
   262  	for i, existingID := range s.slice {
   263  		if existingID == id {
   264  			s.slice = append(s.slice[:i], s.slice[i+1:]...)
   265  			return
   266  		}
   267  	}
   268  }