github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/syft/internal/fileresolver/container_image_squash.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/anchore/syft/syft/file" 11 ) 12 13 var _ file.Resolver = (*ContainerImageSquash)(nil) 14 15 // ContainerImageSquash implements path and content access for the Squashed source option for container image data sources. 16 type ContainerImageSquash struct { 17 img *image.Image 18 } 19 20 // NewFromContainerImageSquash returns a new resolver from the perspective of the squashed representation for the given image. 21 func NewFromContainerImageSquash(img *image.Image) (*ContainerImageSquash, error) { 22 if img.SquashedTree() == nil { 23 return nil, fmt.Errorf("the image does not have have a squashed tree") 24 } 25 26 return &ContainerImageSquash{ 27 img: img, 28 }, nil 29 } 30 31 // HasPath indicates if the given path exists in the underlying source. 32 func (r *ContainerImageSquash) HasPath(path string) bool { 33 return r.img.SquashedTree().HasPath(stereoscopeFile.Path(path)) 34 } 35 36 // FilesByPath returns all file.References that match the given paths within the squashed representation of the image. 37 func (r *ContainerImageSquash) FilesByPath(paths ...string) ([]file.Location, error) { 38 uniqueFileIDs := stereoscopeFile.NewFileReferenceSet() 39 uniqueLocations := make([]file.Location, 0) 40 41 for _, path := range paths { 42 ref, err := r.img.SquashedSearchContext.SearchByPath(path, filetree.FollowBasenameLinks) 43 if err != nil { 44 return nil, err 45 } 46 if !ref.HasReference() { 47 // no file found, keep looking through layers 48 continue 49 } 50 51 // don't consider directories (special case: there is no path information for /) 52 if ref.RealPath == "/" { 53 continue 54 } else if r.img.FileCatalog.Exists(*ref.Reference) { 55 metadata, err := r.img.FileCatalog.Get(*ref.Reference) 56 if err != nil { 57 return nil, fmt.Errorf("unable to get file metadata for path=%q: %w", ref.RealPath, err) 58 } 59 // don't consider directories 60 if metadata.Metadata.IsDir() { 61 continue 62 } 63 } 64 65 // a file may be a symlink, process it as such and resolve it 66 resolvedRef, err := r.img.ResolveLinkByImageSquash(*ref.Reference) 67 if err != nil { 68 return nil, fmt.Errorf("failed to resolve link from img (ref=%+v): %w", ref, err) 69 } 70 71 if resolvedRef.HasReference() && !uniqueFileIDs.Contains(*resolvedRef.Reference) { 72 uniqueFileIDs.Add(*resolvedRef.Reference) 73 uniqueLocations = append(uniqueLocations, file.NewLocationFromImage(path, *resolvedRef.Reference, r.img)) 74 } 75 } 76 77 return uniqueLocations, nil 78 } 79 80 // FilesByGlob returns all file.References that match the given path glob pattern within the squashed representation of the image. 81 // nolint:gocognit 82 func (r *ContainerImageSquash) FilesByGlob(patterns ...string) ([]file.Location, error) { 83 uniqueFileIDs := stereoscopeFile.NewFileReferenceSet() 84 uniqueLocations := make([]file.Location, 0) 85 86 for _, pattern := range patterns { 87 results, err := r.img.SquashedSearchContext.SearchByGlob(pattern, filetree.FollowBasenameLinks) 88 if err != nil { 89 return nil, fmt.Errorf("failed to resolve files by glob (%s): %w", pattern, err) 90 } 91 92 for _, result := range results { 93 if !result.HasReference() { 94 continue 95 } 96 // don't consider directories (special case: there is no path information for /) 97 if result.RealPath == "/" { 98 continue 99 } 100 101 if r.img.FileCatalog.Exists(*result.Reference) { 102 metadata, err := r.img.FileCatalog.Get(*result.Reference) 103 if err != nil { 104 return nil, fmt.Errorf("unable to get file metadata for path=%q: %w", result.RequestPath, err) 105 } 106 // don't consider directories 107 if metadata.Metadata.IsDir() { 108 continue 109 } 110 } 111 // TODO: alex: can't we just use the result.Reference here instead? 112 resolvedLocations, err := r.FilesByPath(string(result.RequestPath)) 113 if err != nil { 114 return nil, fmt.Errorf("failed to find files by path (result=%+v): %w", result, err) 115 } 116 for _, resolvedLocation := range resolvedLocations { 117 if uniqueFileIDs.Contains(resolvedLocation.Reference()) { 118 continue 119 } 120 uniqueFileIDs.Add(resolvedLocation.Reference()) 121 uniqueLocations = append(uniqueLocations, resolvedLocation) 122 } 123 } 124 } 125 126 return uniqueLocations, nil 127 } 128 129 // RelativeFileByPath fetches a single file at the given path relative to the layer squash of the given reference. 130 // This is helpful when attempting to find a file that is in the same layer or lower as another file. For the 131 // ContainerImageSquash, this is a simple path lookup. 132 func (r *ContainerImageSquash) RelativeFileByPath(_ file.Location, path string) *file.Location { 133 paths, err := r.FilesByPath(path) 134 if err != nil { 135 return nil 136 } 137 if len(paths) == 0 { 138 return nil 139 } 140 141 return &paths[0] 142 } 143 144 // FileContentsByLocation fetches file contents for a single file reference, regardless of the source layer. 145 // If the path does not exist an error is returned. 146 func (r *ContainerImageSquash) FileContentsByLocation(location file.Location) (io.ReadCloser, error) { 147 entry, err := r.img.FileCatalog.Get(location.Reference()) 148 if err != nil { 149 return nil, fmt.Errorf("unable to get metadata for path=%q from file catalog: %w", location.RealPath, err) 150 } 151 152 switch entry.Metadata.Type { 153 case stereoscopeFile.TypeSymLink, stereoscopeFile.TypeHardLink: 154 // the location we are searching may be a symlink, we should always work with the resolved file 155 locations, err := r.FilesByPath(location.RealPath) 156 if err != nil { 157 return nil, fmt.Errorf("failed to resolve content location at location=%+v: %w", location, err) 158 } 159 160 switch len(locations) { 161 case 0: 162 return nil, fmt.Errorf("link resolution failed while resolving content location: %+v", location) 163 case 1: 164 location = locations[0] 165 default: 166 return nil, fmt.Errorf("link resolution resulted in multiple results while resolving content location: %+v", location) 167 } 168 case stereoscopeFile.TypeDirectory: 169 return nil, fmt.Errorf("unable to get file contents for directory: %+v", location) 170 } 171 172 return r.img.OpenReference(location.Reference()) 173 } 174 175 func (r *ContainerImageSquash) AllLocations() <-chan file.Location { 176 results := make(chan file.Location) 177 go func() { 178 defer close(results) 179 for _, ref := range r.img.SquashedTree().AllFiles(stereoscopeFile.AllTypes()...) { 180 results <- file.NewLocationFromImage(string(ref.RealPath), ref, r.img) 181 } 182 }() 183 return results 184 } 185 186 func (r *ContainerImageSquash) FilesByMIMEType(types ...string) ([]file.Location, error) { 187 refs, err := r.img.SquashedSearchContext.SearchByMIMEType(types...) 188 if err != nil { 189 return nil, err 190 } 191 192 uniqueFileIDs := stereoscopeFile.NewFileReferenceSet() 193 uniqueLocations := make([]file.Location, 0) 194 195 for _, ref := range refs { 196 if ref.HasReference() { 197 if uniqueFileIDs.Contains(*ref.Reference) { 198 continue 199 } 200 location := file.NewLocationFromImage(string(ref.RequestPath), *ref.Reference, r.img) 201 202 uniqueFileIDs.Add(*ref.Reference) 203 uniqueLocations = append(uniqueLocations, location) 204 } 205 } 206 207 return uniqueLocations, nil 208 } 209 210 func (r *ContainerImageSquash) FileMetadataByLocation(location file.Location) (file.Metadata, error) { 211 return fileMetadataByLocation(r.img, location) 212 }