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