github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/internal/fileresolver/container_image_squash.go (about)

     1  package fileresolver
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  
     7  	stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
     8  	"github.com/anchore/stereoscope/pkg/filetree"
     9  	"github.com/anchore/stereoscope/pkg/image"
    10  	"github.com/anchore/syft/syft/file"
    11  )
    12  
    13  var _ file.Resolver = (*ContainerImageSquash)(nil)
    14  
    15  // ContainerImageSquash implements path and content access for the Squashed source option for container image data sources.
    16  type ContainerImageSquash struct {
    17  	img *image.Image
    18  }
    19  
    20  // NewFromContainerImageSquash returns a new resolver from the perspective of the squashed representation for the given image.
    21  func NewFromContainerImageSquash(img *image.Image) (*ContainerImageSquash, error) {
    22  	if img.SquashedTree() == nil {
    23  		return nil, fmt.Errorf("the image does not have have a squashed tree")
    24  	}
    25  
    26  	return &ContainerImageSquash{
    27  		img: img,
    28  	}, nil
    29  }
    30  
    31  // HasPath indicates if the given path exists in the underlying source.
    32  func (r *ContainerImageSquash) HasPath(path string) bool {
    33  	return r.img.SquashedTree().HasPath(stereoscopeFile.Path(path))
    34  }
    35  
    36  // FilesByPath returns all file.References that match the given paths within the squashed representation of the image.
    37  func (r *ContainerImageSquash) FilesByPath(paths ...string) ([]file.Location, error) {
    38  	uniqueFileIDs := stereoscopeFile.NewFileReferenceSet()
    39  	uniqueLocations := make([]file.Location, 0)
    40  
    41  	for _, path := range paths {
    42  		ref, err := r.img.SquashedSearchContext.SearchByPath(path, filetree.FollowBasenameLinks)
    43  		if err != nil {
    44  			return nil, err
    45  		}
    46  		if !ref.HasReference() {
    47  			// no file found, keep looking through layers
    48  			continue
    49  		}
    50  
    51  		// don't consider directories (special case: there is no path information for /)
    52  		if ref.RealPath == "/" {
    53  			continue
    54  		} else if r.img.FileCatalog.Exists(*ref.Reference) {
    55  			metadata, err := r.img.FileCatalog.Get(*ref.Reference)
    56  			if err != nil {
    57  				return nil, fmt.Errorf("unable to get file metadata for path=%q: %w", ref.RealPath, err)
    58  			}
    59  			// don't consider directories
    60  			if metadata.Metadata.IsDir() {
    61  				continue
    62  			}
    63  		}
    64  
    65  		// a file may be a symlink, process it as such and resolve it
    66  		resolvedRef, err := r.img.ResolveLinkByImageSquash(*ref.Reference)
    67  		if err != nil {
    68  			return nil, fmt.Errorf("failed to resolve link from img (ref=%+v): %w", ref, err)
    69  		}
    70  
    71  		if resolvedRef.HasReference() && !uniqueFileIDs.Contains(*resolvedRef.Reference) {
    72  			uniqueFileIDs.Add(*resolvedRef.Reference)
    73  			uniqueLocations = append(uniqueLocations, file.NewLocationFromImage(path, *resolvedRef.Reference, r.img))
    74  		}
    75  	}
    76  
    77  	return uniqueLocations, nil
    78  }
    79  
    80  // FilesByGlob returns all file.References that match the given path glob pattern within the squashed representation of the image.
    81  // nolint:gocognit
    82  func (r *ContainerImageSquash) FilesByGlob(patterns ...string) ([]file.Location, error) {
    83  	uniqueFileIDs := stereoscopeFile.NewFileReferenceSet()
    84  	uniqueLocations := make([]file.Location, 0)
    85  
    86  	for _, pattern := range patterns {
    87  		results, err := r.img.SquashedSearchContext.SearchByGlob(pattern, filetree.FollowBasenameLinks)
    88  		if err != nil {
    89  			return nil, fmt.Errorf("failed to resolve files by glob (%s): %w", pattern, err)
    90  		}
    91  
    92  		for _, result := range results {
    93  			if !result.HasReference() {
    94  				continue
    95  			}
    96  			// don't consider directories (special case: there is no path information for /)
    97  			if result.RealPath == "/" {
    98  				continue
    99  			}
   100  
   101  			if r.img.FileCatalog.Exists(*result.Reference) {
   102  				metadata, err := r.img.FileCatalog.Get(*result.Reference)
   103  				if err != nil {
   104  					return nil, fmt.Errorf("unable to get file metadata for path=%q: %w", result.RequestPath, err)
   105  				}
   106  				// don't consider directories
   107  				if metadata.Metadata.IsDir() {
   108  					continue
   109  				}
   110  			}
   111  			// TODO: alex: can't we just use the result.Reference here instead?
   112  			resolvedLocations, err := r.FilesByPath(string(result.RequestPath))
   113  			if err != nil {
   114  				return nil, fmt.Errorf("failed to find files by path (result=%+v): %w", result, err)
   115  			}
   116  			for _, resolvedLocation := range resolvedLocations {
   117  				if uniqueFileIDs.Contains(resolvedLocation.Reference()) {
   118  					continue
   119  				}
   120  				uniqueFileIDs.Add(resolvedLocation.Reference())
   121  				uniqueLocations = append(uniqueLocations, resolvedLocation)
   122  			}
   123  		}
   124  	}
   125  
   126  	return uniqueLocations, nil
   127  }
   128  
   129  // RelativeFileByPath fetches a single file at the given path relative to the layer squash of the given reference.
   130  // This is helpful when attempting to find a file that is in the same layer or lower as another file. For the
   131  // ContainerImageSquash, this is a simple path lookup.
   132  func (r *ContainerImageSquash) RelativeFileByPath(_ file.Location, path string) *file.Location {
   133  	paths, err := r.FilesByPath(path)
   134  	if err != nil {
   135  		return nil
   136  	}
   137  	if len(paths) == 0 {
   138  		return nil
   139  	}
   140  
   141  	return &paths[0]
   142  }
   143  
   144  // FileContentsByLocation fetches file contents for a single file reference, regardless of the source layer.
   145  // If the path does not exist an error is returned.
   146  func (r *ContainerImageSquash) FileContentsByLocation(location file.Location) (io.ReadCloser, error) {
   147  	entry, err := r.img.FileCatalog.Get(location.Reference())
   148  	if err != nil {
   149  		return nil, fmt.Errorf("unable to get metadata for path=%q from file catalog: %w", location.RealPath, err)
   150  	}
   151  
   152  	switch entry.Metadata.Type {
   153  	case stereoscopeFile.TypeSymLink, stereoscopeFile.TypeHardLink:
   154  		// the location we are searching may be a symlink, we should always work with the resolved file
   155  		locations, err := r.FilesByPath(location.RealPath)
   156  		if err != nil {
   157  			return nil, fmt.Errorf("failed to resolve content location at location=%+v: %w", location, err)
   158  		}
   159  
   160  		switch len(locations) {
   161  		case 0:
   162  			return nil, fmt.Errorf("link resolution failed while resolving content location: %+v", location)
   163  		case 1:
   164  			location = locations[0]
   165  		default:
   166  			return nil, fmt.Errorf("link resolution resulted in multiple results while resolving content location: %+v", location)
   167  		}
   168  	case stereoscopeFile.TypeDirectory:
   169  		return nil, fmt.Errorf("unable to get file contents for directory: %+v", location)
   170  	}
   171  
   172  	return r.img.OpenReference(location.Reference())
   173  }
   174  
   175  func (r *ContainerImageSquash) AllLocations() <-chan file.Location {
   176  	results := make(chan file.Location)
   177  	go func() {
   178  		defer close(results)
   179  		for _, ref := range r.img.SquashedTree().AllFiles(stereoscopeFile.AllTypes()...) {
   180  			results <- file.NewLocationFromImage(string(ref.RealPath), ref, r.img)
   181  		}
   182  	}()
   183  	return results
   184  }
   185  
   186  func (r *ContainerImageSquash) FilesByMIMEType(types ...string) ([]file.Location, error) {
   187  	refs, err := r.img.SquashedSearchContext.SearchByMIMEType(types...)
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  
   192  	uniqueFileIDs := stereoscopeFile.NewFileReferenceSet()
   193  	uniqueLocations := make([]file.Location, 0)
   194  
   195  	for _, ref := range refs {
   196  		if ref.HasReference() {
   197  			if uniqueFileIDs.Contains(*ref.Reference) {
   198  				continue
   199  			}
   200  			location := file.NewLocationFromImage(string(ref.RequestPath), *ref.Reference, r.img)
   201  
   202  			uniqueFileIDs.Add(*ref.Reference)
   203  			uniqueLocations = append(uniqueLocations, location)
   204  		}
   205  	}
   206  
   207  	return uniqueLocations, nil
   208  }
   209  
   210  func (r *ContainerImageSquash) FileMetadataByLocation(location file.Location) (file.Metadata, error) {
   211  	return fileMetadataByLocation(r.img, location)
   212  }