github.com/nextlinux/gosbom@v0.81.1-0.20230627115839-1ff50c281391/gosbom/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 VirtualPath). 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. gosbom: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 VirtualPath string `hash:"ignore" json:"-"` // 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 }, 72 LocationMetadata: LocationMetadata{ 73 Annotations: map[string]string{}, 74 }, 75 } 76 } 77 78 // NewVirtualLocation creates a new location for a path accessed by a virtual path (a path with a symlink or hardlink somewhere in the path) 79 func NewVirtualLocation(realPath, virtualPath string) Location { 80 return Location{ 81 LocationData: LocationData{ 82 Coordinates: Coordinates{ 83 RealPath: realPath, 84 }, 85 VirtualPath: virtualPath, 86 }, 87 LocationMetadata: LocationMetadata{ 88 Annotations: map[string]string{}, 89 }} 90 } 91 92 // NewLocationFromCoordinates creates a new location for the given Coordinates. 93 func NewLocationFromCoordinates(coordinates Coordinates) Location { 94 return Location{ 95 LocationData: LocationData{ 96 Coordinates: coordinates, 97 }, 98 LocationMetadata: LocationMetadata{ 99 Annotations: map[string]string{}, 100 }} 101 } 102 103 // NewVirtualLocationFromCoordinates creates a new location for the given Coordinates via a virtual path. 104 func NewVirtualLocationFromCoordinates(coordinates Coordinates, virtualPath string) Location { 105 return Location{ 106 LocationData: LocationData{ 107 Coordinates: coordinates, 108 VirtualPath: virtualPath, 109 }, 110 LocationMetadata: LocationMetadata{ 111 Annotations: map[string]string{}, 112 }} 113 } 114 115 // NewLocationFromImage creates a new Location representing the given path (extracted from the Reference) relative to the given image. 116 func NewLocationFromImage(virtualPath string, ref file.Reference, img *image.Image) Location { 117 layer := img.FileCatalog.Layer(ref) 118 return Location{ 119 LocationData: LocationData{ 120 Coordinates: Coordinates{ 121 RealPath: string(ref.RealPath), 122 FileSystemID: layer.Metadata.Digest, 123 }, 124 VirtualPath: virtualPath, 125 ref: ref, 126 }, 127 LocationMetadata: LocationMetadata{ 128 Annotations: map[string]string{}, 129 }, 130 } 131 } 132 133 // NewLocationFromDirectory creates a new Location representing the given path (extracted from the Reference) relative to the given directory. 134 func NewLocationFromDirectory(responsePath string, ref file.Reference) Location { 135 return Location{ 136 LocationData: LocationData{ 137 Coordinates: Coordinates{ 138 RealPath: responsePath, 139 }, 140 ref: ref, 141 }, 142 LocationMetadata: LocationMetadata{ 143 Annotations: map[string]string{}, 144 }, 145 } 146 } 147 148 // NewVirtualLocationFromDirectory creates a new Location representing the given path (extracted from the Reference) relative to the given directory with a separate virtual access path. 149 func NewVirtualLocationFromDirectory(responsePath, virtualResponsePath string, ref file.Reference) Location { 150 if responsePath == virtualResponsePath { 151 return NewLocationFromDirectory(responsePath, ref) 152 } 153 return Location{ 154 LocationData: LocationData{ 155 Coordinates: Coordinates{ 156 RealPath: responsePath, 157 }, 158 VirtualPath: virtualResponsePath, 159 ref: ref, 160 }, 161 LocationMetadata: LocationMetadata{ 162 Annotations: map[string]string{}, 163 }, 164 } 165 } 166 167 func (l Location) AccessPath() string { 168 if l.VirtualPath != "" { 169 return l.VirtualPath 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.VirtualPath != "" { 183 str += fmt.Sprintf(" VirtualPath=%q", l.VirtualPath) 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.VirtualPath == other.VirtualPath && 195 l.FileSystemID == other.FileSystemID 196 }