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

     1  package fileresolver
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  
     7  	"github.com/nextlinux/gosbom/gosbom/file"
     8  
     9  	stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
    10  	"github.com/anchore/stereoscope/pkg/filetree"
    11  	"github.com/anchore/stereoscope/pkg/image"
    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.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  // nolint:gocognit
    83  func (r *ContainerImageSquash) FilesByGlob(patterns ...string) ([]file.Location, error) {
    84  	uniqueFileIDs := stereoscopeFile.NewFileReferenceSet()
    85  	uniqueLocations := make([]file.Location, 0)
    86  
    87  	for _, pattern := range patterns {
    88  		results, err := r.img.SquashedSearchContext.SearchByGlob(pattern, filetree.FollowBasenameLinks)
    89  		if err != nil {
    90  			return nil, fmt.Errorf("failed to resolve files by glob (%s): %w", pattern, err)
    91  		}
    92  
    93  		for _, result := range results {
    94  			if !result.HasReference() {
    95  				continue
    96  			}
    97  			// don't consider directories (special case: there is no path information for /)
    98  			if result.RealPath == "/" {
    99  				continue
   100  			}
   101  
   102  			if r.img.FileCatalog.Exists(*result.Reference) {
   103  				metadata, err := r.img.FileCatalog.Get(*result.Reference)
   104  				if err != nil {
   105  					return nil, fmt.Errorf("unable to get file metadata for path=%q: %w", result.RequestPath, err)
   106  				}
   107  				// don't consider directories
   108  				if metadata.Metadata.IsDir() {
   109  					continue
   110  				}
   111  			}
   112  			// TODO: alex: can't we just use the result.Reference here instead?
   113  			resolvedLocations, err := r.FilesByPath(string(result.RequestPath))
   114  			if err != nil {
   115  				return nil, fmt.Errorf("failed to find files by path (result=%+v): %w", result, err)
   116  			}
   117  			for _, resolvedLocation := range resolvedLocations {
   118  				if uniqueFileIDs.Contains(resolvedLocation.Reference()) {
   119  					continue
   120  				}
   121  				uniqueFileIDs.Add(resolvedLocation.Reference())
   122  				uniqueLocations = append(uniqueLocations, resolvedLocation)
   123  			}
   124  		}
   125  	}
   126  
   127  	return uniqueLocations, nil
   128  }
   129  
   130  // RelativeFileByPath fetches a single file at the given path relative to the layer squash of the given reference.
   131  // This is helpful when attempting to find a file that is in the same layer or lower as another file. For the
   132  // ContainerImageSquash, this is a simple path lookup.
   133  func (r *ContainerImageSquash) RelativeFileByPath(_ file.Location, path string) *file.Location {
   134  	paths, err := r.FilesByPath(path)
   135  	if err != nil {
   136  		return nil
   137  	}
   138  	if len(paths) == 0 {
   139  		return nil
   140  	}
   141  
   142  	return &paths[0]
   143  }
   144  
   145  // FileContentsByLocation fetches file contents for a single file reference, regardless of the source layer.
   146  // If the path does not exist an error is returned.
   147  func (r *ContainerImageSquash) FileContentsByLocation(location file.Location) (io.ReadCloser, error) {
   148  	entry, err := r.img.FileCatalog.Get(location.Reference())
   149  	if err != nil {
   150  		return nil, fmt.Errorf("unable to get metadata for path=%q from file catalog: %w", location.RealPath, err)
   151  	}
   152  
   153  	switch entry.Metadata.Type {
   154  	case stereoscopeFile.TypeSymLink, stereoscopeFile.TypeHardLink:
   155  		// the location we are searching may be a symlink, we should always work with the resolved file
   156  		locations, err := r.FilesByPath(location.RealPath)
   157  		if err != nil {
   158  			return nil, fmt.Errorf("failed to resolve content location at location=%+v: %w", location, err)
   159  		}
   160  
   161  		switch len(locations) {
   162  		case 0:
   163  			return nil, fmt.Errorf("link resolution failed while resolving content location: %+v", location)
   164  		case 1:
   165  			location = locations[0]
   166  		default:
   167  			return nil, fmt.Errorf("link resolution resulted in multiple results while resolving content location: %+v", location)
   168  		}
   169  	case stereoscopeFile.TypeDirectory:
   170  		return nil, fmt.Errorf("unable to get file contents for directory: %+v", location)
   171  	}
   172  
   173  	return r.img.OpenReference(location.Reference())
   174  }
   175  
   176  func (r *ContainerImageSquash) AllLocations() <-chan file.Location {
   177  	results := make(chan file.Location)
   178  	go func() {
   179  		defer close(results)
   180  		for _, ref := range r.img.SquashedTree().AllFiles(stereoscopeFile.AllTypes()...) {
   181  			results <- file.NewLocationFromImage(string(ref.RealPath), ref, r.img)
   182  		}
   183  	}()
   184  	return results
   185  }
   186  
   187  func (r *ContainerImageSquash) FilesByMIMEType(types ...string) ([]file.Location, error) {
   188  	refs, err := r.img.SquashedSearchContext.SearchByMIMEType(types...)
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  
   193  	uniqueFileIDs := stereoscopeFile.NewFileReferenceSet()
   194  	uniqueLocations := make([]file.Location, 0)
   195  
   196  	for _, ref := range refs {
   197  		if ref.HasReference() {
   198  			if uniqueFileIDs.Contains(*ref.Reference) {
   199  				continue
   200  			}
   201  			location := file.NewLocationFromImage(string(ref.RequestPath), *ref.Reference, r.img)
   202  
   203  			uniqueFileIDs.Add(*ref.Reference)
   204  			uniqueLocations = append(uniqueLocations, location)
   205  		}
   206  	}
   207  
   208  	return uniqueLocations, nil
   209  }
   210  
   211  func (r *ContainerImageSquash) FileMetadataByLocation(location file.Location) (file.Metadata, error) {
   212  	return fileMetadataByLocation(r.img, location)
   213  }