add squashed with all layers resolver

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
Alex Goodman 2023-04-07 15:12:25 -04:00
parent 2fa238af7c
commit 51bb710861
No known key found for this signature in database
GPG key ID: 5CB45AE22BAB7EA7
7 changed files with 121 additions and 14 deletions

View file

@ -59,6 +59,8 @@ func (p *Package) merge(other Package) error {
return fmt.Errorf("cannot merge packages with different IDs: %q vs %q", p.id, other.id) return fmt.Errorf("cannot merge packages with different IDs: %q vs %q", p.id, other.id)
} }
log.WithFields("id", p.id, "purl", p.PURL).Trace("merging similar packages")
if p.PURL != other.PURL { if p.PURL != other.PURL {
log.Warnf("merging packages have with different pURLs: %q=%q vs %q=%q", p.id, p.PURL, other.id, other.PURL) log.Warnf("merging packages have with different pURLs: %q=%q vs %q=%q", p.id, p.PURL, other.id, other.PURL)
} }

View file

@ -18,8 +18,8 @@ type imageAllLayersResolver struct {
layers []int layers []int
} }
// newAllLayersResolver returns a new resolver from the perspective of all image layers for the given image. // newImageAllLayersResolver returns a new resolver from the perspective of all image layers for the given image.
func newAllLayersResolver(img *image.Image) (*imageAllLayersResolver, error) { func newImageAllLayersResolver(img *image.Image) (*imageAllLayersResolver, error) {
if len(img.Layers) == 0 { if len(img.Layers) == 0 {
return nil, fmt.Errorf("the image does not contain any layers") return nil, fmt.Errorf("the image does not contain any layers")
} }
@ -202,7 +202,7 @@ func (r *imageAllLayersResolver) FileContentsByLocation(location Location) (io.R
return nil, fmt.Errorf("cannot read contents of non-file %q", location.ref.RealPath) return nil, fmt.Errorf("cannot read contents of non-file %q", location.ref.RealPath)
} }
return r.img.FileContentsByRef(location.ref) return r.img.OpenReference(location.ref)
} }
func (r *imageAllLayersResolver) FilesByMIMEType(types ...string) ([]Location, error) { func (r *imageAllLayersResolver) FilesByMIMEType(types ...string) ([]Location, error) {

View file

@ -91,7 +91,7 @@ func TestAllLayersResolver_FilesByPath(t *testing.T) {
t.Run(c.name, func(t *testing.T) { t.Run(c.name, func(t *testing.T) {
img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks")
resolver, err := newAllLayersResolver(img) resolver, err := newImageAllLayersResolver(img)
if err != nil { if err != nil {
t.Fatalf("could not create resolver: %+v", err) t.Fatalf("could not create resolver: %+v", err)
} }
@ -205,7 +205,7 @@ func TestAllLayersResolver_FilesByGlob(t *testing.T) {
t.Run(c.name, func(t *testing.T) { t.Run(c.name, func(t *testing.T) {
img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks")
resolver, err := newAllLayersResolver(img) resolver, err := newImageAllLayersResolver(img)
if err != nil { if err != nil {
t.Fatalf("could not create resolver: %+v", err) t.Fatalf("could not create resolver: %+v", err)
} }
@ -257,7 +257,7 @@ func Test_imageAllLayersResolver_FilesByMIMEType(t *testing.T) {
t.Run(test.fixtureName, func(t *testing.T) { t.Run(test.fixtureName, func(t *testing.T) {
img := imagetest.GetFixtureImage(t, "docker-archive", test.fixtureName) img := imagetest.GetFixtureImage(t, "docker-archive", test.fixtureName)
resolver, err := newAllLayersResolver(img) resolver, err := newImageAllLayersResolver(img)
assert.NoError(t, err) assert.NoError(t, err)
locations, err := resolver.FilesByMIMEType(test.mimeType) locations, err := resolver.FilesByMIMEType(test.mimeType)
@ -274,7 +274,7 @@ func Test_imageAllLayersResolver_FilesByMIMEType(t *testing.T) {
func Test_imageAllLayersResolver_hasFilesystemIDInLocation(t *testing.T) { func Test_imageAllLayersResolver_hasFilesystemIDInLocation(t *testing.T) {
img := imagetest.GetFixtureImage(t, "docker-archive", "image-duplicate-path") img := imagetest.GetFixtureImage(t, "docker-archive", "image-duplicate-path")
resolver, err := newAllLayersResolver(img) resolver, err := newImageAllLayersResolver(img)
assert.NoError(t, err) assert.NoError(t, err)
locations, err := resolver.FilesByMIMEType("text/plain") locations, err := resolver.FilesByMIMEType("text/plain")
@ -334,7 +334,7 @@ func TestAllLayersImageResolver_FilesContents(t *testing.T) {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks")
resolver, err := newAllLayersResolver(img) resolver, err := newImageAllLayersResolver(img)
assert.NoError(t, err) assert.NoError(t, err)
refs, err := resolver.FilesByPath(test.fixture) refs, err := resolver.FilesByPath(test.fixture)
@ -361,7 +361,7 @@ func TestAllLayersImageResolver_FilesContents_errorOnDirRequest(t *testing.T) {
img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks")
resolver, err := newAllLayersResolver(img) resolver, err := newImageAllLayersResolver(img)
assert.NoError(t, err) assert.NoError(t, err)
var dirLoc *Location var dirLoc *Location
@ -675,7 +675,7 @@ func Test_imageAllLayersResolver_resolvesLinks(t *testing.T) {
img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks")
resolver, err := newAllLayersResolver(img) resolver, err := newImageAllLayersResolver(img)
assert.NoError(t, err) assert.NoError(t, err)
actual := test.runner(resolver) actual := test.runner(resolver)
@ -689,7 +689,7 @@ func Test_imageAllLayersResolver_resolvesLinks(t *testing.T) {
func TestAllLayersResolver_AllLocations(t *testing.T) { func TestAllLayersResolver_AllLocations(t *testing.T) {
img := imagetest.GetFixtureImage(t, "docker-archive", "image-files-deleted") img := imagetest.GetFixtureImage(t, "docker-archive", "image-files-deleted")
resolver, err := newAllLayersResolver(img) resolver, err := newImageAllLayersResolver(img)
assert.NoError(t, err) assert.NoError(t, err)
paths := strset.New() paths := strset.New()

View file

@ -168,7 +168,7 @@ func (r *imageSquashResolver) FileContentsByLocation(location Location) (io.Read
return nil, fmt.Errorf("unable to get file contents for directory: %+v", location) return nil, fmt.Errorf("unable to get file contents for directory: %+v", location)
} }
return r.img.FileContentsByRef(location.ref) return r.img.OpenReference(location.ref)
} }
func (r *imageSquashResolver) AllLocations() <-chan Location { func (r *imageSquashResolver) AllLocations() <-chan Location {

View file

@ -0,0 +1,98 @@
package source
import (
"github.com/anchore/stereoscope/pkg/image"
"io"
)
var _ FileResolver = (*imageSquashWithAllLayersResolver)(nil)
// imageSquashWithAllLayersResolver acts like a squash resolver, but additionally returns all paths in earlier layers
// that have been added/modified (like the all-layers resolver).
type imageSquashWithAllLayersResolver struct {
squashed *imageSquashResolver
allLayers *imageAllLayersResolver
}
// newImageSquashWithAllLayersResolver returns a new resolver from the perspective of the squashed representation for
// the given image, but additionally returns all instances of a path that have been added/modified.
func newImageSquashWithAllLayersResolver(img *image.Image) (*imageSquashWithAllLayersResolver, error) {
squashed, err := newImageSquashResolver(img)
if err != nil {
return nil, err
}
allLayers, err := newImageAllLayersResolver(img)
if err != nil {
return nil, err
}
return &imageSquashWithAllLayersResolver{
squashed: squashed,
allLayers: allLayers,
}, nil
}
func (i imageSquashWithAllLayersResolver) FileContentsByLocation(location Location) (io.ReadCloser, error) {
return i.squashed.FileContentsByLocation(location)
}
func (i imageSquashWithAllLayersResolver) HasPath(s string) bool {
return i.squashed.HasPath(s)
}
func (i imageSquashWithAllLayersResolver) filterLocations(locations []Location, err error) ([]Location, error) {
if err != nil {
return locations, err
}
var ret []Location
for _, l := range locations {
if i.squashed.HasPath(l.RealPath) {
// not only should the real path to the file exist, but the way we took to get there should also exist
// (e.g. if we are looking for /etc/passwd, but the real path is /etc/passwd -> /etc/passwd-1, then we should
// make certain that /etc/passwd-1 exists)
if l.VirtualPath != "" && !i.squashed.HasPath(l.VirtualPath) {
continue
}
ret = append(ret, l)
}
}
return ret, nil
}
func (i imageSquashWithAllLayersResolver) FilesByPath(paths ...string) ([]Location, error) {
return i.filterLocations(i.allLayers.FilesByPath(paths...))
}
func (i imageSquashWithAllLayersResolver) FilesByGlob(patterns ...string) ([]Location, error) {
return i.filterLocations(i.allLayers.FilesByGlob(patterns...))
}
func (i imageSquashWithAllLayersResolver) FilesByMIMEType(types ...string) ([]Location, error) {
return i.filterLocations(i.allLayers.FilesByMIMEType(types...))
}
func (i imageSquashWithAllLayersResolver) RelativeFileByPath(l Location, path string) *Location {
if !i.squashed.HasPath(path) {
return nil
}
return i.allLayers.RelativeFileByPath(l, path)
}
func (i imageSquashWithAllLayersResolver) AllLocations() <-chan Location {
var ret = make(chan Location)
go func() {
defer close(ret)
for l := range i.allLayers.AllLocations() {
if i.squashed.HasPath(l.RealPath) {
ret <- l
}
}
}()
return ret
}
func (i imageSquashWithAllLayersResolver) FileMetadataByLocation(location Location) (FileMetadata, error) {
return fileMetadataByLocation(i.squashed.img, location)
}

View file

@ -10,14 +10,17 @@ const (
UnknownScope Scope = "UnknownScope" UnknownScope Scope = "UnknownScope"
// SquashedScope indicates to only catalog content visible from the squashed filesystem representation (what can be seen only within the container at runtime) // SquashedScope indicates to only catalog content visible from the squashed filesystem representation (what can be seen only within the container at runtime)
SquashedScope Scope = "Squashed" SquashedScope Scope = "Squashed"
// AllLayersScope indicates to catalog content on all layers, irregardless if it is visible from the container at runtime. // AllLayersScope indicates to catalog content on all layers, regardless if it is visible from the container at runtime.
AllLayersScope Scope = "AllLayers" AllLayersScope Scope = "AllLayers"
// SquashedWithAllLayersScope indicates to catalog content on all layers, but only include content visible from the squashed filesystem representation.
SquashedWithAllLayersScope Scope = "SquashedWithAllLayers"
) )
// AllScopes is a slice containing all possible scope options // AllScopes is a slice containing all possible scope options
var AllScopes = []Scope{ var AllScopes = []Scope{
SquashedScope, SquashedScope,
AllLayersScope, AllLayersScope,
SquashedWithAllLayersScope,
} }
// ParseScope returns a scope as indicated from the given string. // ParseScope returns a scope as indicated from the given string.
@ -27,6 +30,8 @@ func ParseScope(userStr string) Scope {
return SquashedScope return SquashedScope
case "all-layers", strings.ToLower(AllLayersScope.String()): case "all-layers", strings.ToLower(AllLayersScope.String()):
return AllLayersScope return AllLayersScope
case "squashed-with-all-layers", strings.ToLower(SquashedWithAllLayersScope.String()):
return SquashedWithAllLayersScope
} }
return UnknownScope return UnknownScope
} }

View file

@ -466,7 +466,9 @@ func (s *Source) FileResolver(scope Scope) (FileResolver, error) {
case SquashedScope: case SquashedScope:
resolver, err = newImageSquashResolver(s.Image) resolver, err = newImageSquashResolver(s.Image)
case AllLayersScope: case AllLayersScope:
resolver, err = newAllLayersResolver(s.Image) resolver, err = newImageAllLayersResolver(s.Image)
case SquashedWithAllLayersScope:
resolver, err = newImageSquashWithAllLayersResolver(s.Image)
default: default:
return nil, fmt.Errorf("bad image scope provided: %+v", scope) return nil, fmt.Errorf("bad image scope provided: %+v", scope)
} }