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 }