mirror of
https://github.com/anchore/syft
synced 2024-11-13 23:57:07 +00:00
181 lines
5.4 KiB
Go
181 lines
5.4 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{}
|
|
out.Add(relationships...)
|
|
return &out
|
|
}
|
|
|
|
// Add adds all the given relationships to the index, without adding duplicates
|
|
func (i *Index) Add(relationships ...artifact.Relationship) {
|
|
if i.fromID == nil {
|
|
i.fromID = map[artifact.ID]*mappedRelationships{}
|
|
}
|
|
if i.toID == nil {
|
|
i.toID = map[artifact.ID]*mappedRelationships{}
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
// 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 || idMap == nil {
|
|
return nil
|
|
}
|
|
mapped := idMap[identifiable.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))
|
|
}
|