github.com/nextlinux/gosbom@v0.81.1-0.20230627115839-1ff50c281391/gosbom/internal/fileresolver/container_image_all_layers.go (about)

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