github.com/kastenhq/syft@v0.0.0-20230821225854-0710af25cdbe/syft/internal/fileresolver/container_image_all_layers.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/kastenhq/syft/internal/log"
    11  	"github.com/kastenhq/syft/syft/file"
    12  )
    13  
    14  var _ file.Resolver = (*ContainerImageAllLayers)(nil)
    15  
    16  // ContainerImageAllLayers implements path and content access for the AllLayers source option for container image data sources.
    17  type ContainerImageAllLayers struct {
    18  	img    *image.Image
    19  	layers []int
    20  }
    21  
    22  // NewFromContainerImageAllLayers returns a new resolver from the perspective of all image layers for the given image.
    23  func NewFromContainerImageAllLayers(img *image.Image) (*ContainerImageAllLayers, error) {
    24  	if len(img.Layers) == 0 {
    25  		return nil, fmt.Errorf("the image does not contain any layers")
    26  	}
    27  
    28  	var layers = make([]int, 0)
    29  	for idx := range img.Layers {
    30  		layers = append(layers, idx)
    31  	}
    32  	return &ContainerImageAllLayers{
    33  		img:    img,
    34  		layers: layers,
    35  	}, nil
    36  }
    37  
    38  // HasPath indicates if the given path exists in the underlying source.
    39  func (r *ContainerImageAllLayers) HasPath(path string) bool {
    40  	p := stereoscopeFile.Path(path)
    41  	for _, layerIdx := range r.layers {
    42  		tree := r.img.Layers[layerIdx].Tree
    43  		if tree.HasPath(p) {
    44  			return true
    45  		}
    46  	}
    47  	return false
    48  }
    49  
    50  func (r *ContainerImageAllLayers) fileByRef(ref stereoscopeFile.Reference, uniqueFileIDs stereoscopeFile.ReferenceSet, layerIdx int) ([]stereoscopeFile.Reference, error) {
    51  	uniqueFiles := make([]stereoscopeFile.Reference, 0)
    52  
    53  	// since there is potentially considerable work for each symlink/hardlink that needs to be resolved, let's check to see if this is a symlink/hardlink first
    54  	entry, err := r.img.FileCatalog.Get(ref)
    55  	if err != nil {
    56  		return nil, fmt.Errorf("unable to fetch metadata (ref=%+v): %w", ref, err)
    57  	}
    58  
    59  	if entry.Metadata.Type == stereoscopeFile.TypeHardLink || entry.Metadata.Type == stereoscopeFile.TypeSymLink {
    60  		// a link may resolve in this layer or higher, assuming a squashed tree is used to search
    61  		// we should search all possible resolutions within the valid source
    62  		for _, subLayerIdx := range r.layers[layerIdx:] {
    63  			resolvedRef, err := r.img.ResolveLinkByLayerSquash(ref, subLayerIdx)
    64  			if err != nil {
    65  				return nil, fmt.Errorf("failed to resolve link from layer (layer=%d ref=%+v): %w", subLayerIdx, ref, err)
    66  			}
    67  			if resolvedRef.HasReference() && !uniqueFileIDs.Contains(*resolvedRef.Reference) {
    68  				uniqueFileIDs.Add(*resolvedRef.Reference)
    69  				uniqueFiles = append(uniqueFiles, *resolvedRef.Reference)
    70  			}
    71  		}
    72  	} else if !uniqueFileIDs.Contains(ref) {
    73  		uniqueFileIDs.Add(ref)
    74  		uniqueFiles = append(uniqueFiles, ref)
    75  	}
    76  
    77  	return uniqueFiles, nil
    78  }
    79  
    80  // FilesByPath returns all file.References that match the given paths from any layer in the image.
    81  func (r *ContainerImageAllLayers) FilesByPath(paths ...string) ([]file.Location, error) {
    82  	uniqueFileIDs := stereoscopeFile.NewFileReferenceSet()
    83  	uniqueLocations := make([]file.Location, 0)
    84  
    85  	for _, path := range paths {
    86  		for idx, layerIdx := range r.layers {
    87  			ref, err := r.img.Layers[layerIdx].SearchContext.SearchByPath(path, filetree.FollowBasenameLinks, filetree.DoNotFollowDeadBasenameLinks)
    88  			if err != nil {
    89  				return nil, err
    90  			}
    91  			if !ref.HasReference() {
    92  				// no file found, keep looking through layers
    93  				continue
    94  			}
    95  
    96  			// don't consider directories (special case: there is no path information for /)
    97  			if ref.RealPath == "/" {
    98  				continue
    99  			} else if r.img.FileCatalog.Exists(*ref.Reference) {
   100  				metadata, err := r.img.FileCatalog.Get(*ref.Reference)
   101  				if err != nil {
   102  					return nil, fmt.Errorf("unable to get file metadata for path=%q: %w", ref.RealPath, err)
   103  				}
   104  				if metadata.Metadata.IsDir() {
   105  					continue
   106  				}
   107  			}
   108  
   109  			results, err := r.fileByRef(*ref.Reference, uniqueFileIDs, idx)
   110  			if err != nil {
   111  				return nil, err
   112  			}
   113  			for _, result := range results {
   114  				uniqueLocations = append(uniqueLocations, file.NewLocationFromImage(path, result, r.img))
   115  			}
   116  		}
   117  	}
   118  	return uniqueLocations, nil
   119  }
   120  
   121  // FilesByGlob returns all file.References that match the given path glob pattern from any layer in the image.
   122  // nolint:gocognit
   123  func (r *ContainerImageAllLayers) FilesByGlob(patterns ...string) ([]file.Location, error) {
   124  	uniqueFileIDs := stereoscopeFile.NewFileReferenceSet()
   125  	uniqueLocations := make([]file.Location, 0)
   126  
   127  	for _, pattern := range patterns {
   128  		for idx, layerIdx := range r.layers {
   129  			results, err := r.img.Layers[layerIdx].SquashedSearchContext.SearchByGlob(pattern, filetree.FollowBasenameLinks, filetree.DoNotFollowDeadBasenameLinks)
   130  			if err != nil {
   131  				return nil, fmt.Errorf("failed to resolve files by glob (%s): %w", pattern, err)
   132  			}
   133  
   134  			for _, result := range results {
   135  				if !result.HasReference() {
   136  					continue
   137  				}
   138  				// don't consider directories (special case: there is no path information for /)
   139  				if result.RealPath == "/" {
   140  					continue
   141  				} else if r.img.FileCatalog.Exists(*result.Reference) {
   142  					metadata, err := r.img.FileCatalog.Get(*result.Reference)
   143  					if err != nil {
   144  						return nil, fmt.Errorf("unable to get file metadata for path=%q: %w", result.RequestPath, err)
   145  					}
   146  					// don't consider directories
   147  					if metadata.Metadata.IsDir() {
   148  						continue
   149  					}
   150  				}
   151  
   152  				refResults, err := r.fileByRef(*result.Reference, uniqueFileIDs, idx)
   153  				if err != nil {
   154  					return nil, err
   155  				}
   156  				for _, refResult := range refResults {
   157  					uniqueLocations = append(uniqueLocations, file.NewLocationFromImage(string(result.RequestPath), refResult, r.img))
   158  				}
   159  			}
   160  		}
   161  	}
   162  
   163  	return uniqueLocations, nil
   164  }
   165  
   166  // RelativeFileByPath fetches a single file at the given path relative to the layer squash of the given reference.
   167  // This is helpful when attempting to find a file that is in the same layer or lower as another file.
   168  func (r *ContainerImageAllLayers) RelativeFileByPath(location file.Location, path string) *file.Location {
   169  	layer := r.img.FileCatalog.Layer(location.Reference())
   170  
   171  	exists, relativeRef, err := layer.SquashedTree.File(stereoscopeFile.Path(path), filetree.FollowBasenameLinks)
   172  	if err != nil {
   173  		log.Errorf("failed to find path=%q in squash: %+w", path, err)
   174  		return nil
   175  	}
   176  	if !exists && !relativeRef.HasReference() {
   177  		return nil
   178  	}
   179  
   180  	relativeLocation := file.NewLocationFromImage(path, *relativeRef.Reference, r.img)
   181  
   182  	return &relativeLocation
   183  }
   184  
   185  // FileContentsByLocation fetches file contents for a single file reference, irregardless of the source layer.
   186  // If the path does not exist an error is returned.
   187  func (r *ContainerImageAllLayers) FileContentsByLocation(location file.Location) (io.ReadCloser, error) {
   188  	entry, err := r.img.FileCatalog.Get(location.Reference())
   189  	if err != nil {
   190  		return nil, fmt.Errorf("unable to get metadata for path=%q from file catalog: %w", location.RealPath, err)
   191  	}
   192  
   193  	switch entry.Metadata.Type {
   194  	case stereoscopeFile.TypeSymLink, stereoscopeFile.TypeHardLink:
   195  		// the location we are searching may be a symlink, we should always work with the resolved file
   196  		newLocation := r.RelativeFileByPath(location, location.VirtualPath)
   197  		if newLocation == nil {
   198  			// this is a dead link
   199  			return nil, fmt.Errorf("no contents for location=%q", location.VirtualPath)
   200  		}
   201  		location = *newLocation
   202  	case stereoscopeFile.TypeDirectory:
   203  		return nil, fmt.Errorf("cannot read contents of non-file %q", location.Reference().RealPath)
   204  	}
   205  
   206  	return r.img.OpenReference(location.Reference())
   207  }
   208  
   209  func (r *ContainerImageAllLayers) FilesByMIMEType(types ...string) ([]file.Location, error) {
   210  	uniqueFileIDs := stereoscopeFile.NewFileReferenceSet()
   211  	uniqueLocations := make([]file.Location, 0)
   212  
   213  	for idx, layerIdx := range r.layers {
   214  		refs, err := r.img.Layers[layerIdx].SearchContext.SearchByMIMEType(types...)
   215  		if err != nil {
   216  			return nil, err
   217  		}
   218  
   219  		for _, ref := range refs {
   220  			if !ref.HasReference() {
   221  				continue
   222  			}
   223  
   224  			refResults, err := r.fileByRef(*ref.Reference, uniqueFileIDs, idx)
   225  			if err != nil {
   226  				return nil, err
   227  			}
   228  			for _, refResult := range refResults {
   229  				uniqueLocations = append(uniqueLocations, file.NewLocationFromImage(string(ref.RequestPath), refResult, r.img))
   230  			}
   231  		}
   232  	}
   233  
   234  	return uniqueLocations, nil
   235  }
   236  
   237  func (r *ContainerImageAllLayers) AllLocations() <-chan file.Location {
   238  	results := make(chan file.Location)
   239  	go func() {
   240  		defer close(results)
   241  		for _, layerIdx := range r.layers {
   242  			tree := r.img.Layers[layerIdx].Tree
   243  			for _, ref := range tree.AllFiles(stereoscopeFile.AllTypes()...) {
   244  				results <- file.NewLocationFromImage(string(ref.RealPath), ref, r.img)
   245  			}
   246  		}
   247  	}()
   248  	return results
   249  }
   250  
   251  func (r *ContainerImageAllLayers) FileMetadataByLocation(location file.Location) (file.Metadata, error) {
   252  	return fileMetadataByLocation(r.img, location)
   253  }