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  }