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