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  }