github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/file/location.go (about) 1 package file 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/go-multierror" 7 8 "github.com/anchore/stereoscope/pkg/file" 9 "github.com/anchore/stereoscope/pkg/image" 10 ) 11 12 // Location represents a path relative to a particular filesystem resolved to a specific file.Reference. This struct is used as a key 13 // in content fetching to uniquely identify a file relative to a request (the AccessPath). 14 type Location struct { 15 LocationData `cyclonedx:""` 16 LocationMetadata `cyclonedx:""` 17 } 18 19 type LocationData struct { 20 Coordinates `cyclonedx:""` // Empty string here means there is no intermediate property name, e.g. syft:locations:0:path without "coordinates" 21 // note: it is IMPORTANT to ignore anything but the coordinates for a Location when considering the ID (hash value) 22 // since the coordinates are the minimally correct ID for a location (symlinks should not come into play) 23 AccessPath string `hash:"ignore" json:"accessPath"` // The path to the file which may or may not have hardlinks / symlinks 24 ref file.Reference `hash:"ignore"` // The file reference relative to the stereoscope.FileCatalog that has more information about this location. 25 } 26 27 func (l LocationData) Reference() file.Reference { 28 return l.ref 29 } 30 31 type LocationMetadata struct { 32 Annotations map[string]string `json:"annotations,omitempty"` // Arbitrary key-value pairs that can be used to annotate a location 33 } 34 35 func (m *LocationMetadata) merge(other LocationMetadata) error { 36 var errs error 37 for k, v := range other.Annotations { 38 if otherV, ok := m.Annotations[k]; ok { 39 if v != otherV { 40 err := fmt.Errorf("unable to merge location metadata: conflicting values for key=%q: %q != %q", k, v, otherV) 41 errs = multierror.Append(errs, err) 42 continue 43 } 44 } 45 m.Annotations[k] = v 46 } 47 return errs 48 } 49 50 func (l Location) WithAnnotation(key, value string) Location { 51 if l.LocationMetadata.Annotations == nil { 52 l.LocationMetadata.Annotations = map[string]string{} 53 } 54 l.LocationMetadata.Annotations[key] = value 55 return l 56 } 57 58 func (l Location) WithoutAnnotations() Location { 59 l.LocationMetadata.Annotations = map[string]string{} 60 61 return l 62 } 63 64 // NewLocation creates a new Location representing a path without denoting a filesystem or FileCatalog reference. 65 func NewLocation(realPath string) Location { 66 return Location{ 67 LocationData: LocationData{ 68 Coordinates: Coordinates{ 69 RealPath: realPath, 70 }, 71 AccessPath: realPath, 72 }, 73 LocationMetadata: LocationMetadata{ 74 Annotations: map[string]string{}, 75 }, 76 } 77 } 78 79 // NewVirtualLocation creates a new location for a path accessed by a virtual path (a path with a symlink or hardlink somewhere in the path) 80 func NewVirtualLocation(realPath, accessPath string) Location { 81 return Location{ 82 LocationData: LocationData{ 83 Coordinates: Coordinates{ 84 RealPath: realPath, 85 }, 86 AccessPath: accessPath, 87 }, 88 LocationMetadata: LocationMetadata{ 89 Annotations: map[string]string{}, 90 }} 91 } 92 93 // NewLocationFromCoordinates creates a new location for the given Coordinates. 94 func NewLocationFromCoordinates(coordinates Coordinates) Location { 95 return Location{ 96 LocationData: LocationData{ 97 Coordinates: coordinates, 98 AccessPath: coordinates.RealPath, 99 }, 100 LocationMetadata: LocationMetadata{ 101 Annotations: map[string]string{}, 102 }} 103 } 104 105 // NewVirtualLocationFromCoordinates creates a new location for the given Coordinates via a virtual path. 106 func NewVirtualLocationFromCoordinates(coordinates Coordinates, accessPath string) Location { 107 return Location{ 108 LocationData: LocationData{ 109 Coordinates: coordinates, 110 AccessPath: accessPath, 111 }, 112 LocationMetadata: LocationMetadata{ 113 Annotations: map[string]string{}, 114 }} 115 } 116 117 // NewLocationFromImage creates a new Location representing the given path (extracted from the Reference) relative to the given image. 118 func NewLocationFromImage(accessPath string, ref file.Reference, img *image.Image) Location { 119 layer := img.FileCatalog.Layer(ref) 120 return Location{ 121 LocationData: LocationData{ 122 Coordinates: Coordinates{ 123 RealPath: string(ref.RealPath), 124 FileSystemID: layer.Metadata.Digest, 125 }, 126 AccessPath: accessPath, 127 ref: ref, 128 }, 129 LocationMetadata: LocationMetadata{ 130 Annotations: map[string]string{}, 131 }, 132 } 133 } 134 135 // NewLocationFromDirectory creates a new Location representing the given path (extracted from the Reference) relative to the given directory. 136 func NewLocationFromDirectory(responsePath string, ref file.Reference) Location { 137 return Location{ 138 LocationData: LocationData{ 139 Coordinates: Coordinates{ 140 RealPath: responsePath, 141 }, 142 AccessPath: responsePath, 143 ref: ref, 144 }, 145 LocationMetadata: LocationMetadata{ 146 Annotations: map[string]string{}, 147 }, 148 } 149 } 150 151 // NewVirtualLocationFromDirectory creates a new Location representing the given path (extracted from the Reference) relative to the given directory with a separate virtual access path. 152 func NewVirtualLocationFromDirectory(responsePath, responseAccessPath string, ref file.Reference) Location { 153 return Location{ 154 LocationData: LocationData{ 155 Coordinates: Coordinates{ 156 RealPath: responsePath, 157 }, 158 AccessPath: responseAccessPath, 159 ref: ref, 160 }, 161 LocationMetadata: LocationMetadata{ 162 Annotations: map[string]string{}, 163 }, 164 } 165 } 166 167 func (l Location) Path() string { 168 if l.AccessPath != "" { 169 return l.AccessPath 170 } 171 return l.RealPath 172 } 173 174 func (l Location) String() string { 175 str := "" 176 if l.ref.ID() != 0 { 177 str += fmt.Sprintf("id=%d ", l.ref.ID()) 178 } 179 180 str += fmt.Sprintf("RealPath=%q", l.RealPath) 181 182 if l.AccessPath != "" && l.AccessPath != l.RealPath { 183 str += fmt.Sprintf(" AccessPath=%q", l.AccessPath) 184 } 185 186 if l.FileSystemID != "" { 187 str += fmt.Sprintf(" Layer=%q", l.FileSystemID) 188 } 189 return fmt.Sprintf("Location<%s>", str) 190 } 191 192 func (l Location) Equals(other Location) bool { 193 return l.RealPath == other.RealPath && 194 l.AccessPath == other.AccessPath && 195 l.FileSystemID == other.FileSystemID 196 }