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 }