github.com/anchore/syft@v1.38.2/syft/internal/fileresolver/container_image_squash.go (about)

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