mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 08:23:15 +01:00
* add policy for empty name and version Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * default stub version Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * modifying ids requires augmenting relationships Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
218 lines
6.2 KiB
Go
218 lines
6.2 KiB
Go
package relationship
|
|
|
|
import (
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/anchore/syft/syft/artifact"
|
|
"github.com/anchore/syft/syft/file"
|
|
)
|
|
|
|
// Index indexes relationships, preventing duplicates
|
|
type Index struct {
|
|
all []*sortableRelationship
|
|
fromID map[artifact.ID]*mappedRelationships
|
|
toID map[artifact.ID]*mappedRelationships
|
|
}
|
|
|
|
// NewIndex returns a new relationship Index
|
|
func NewIndex(relationships ...artifact.Relationship) *Index {
|
|
out := Index{
|
|
fromID: make(map[artifact.ID]*mappedRelationships),
|
|
toID: make(map[artifact.ID]*mappedRelationships),
|
|
}
|
|
out.Add(relationships...)
|
|
return &out
|
|
}
|
|
|
|
// Add adds all the given relationships to the index, without adding duplicates
|
|
func (i *Index) Add(relationships ...artifact.Relationship) {
|
|
// store appropriate indexes for stable ordering to minimize ID() calls
|
|
for _, r := range relationships {
|
|
// prevent duplicates
|
|
if i.Contains(r) {
|
|
continue
|
|
}
|
|
|
|
fromID := r.From.ID()
|
|
toID := r.To.ID()
|
|
|
|
relationship := &sortableRelationship{
|
|
from: fromID,
|
|
to: toID,
|
|
relationship: r,
|
|
}
|
|
|
|
// add to all relationships
|
|
i.all = append(i.all, relationship)
|
|
|
|
// add from -> to mapping
|
|
mapped := i.fromID[fromID]
|
|
if mapped == nil {
|
|
mapped = &mappedRelationships{}
|
|
i.fromID[fromID] = mapped
|
|
}
|
|
mapped.add(toID, relationship)
|
|
|
|
// add to -> from mapping
|
|
mapped = i.toID[toID]
|
|
if mapped == nil {
|
|
mapped = &mappedRelationships{}
|
|
i.toID[toID] = mapped
|
|
}
|
|
mapped.add(fromID, relationship)
|
|
}
|
|
}
|
|
|
|
func (i *Index) Remove(id artifact.ID) {
|
|
delete(i.fromID, id)
|
|
delete(i.toID, id)
|
|
|
|
for idx := 0; idx < len(i.all); {
|
|
if i.all[idx].from == id || i.all[idx].to == id {
|
|
i.all = append(i.all[:idx], i.all[idx+1:]...)
|
|
} else {
|
|
idx++
|
|
}
|
|
}
|
|
}
|
|
|
|
func (i *Index) Replace(ogID artifact.ID, replacement artifact.Identifiable) {
|
|
for _, mapped := range fromMappedByID(i.fromID, ogID) {
|
|
i.Add(artifact.Relationship{
|
|
From: replacement,
|
|
To: mapped.relationship.To,
|
|
Type: mapped.relationship.Type,
|
|
})
|
|
}
|
|
|
|
for _, mapped := range fromMappedByID(i.toID, ogID) {
|
|
i.Add(artifact.Relationship{
|
|
From: mapped.relationship.From,
|
|
To: replacement,
|
|
Type: mapped.relationship.Type,
|
|
})
|
|
}
|
|
|
|
i.Remove(ogID)
|
|
}
|
|
|
|
// From returns all relationships from the given identifiable, with specified types
|
|
func (i *Index) From(identifiable artifact.Identifiable, types ...artifact.RelationshipType) []artifact.Relationship {
|
|
return toSortedSlice(fromMapped(i.fromID, identifiable), types)
|
|
}
|
|
|
|
// To returns all relationships to the given identifiable, with specified types
|
|
func (i *Index) To(identifiable artifact.Identifiable, types ...artifact.RelationshipType) []artifact.Relationship {
|
|
return toSortedSlice(fromMapped(i.toID, identifiable), types)
|
|
}
|
|
|
|
// References returns all relationships that reference to or from the given identifiable
|
|
func (i *Index) References(identifiable artifact.Identifiable, types ...artifact.RelationshipType) []artifact.Relationship {
|
|
return toSortedSlice(append(fromMapped(i.fromID, identifiable), fromMapped(i.toID, identifiable)...), types)
|
|
}
|
|
|
|
// Coordinates returns all coordinates for the provided identifiable for provided relationship types
|
|
// If no types are provided, all relationship types are considered.
|
|
func (i *Index) Coordinates(identifiable artifact.Identifiable, types ...artifact.RelationshipType) []file.Coordinates {
|
|
var coordinates []file.Coordinates
|
|
for _, relationship := range i.References(identifiable, types...) {
|
|
cords := extractCoordinates(relationship)
|
|
coordinates = append(coordinates, cords...)
|
|
}
|
|
return coordinates
|
|
}
|
|
|
|
// Contains indicates the relationship is present in this index
|
|
func (i *Index) Contains(r artifact.Relationship) bool {
|
|
if mapped := i.fromID[r.From.ID()]; mapped != nil {
|
|
if ids := mapped.typeMap[r.Type]; ids != nil {
|
|
return ids[r.To.ID()] != nil
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// All returns a sorted set of relationships matching all types, or all relationships if no types specified
|
|
func (i *Index) All(types ...artifact.RelationshipType) []artifact.Relationship {
|
|
return toSortedSlice(i.all, types)
|
|
}
|
|
|
|
func fromMapped(idMap map[artifact.ID]*mappedRelationships, identifiable artifact.Identifiable) []*sortableRelationship {
|
|
if identifiable == nil {
|
|
return nil
|
|
}
|
|
return fromMappedByID(idMap, identifiable.ID())
|
|
}
|
|
|
|
func fromMappedByID(idMap map[artifact.ID]*mappedRelationships, id artifact.ID) []*sortableRelationship {
|
|
if idMap == nil {
|
|
return nil
|
|
}
|
|
mapped := idMap[id]
|
|
if mapped == nil {
|
|
return nil
|
|
}
|
|
return mapped.allRelated
|
|
}
|
|
|
|
func toSortedSlice(relationships []*sortableRelationship, types []artifact.RelationshipType) []artifact.Relationship {
|
|
// always return sorted for SBOM stability
|
|
slices.SortFunc(relationships, sortFunc)
|
|
var out []artifact.Relationship
|
|
for _, r := range relationships {
|
|
if len(types) == 0 || slices.Contains(types, r.relationship.Type) {
|
|
out = append(out, r.relationship)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func extractCoordinates(relationship artifact.Relationship) (results []file.Coordinates) {
|
|
if coordinates, exists := relationship.From.(file.Coordinates); exists {
|
|
results = append(results, coordinates)
|
|
}
|
|
|
|
if coordinates, exists := relationship.To.(file.Coordinates); exists {
|
|
results = append(results, coordinates)
|
|
}
|
|
|
|
return results
|
|
}
|
|
|
|
type mappedRelationships struct {
|
|
typeMap map[artifact.RelationshipType]map[artifact.ID]*sortableRelationship
|
|
allRelated []*sortableRelationship
|
|
}
|
|
|
|
func (m *mappedRelationships) add(id artifact.ID, newRelationship *sortableRelationship) {
|
|
m.allRelated = append(m.allRelated, newRelationship)
|
|
if m.typeMap == nil {
|
|
m.typeMap = map[artifact.RelationshipType]map[artifact.ID]*sortableRelationship{}
|
|
}
|
|
typeMap := m.typeMap[newRelationship.relationship.Type]
|
|
if typeMap == nil {
|
|
typeMap = map[artifact.ID]*sortableRelationship{}
|
|
m.typeMap[newRelationship.relationship.Type] = typeMap
|
|
}
|
|
typeMap[id] = newRelationship
|
|
}
|
|
|
|
type sortableRelationship struct {
|
|
from artifact.ID
|
|
to artifact.ID
|
|
relationship artifact.Relationship
|
|
}
|
|
|
|
func sortFunc(a, b *sortableRelationship) int {
|
|
cmp := strings.Compare(string(a.relationship.Type), string(b.relationship.Type))
|
|
if cmp != 0 {
|
|
return cmp
|
|
}
|
|
cmp = strings.Compare(string(a.from), string(b.from))
|
|
if cmp != 0 {
|
|
return cmp
|
|
}
|
|
return strings.Compare(string(a.to), string(b.to))
|
|
}
|