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 }