Add annotations for evidence on package locations (#1723)

* add location annotations + deb evidence annotations

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* rename LocationData struct and Annotation helper function

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* add failing integration test for evidence coverage

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* add evidence to aplm cataloger locations

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* change location annotation helper to return a location copy

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* add evidence to binary cataloger locations

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* updated remaining catalogers with location annotations

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* fix unit tests

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* fix linting

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* bump json schema

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* partial addressing of review comments

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* rename location.WithAnnotation

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

---------

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
Alex Goodman
2023-04-13 17:02:29 -04:00
committed by GitHub
parent 05715489c4
commit 5d156b8241
67 changed files with 2511 additions and 849 deletions
+44 -131
View File
@@ -765,30 +765,10 @@ func Test_directoryResolver_resolvesLinks(t *testing.T) {
return actualLocations
},
expected: []Location{
{
Coordinates: Coordinates{
RealPath: "file-1.txt",
},
//VirtualPath: "file-1.txt",
},
{
Coordinates: Coordinates{
RealPath: "file-3.txt",
},
//VirtualPath: "file-3.txt",
},
{
Coordinates: Coordinates{
RealPath: "file-2.txt",
},
//VirtualPath: "file-2.txt",
},
{
Coordinates: Coordinates{
RealPath: "parent/file-4.txt",
},
//VirtualPath: "parent/file-4.txt",
},
NewLocation("file-1.txt"), // note: missing virtual path "file-1.txt"
NewLocation("file-3.txt"), // note: missing virtual path "file-3.txt"
NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt"
NewLocation("parent/file-4.txt"), // note: missing virtual path "file-4.txt"
},
},
{
@@ -801,31 +781,11 @@ func Test_directoryResolver_resolvesLinks(t *testing.T) {
return actualLocations
},
expected: []Location{
{
Coordinates: Coordinates{
RealPath: "file-1.txt",
},
VirtualPath: "link-1",
},
{
Coordinates: Coordinates{
RealPath: "file-2.txt",
},
VirtualPath: "link-2",
},
NewVirtualLocation("file-1.txt", "link-1"),
NewVirtualLocation("file-2.txt", "link-2"),
// we already have this real file path via another link, so only one is returned
//{
// Coordinates: Coordinates{
// RealPath: "file-2.txt",
// },
// VirtualPath: "link-indirect",
//},
{
Coordinates: Coordinates{
RealPath: "file-3.txt",
},
VirtualPath: "link-within",
},
//NewVirtualLocation("file-2.txt", "link-indirect"),
NewVirtualLocation("file-3.txt", "link-within"),
},
},
{
@@ -838,12 +798,7 @@ func Test_directoryResolver_resolvesLinks(t *testing.T) {
},
expected: []Location{
// this has two copies in the base image, which overwrites the same location
{
Coordinates: Coordinates{
RealPath: "file-2.txt",
},
//VirtualPath: "file-2.txt",
},
NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt",
},
},
{
@@ -855,30 +810,10 @@ func Test_directoryResolver_resolvesLinks(t *testing.T) {
return actualLocations
},
expected: []Location{
{
Coordinates: Coordinates{
RealPath: "file-1.txt",
},
//VirtualPath: "file-1.txt",
},
{
Coordinates: Coordinates{
RealPath: "file-2.txt",
},
//VirtualPath: "file-2.txt",
},
{
Coordinates: Coordinates{
RealPath: "file-3.txt",
},
//VirtualPath: "file-3.txt",
},
{
Coordinates: Coordinates{
RealPath: "parent/file-4.txt",
},
//VirtualPath: "parent/file-4.txt",
},
NewLocation("file-1.txt"), // note: missing virtual path "file-1.txt"
NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt"
NewLocation("file-3.txt"), // note: missing virtual path "file-3.txt"
NewLocation("parent/file-4.txt"), // note: missing virtual path "parent/file-4.txt"
},
},
{
@@ -890,33 +825,41 @@ func Test_directoryResolver_resolvesLinks(t *testing.T) {
},
expected: []Location{
{
Coordinates: Coordinates{
RealPath: "file-1.txt",
LocationData: LocationData{
Coordinates: Coordinates{
RealPath: "file-1.txt",
},
VirtualPath: "link-1",
ref: file.Reference{RealPath: "file-1.txt"},
},
VirtualPath: "link-1",
ref: file.Reference{RealPath: "file-1.txt"},
},
{
Coordinates: Coordinates{
RealPath: "file-2.txt",
LocationData: LocationData{
Coordinates: Coordinates{
RealPath: "file-2.txt",
},
VirtualPath: "link-2",
ref: file.Reference{RealPath: "file-2.txt"},
},
VirtualPath: "link-2",
ref: file.Reference{RealPath: "file-2.txt"},
},
// we already have this real file path via another link, so only one is returned
//{
// Coordinates: Coordinates{
// RealPath: "file-2.txt",
// },
// VirtualPath: "link-indirect",
// ref: file.Reference{RealPath: "file-2.txt"},
// LocationData: LocationData{
// Coordinates: Coordinates{
// RealPath: "file-2.txt",
// },
// VirtualPath: "link-indirect",
// ref: file.Reference{RealPath: "file-2.txt"},
// },
//},
{
Coordinates: Coordinates{
RealPath: "file-3.txt",
LocationData: LocationData{
Coordinates: Coordinates{
RealPath: "file-3.txt",
},
VirtualPath: "link-within",
ref: file.Reference{RealPath: "file-3.txt"},
},
VirtualPath: "link-within",
ref: file.Reference{RealPath: "file-3.txt"},
},
},
},
@@ -929,30 +872,10 @@ func Test_directoryResolver_resolvesLinks(t *testing.T) {
return actualLocations
},
expected: []Location{
{
Coordinates: Coordinates{
RealPath: "file-1.txt",
},
//VirtualPath: "file-1.txt",
},
{
Coordinates: Coordinates{
RealPath: "file-2.txt",
},
//VirtualPath: "file-2.txt",
},
{
Coordinates: Coordinates{
RealPath: "file-3.txt",
},
//VirtualPath: "file-3.txt",
},
{
Coordinates: Coordinates{
RealPath: "parent/file-4.txt",
},
//VirtualPath: "parent/file-4.txt",
},
NewLocation("file-1.txt"), // note: missing virtual path "file-1.txt"
NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt"
NewLocation("file-3.txt"), // note: missing virtual path "file-3.txt"
NewLocation("parent/file-4.txt"), // note: missing virtual path "parent/file-4.txt"
},
},
{
@@ -965,12 +888,7 @@ func Test_directoryResolver_resolvesLinks(t *testing.T) {
},
expected: []Location{
// we have multiple copies across layers
{
Coordinates: Coordinates{
RealPath: "file-2.txt",
},
VirtualPath: "link-2",
},
NewVirtualLocation("file-2.txt", "link-2"),
},
},
{
@@ -983,12 +901,7 @@ func Test_directoryResolver_resolvesLinks(t *testing.T) {
},
expected: []Location{
// we have multiple copies across layers
{
Coordinates: Coordinates{
RealPath: "file-2.txt",
},
VirtualPath: "link-indirect",
},
NewVirtualLocation("file-2.txt", "link-indirect"),
},
},
}
+9 -25
View File
@@ -6,8 +6,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/anchore/stereoscope/pkg/file"
)
func TestExcludingResolver(t *testing.T) {
@@ -79,25 +77,25 @@ func TestExcludingResolver(t *testing.T) {
for _, path := range diff {
assert.False(t, er.HasPath(path))
c, err := er.FileContentsByLocation(makeLocation(path))
c, err := er.FileContentsByLocation(NewLocation(path))
assert.Nil(t, c)
assert.Error(t, err)
m, err := er.FileMetadataByLocation(makeLocation(path))
m, err := er.FileMetadataByLocation(NewLocation(path))
assert.Empty(t, m.LinkDestination)
assert.Error(t, err)
l := er.RelativeFileByPath(makeLocation(""), path)
l := er.RelativeFileByPath(NewLocation(""), path)
assert.Nil(t, l)
}
for _, path := range test.expected {
assert.True(t, er.HasPath(path))
c, err := er.FileContentsByLocation(makeLocation(path))
c, err := er.FileContentsByLocation(NewLocation(path))
assert.NotNil(t, c)
assert.Nil(t, err)
m, err := er.FileMetadataByLocation(makeLocation(path))
m, err := er.FileMetadataByLocation(NewLocation(path))
assert.NotEmpty(t, m.LinkDestination)
assert.Nil(t, err)
l := er.RelativeFileByPath(makeLocation(""), path)
l := er.RelativeFileByPath(NewLocation(""), path)
assert.NotNil(t, l)
}
})
@@ -119,17 +117,6 @@ func difference(a, b []string) []string {
return diff
}
func makeLocation(path string) Location {
return Location{
Coordinates: Coordinates{
RealPath: path,
FileSystemID: "",
},
VirtualPath: "",
ref: file.Reference{},
}
}
func locationPaths(locations []Location) []string {
paths := []string{}
for _, l := range locations {
@@ -145,7 +132,7 @@ type mockResolver struct {
func (r *mockResolver) getLocations() ([]Location, error) {
out := []Location{}
for _, path := range r.locations {
out = append(out, makeLocation(path))
out = append(out, NewLocation(path))
}
return out, nil
}
@@ -189,11 +176,8 @@ func (r *mockResolver) FilesByBasenameGlob(_ ...string) ([]Location, error) {
}
func (r *mockResolver) RelativeFileByPath(_ Location, path string) *Location {
return &Location{
Coordinates: Coordinates{
RealPath: path,
},
}
l := NewLocation(path)
return &l
}
func (r *mockResolver) AllLocations() <-chan Location {
+31 -200
View File
@@ -398,66 +398,17 @@ func Test_imageAllLayersResolver_resolvesLinks(t *testing.T) {
return actualLocations
},
expected: []Location{
{
Coordinates: Coordinates{
RealPath: "/etc/group",
},
VirtualPath: "/etc/group",
},
{
Coordinates: Coordinates{
RealPath: "/etc/passwd",
},
VirtualPath: "/etc/passwd",
},
{
Coordinates: Coordinates{
RealPath: "/etc/shadow",
},
VirtualPath: "/etc/shadow",
},
{
Coordinates: Coordinates{
RealPath: "/file-1.txt",
},
VirtualPath: "/file-1.txt",
},
// copy 1
{
Coordinates: Coordinates{
RealPath: "/file-2.txt",
},
VirtualPath: "/file-2.txt",
},
NewVirtualLocation("/etc/group", "/etc/group"),
NewVirtualLocation("/etc/passwd", "/etc/passwd"),
NewVirtualLocation("/etc/shadow", "/etc/shadow"),
NewVirtualLocation("/file-1.txt", "/file-1.txt"),
NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 1
// note: we're de-duping the redundant access to file-3.txt
// ... (there would usually be two copies)
{
Coordinates: Coordinates{
RealPath: "/file-3.txt",
},
VirtualPath: "/file-3.txt",
},
// copy 2
{
Coordinates: Coordinates{
RealPath: "/file-2.txt",
},
VirtualPath: "/file-2.txt",
},
// copy 1
{
Coordinates: Coordinates{
RealPath: "/parent/file-4.txt",
},
VirtualPath: "/parent/file-4.txt",
},
// copy 2
{
Coordinates: Coordinates{
RealPath: "/parent/file-4.txt",
},
VirtualPath: "/parent/file-4.txt",
},
NewVirtualLocation("/file-3.txt", "/file-3.txt"),
NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 2
NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), // copy 1
NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), // copy 2
},
},
{
@@ -469,32 +420,10 @@ func Test_imageAllLayersResolver_resolvesLinks(t *testing.T) {
return actualLocations
},
expected: []Location{
{
Coordinates: Coordinates{
RealPath: "/file-1.txt",
},
VirtualPath: "/link-1",
},
// copy 1
{
Coordinates: Coordinates{
RealPath: "/file-2.txt",
},
VirtualPath: "/link-2",
},
// copy 2
{
Coordinates: Coordinates{
RealPath: "/file-2.txt",
},
VirtualPath: "/link-2",
},
{
Coordinates: Coordinates{
RealPath: "/file-3.txt",
},
VirtualPath: "/link-within",
},
NewVirtualLocation("/file-1.txt", "/link-1"),
NewVirtualLocation("/file-2.txt", "/link-2"), // copy 1
NewVirtualLocation("/file-2.txt", "/link-2"), // copy 2
NewVirtualLocation("/file-3.txt", "/link-within"),
},
},
{
@@ -506,20 +435,8 @@ func Test_imageAllLayersResolver_resolvesLinks(t *testing.T) {
return actualLocations
},
expected: []Location{
// copy 1
{
Coordinates: Coordinates{
RealPath: "/file-2.txt",
},
VirtualPath: "/file-2.txt",
},
// copy 2
{
Coordinates: Coordinates{
RealPath: "/file-2.txt",
},
VirtualPath: "/file-2.txt",
},
NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 1
NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 2
},
},
{
@@ -531,45 +448,12 @@ func Test_imageAllLayersResolver_resolvesLinks(t *testing.T) {
return actualLocations
},
expected: []Location{
{
Coordinates: Coordinates{
RealPath: "/file-1.txt",
},
VirtualPath: "/file-1.txt",
},
// copy 1
{
Coordinates: Coordinates{
RealPath: "/file-2.txt",
},
VirtualPath: "/file-2.txt",
},
// copy 2
{
Coordinates: Coordinates{
RealPath: "/file-2.txt",
},
VirtualPath: "/file-2.txt",
},
{
Coordinates: Coordinates{
RealPath: "/file-3.txt",
},
VirtualPath: "/file-3.txt",
},
{
Coordinates: Coordinates{
RealPath: "/parent/file-4.txt",
},
VirtualPath: "/parent/file-4.txt",
},
// when we copy into the link path, the same file-4.txt is copied
{
Coordinates: Coordinates{
RealPath: "/parent/file-4.txt",
},
VirtualPath: "/parent/file-4.txt",
},
NewVirtualLocation("/file-1.txt", "/file-1.txt"),
NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 1
NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 2
NewVirtualLocation("/file-3.txt", "/file-3.txt"),
NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"),
NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), // when we copy into the link path, the same file-4.txt is copied
},
},
{
@@ -581,45 +465,12 @@ func Test_imageAllLayersResolver_resolvesLinks(t *testing.T) {
return actualLocations
},
expected: []Location{
{
Coordinates: Coordinates{
RealPath: "/file-1.txt",
},
VirtualPath: "/file-1.txt",
},
// copy 1
{
Coordinates: Coordinates{
RealPath: "/file-2.txt",
},
VirtualPath: "/file-2.txt",
},
// copy 2
{
Coordinates: Coordinates{
RealPath: "/file-2.txt",
},
VirtualPath: "/file-2.txt",
},
{
Coordinates: Coordinates{
RealPath: "/file-3.txt",
},
VirtualPath: "/file-3.txt",
},
{
Coordinates: Coordinates{
RealPath: "/parent/file-4.txt",
},
VirtualPath: "/parent/file-4.txt",
},
// when we copy into the link path, the same file-4.txt is copied
{
Coordinates: Coordinates{
RealPath: "/parent/file-4.txt",
},
VirtualPath: "/parent/file-4.txt",
},
NewVirtualLocation("/file-1.txt", "/file-1.txt"),
NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 1
NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 2
NewVirtualLocation("/file-3.txt", "/file-3.txt"),
NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"),
NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), // when we copy into the link path, the same file-4.txt is copied
},
},
{
@@ -632,18 +483,8 @@ func Test_imageAllLayersResolver_resolvesLinks(t *testing.T) {
},
expected: []Location{
// we have multiple copies across layers
{
Coordinates: Coordinates{
RealPath: "/file-2.txt",
},
VirtualPath: "/link-2",
},
{
Coordinates: Coordinates{
RealPath: "/file-2.txt",
},
VirtualPath: "/link-2",
},
NewVirtualLocation("/file-2.txt", "/link-2"),
NewVirtualLocation("/file-2.txt", "/link-2"),
},
},
{
@@ -656,18 +497,8 @@ func Test_imageAllLayersResolver_resolvesLinks(t *testing.T) {
},
expected: []Location{
// we have multiple copies across layers
{
Coordinates: Coordinates{
RealPath: "/file-2.txt",
},
VirtualPath: "/link-indirect",
},
{
Coordinates: Coordinates{
RealPath: "/file-2.txt",
},
VirtualPath: "/link-indirect",
},
NewVirtualLocation("/file-2.txt", "/link-indirect"),
NewVirtualLocation("/file-2.txt", "/link-indirect"),
},
},
}
+54 -150
View File
@@ -382,48 +382,13 @@ func Test_imageSquashResolver_resolvesLinks(t *testing.T) {
return actualLocations
},
expected: []Location{
{
Coordinates: Coordinates{
RealPath: "/etc/group",
},
VirtualPath: "/etc/group",
},
{
Coordinates: Coordinates{
RealPath: "/etc/passwd",
},
VirtualPath: "/etc/passwd",
},
{
Coordinates: Coordinates{
RealPath: "/etc/shadow",
},
VirtualPath: "/etc/shadow",
},
{
Coordinates: Coordinates{
RealPath: "/file-1.txt",
},
VirtualPath: "/file-1.txt",
},
{
Coordinates: Coordinates{
RealPath: "/file-3.txt",
},
VirtualPath: "/file-3.txt",
},
{
Coordinates: Coordinates{
RealPath: "/file-2.txt",
},
VirtualPath: "/file-2.txt",
},
{
Coordinates: Coordinates{
RealPath: "/parent/file-4.txt",
},
VirtualPath: "/parent/file-4.txt",
},
NewVirtualLocation("/etc/group", "/etc/group"),
NewVirtualLocation("/etc/passwd", "/etc/passwd"),
NewVirtualLocation("/etc/shadow", "/etc/shadow"),
NewVirtualLocation("/file-1.txt", "/file-1.txt"),
NewVirtualLocation("/file-3.txt", "/file-3.txt"),
NewVirtualLocation("/file-2.txt", "/file-2.txt"),
NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"),
},
},
{
@@ -435,32 +400,14 @@ func Test_imageSquashResolver_resolvesLinks(t *testing.T) {
return actualLocations
},
expected: []Location{
{
Coordinates: Coordinates{
RealPath: "/file-1.txt",
},
VirtualPath: "/link-1",
},
{
Coordinates: Coordinates{
RealPath: "/file-2.txt",
},
VirtualPath: "/link-2",
},
NewVirtualLocation("/file-1.txt", "/link-1"),
NewVirtualLocation("/file-2.txt", "/link-2"),
// though this is a link, and it matches to the file, the resolver de-duplicates files
// by the real path, so it is not included in the results
//{
// Coordinates: Coordinates{
// RealPath: "/file-2.txt",
// },
// VirtualPath: "/link-indirect",
//},
{
Coordinates: Coordinates{
RealPath: "/file-3.txt",
},
VirtualPath: "/link-within",
},
//NewVirtualLocation("/file-2.txt", "/link-indirect"),
NewVirtualLocation("/file-3.txt", "/link-within"),
},
},
{
@@ -473,12 +420,7 @@ func Test_imageSquashResolver_resolvesLinks(t *testing.T) {
},
expected: []Location{
// this has two copies in the base image, which overwrites the same location
{
Coordinates: Coordinates{
RealPath: "/file-2.txt",
},
VirtualPath: "/file-2.txt",
},
NewVirtualLocation("/file-2.txt", "/file-2.txt"),
},
},
{
@@ -490,30 +432,10 @@ func Test_imageSquashResolver_resolvesLinks(t *testing.T) {
return actualLocations
},
expected: []Location{
{
Coordinates: Coordinates{
RealPath: "/file-1.txt",
},
VirtualPath: "/file-1.txt",
},
{
Coordinates: Coordinates{
RealPath: "/file-2.txt",
},
VirtualPath: "/file-2.txt",
},
{
Coordinates: Coordinates{
RealPath: "/file-3.txt",
},
VirtualPath: "/file-3.txt",
},
{
Coordinates: Coordinates{
RealPath: "/parent/file-4.txt",
},
VirtualPath: "/parent/file-4.txt",
},
NewVirtualLocation("/file-1.txt", "/file-1.txt"),
NewVirtualLocation("/file-2.txt", "/file-2.txt"),
NewVirtualLocation("/file-3.txt", "/file-3.txt"),
NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"),
},
},
{
@@ -524,34 +446,44 @@ func Test_imageSquashResolver_resolvesLinks(t *testing.T) {
return actualLocations
},
expected: []Location{
{
Coordinates: Coordinates{
RealPath: "/file-1.txt",
LocationData: LocationData{
Coordinates: Coordinates{
RealPath: "/file-1.txt",
},
VirtualPath: "/link-1",
ref: file.Reference{RealPath: "/file-1.txt"},
},
VirtualPath: "/link-1",
ref: file.Reference{RealPath: "/file-1.txt"},
},
{
Coordinates: Coordinates{
RealPath: "/file-2.txt",
LocationData: LocationData{
Coordinates: Coordinates{
RealPath: "/file-2.txt",
},
VirtualPath: "/link-2",
ref: file.Reference{RealPath: "/file-2.txt"},
},
VirtualPath: "/link-2",
ref: file.Reference{RealPath: "/file-2.txt"},
},
// we already have this real file path via another link, so only one is returned
//{
// Coordinates: Coordinates{
// RealPath: "/file-2.txt",
// },
// VirtualPath: "/link-indirect",
// ref: file.Reference{RealPath: "/file-2.txt"},
// LocationData: LocationData{
// Coordinates: Coordinates{
// RealPath: "/file-2.txt",
// },
// VirtualPath: "/link-indirect",
// ref: file.Reference{RealPath: "/file-2.txt"},
// },
//},
{
Coordinates: Coordinates{
RealPath: "/file-3.txt",
LocationData: LocationData{
Coordinates: Coordinates{
RealPath: "/file-3.txt",
},
VirtualPath: "/link-within",
ref: file.Reference{RealPath: "/file-3.txt"},
},
VirtualPath: "/link-within",
ref: file.Reference{RealPath: "/file-3.txt"},
},
},
},
@@ -564,30 +496,10 @@ func Test_imageSquashResolver_resolvesLinks(t *testing.T) {
return actualLocations
},
expected: []Location{
{
Coordinates: Coordinates{
RealPath: "/file-1.txt",
},
VirtualPath: "/file-1.txt",
},
{
Coordinates: Coordinates{
RealPath: "/file-2.txt",
},
VirtualPath: "/file-2.txt",
},
{
Coordinates: Coordinates{
RealPath: "/file-3.txt",
},
VirtualPath: "/file-3.txt",
},
{
Coordinates: Coordinates{
RealPath: "/parent/file-4.txt",
},
VirtualPath: "/parent/file-4.txt",
},
NewVirtualLocation("/file-1.txt", "/file-1.txt"),
NewVirtualLocation("/file-2.txt", "/file-2.txt"),
NewVirtualLocation("/file-3.txt", "/file-3.txt"),
NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"),
},
},
{
@@ -600,12 +512,7 @@ func Test_imageSquashResolver_resolvesLinks(t *testing.T) {
},
expected: []Location{
// we have multiple copies across layers
{
Coordinates: Coordinates{
RealPath: "/file-2.txt",
},
VirtualPath: "/link-2",
},
NewVirtualLocation("/file-2.txt", "/link-2"),
},
},
{
@@ -618,12 +525,7 @@ func Test_imageSquashResolver_resolvesLinks(t *testing.T) {
},
expected: []Location{
// we have multiple copies across layers
{
Coordinates: Coordinates{
RealPath: "/file-2.txt",
},
VirtualPath: "/link-indirect",
},
NewVirtualLocation("/file-2.txt", "/link-indirect"),
},
},
}
@@ -646,7 +548,8 @@ func Test_imageSquashResolver_resolvesLinks(t *testing.T) {
func compareLocations(t *testing.T, expected, actual []Location) {
t.Helper()
ignoreUnexported := cmpopts.IgnoreFields(Location{}, "ref")
ignoreUnexported := cmpopts.IgnoreFields(LocationData{}, "ref")
ignoreMetadata := cmpopts.IgnoreFields(LocationMetadata{}, "Annotations")
ignoreFS := cmpopts.IgnoreFields(Coordinates{}, "FileSystemID")
sort.Sort(Locations(expected))
@@ -655,6 +558,7 @@ func compareLocations(t *testing.T, expected, actual []Location) {
if d := cmp.Diff(expected, actual,
ignoreUnexported,
ignoreFS,
ignoreMetadata,
); d != "" {
t.Errorf("unexpected locations (-want +got):\n%s", d)
+96 -22
View File
@@ -3,6 +3,8 @@ package source
import (
"fmt"
"github.com/hashicorp/go-multierror"
"github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/stereoscope/pkg/image"
)
@@ -10,18 +12,55 @@ import (
// Location represents a path relative to a particular filesystem resolved to a specific file.Reference. This struct is used as a key
// in content fetching to uniquely identify a file relative to a request (the VirtualPath).
type Location struct {
LocationData `cyclonedx:""`
LocationMetadata `cyclonedx:""`
}
type LocationData struct {
Coordinates `cyclonedx:""` // Empty string here means there is no intermediate property name, e.g. syft:locations:0:path without "coordinates"
// note: it is IMPORTANT to ignore anything but the coordinates for a Location when considering the ID (hash value)
// since the coordinates are the minimally correct ID for a location (symlinks should not come into play)
VirtualPath string `hash:"ignore" json:"virtualPath,omitempty"` // The path to the file which may or may not have hardlinks / symlinks
ref file.Reference `hash:"ignore"` // The file reference relative to the stereoscope.FileCatalog that has more information about this location.
VirtualPath string `hash:"ignore" json:"-"` // The path to the file which may or may not have hardlinks / symlinks
ref file.Reference `hash:"ignore"` // The file reference relative to the stereoscope.FileCatalog that has more information about this location.
}
type LocationMetadata struct {
Annotations map[string]string `json:"annotations,omitempty"` // Arbitrary key-value pairs that can be used to annotate a location
}
func (m *LocationMetadata) merge(other LocationMetadata) error {
var errs error
for k, v := range other.Annotations {
if otherV, ok := m.Annotations[k]; ok {
if v != otherV {
err := fmt.Errorf("unable to merge location metadata: conflicting values for key=%q: %q != %q", k, v, otherV)
errs = multierror.Append(errs, err)
continue
}
}
m.Annotations[k] = v
}
return errs
}
func (l Location) WithAnnotation(key, value string) Location {
if l.LocationMetadata.Annotations == nil {
l.LocationMetadata.Annotations = map[string]string{}
}
l.LocationMetadata.Annotations[key] = value
return l
}
// NewLocation creates a new Location representing a path without denoting a filesystem or FileCatalog reference.
func NewLocation(realPath string) Location {
return Location{
Coordinates: Coordinates{
RealPath: realPath,
LocationData: LocationData{
Coordinates: Coordinates{
RealPath: realPath,
},
},
LocationMetadata: LocationMetadata{
Annotations: map[string]string{},
},
}
}
@@ -29,40 +68,70 @@ func NewLocation(realPath string) Location {
// NewVirtualLocation creates a new location for a path accessed by a virtual path (a path with a symlink or hardlink somewhere in the path)
func NewVirtualLocation(realPath, virtualPath string) Location {
return Location{
Coordinates: Coordinates{
RealPath: realPath,
LocationData: LocationData{
Coordinates: Coordinates{
RealPath: realPath,
},
VirtualPath: virtualPath,
},
VirtualPath: virtualPath,
}
LocationMetadata: LocationMetadata{
Annotations: map[string]string{},
}}
}
// NewLocationFromCoordinates creates a new location for the given Coordinates.
func NewLocationFromCoordinates(coordinates Coordinates) Location {
return Location{
Coordinates: coordinates,
}
LocationData: LocationData{
Coordinates: coordinates,
},
LocationMetadata: LocationMetadata{
Annotations: map[string]string{},
}}
}
// NewVirtualLocationFromCoordinates creates a new location for the given Coordinates via a virtual path.
func NewVirtualLocationFromCoordinates(coordinates Coordinates, virtualPath string) Location {
return Location{
LocationData: LocationData{
Coordinates: coordinates,
VirtualPath: virtualPath,
},
LocationMetadata: LocationMetadata{
Annotations: map[string]string{},
}}
}
// NewLocationFromImage creates a new Location representing the given path (extracted from the ref) relative to the given image.
func NewLocationFromImage(virtualPath string, ref file.Reference, img *image.Image) Location {
layer := img.FileCatalog.Layer(ref)
return Location{
Coordinates: Coordinates{
RealPath: string(ref.RealPath),
FileSystemID: layer.Metadata.Digest,
LocationData: LocationData{
Coordinates: Coordinates{
RealPath: string(ref.RealPath),
FileSystemID: layer.Metadata.Digest,
},
VirtualPath: virtualPath,
ref: ref,
},
LocationMetadata: LocationMetadata{
Annotations: map[string]string{},
},
VirtualPath: virtualPath,
ref: ref,
}
}
// NewLocationFromDirectory creates a new Location representing the given path (extracted from the ref) relative to the given directory.
func NewLocationFromDirectory(responsePath string, ref file.Reference) Location {
return Location{
Coordinates: Coordinates{
RealPath: responsePath,
LocationData: LocationData{
Coordinates: Coordinates{
RealPath: responsePath,
},
ref: ref,
},
LocationMetadata: LocationMetadata{
Annotations: map[string]string{},
},
ref: ref,
}
}
@@ -72,11 +141,16 @@ func NewVirtualLocationFromDirectory(responsePath, virtualResponsePath string, r
return NewLocationFromDirectory(responsePath, ref)
}
return Location{
Coordinates: Coordinates{
RealPath: responsePath,
LocationData: LocationData{
Coordinates: Coordinates{
RealPath: responsePath,
},
VirtualPath: virtualResponsePath,
ref: ref,
},
LocationMetadata: LocationMetadata{
Annotations: map[string]string{},
},
VirtualPath: virtualResponsePath,
ref: ref,
}
}
+20 -7
View File
@@ -4,10 +4,12 @@ import (
"sort"
"github.com/mitchellh/hashstructure/v2"
"github.com/anchore/syft/internal/log"
)
type LocationSet struct {
set map[Location]struct{}
set map[LocationData]LocationMetadata
}
func NewLocationSet(locations ...Location) (s LocationSet) {
@@ -20,10 +22,18 @@ func NewLocationSet(locations ...Location) (s LocationSet) {
func (s *LocationSet) Add(locations ...Location) {
if s.set == nil {
s.set = make(map[Location]struct{})
s.set = make(map[LocationData]LocationMetadata)
}
for _, l := range locations {
s.set[l] = struct{}{}
if m, ok := s.set[l.LocationData]; ok {
err := m.merge(l.LocationMetadata)
if err != nil {
log.Debugf("partial merge of location metadata: %+v", err)
}
s.set[l.LocationData] = m
} else {
s.set[l.LocationData] = l.LocationMetadata
}
}
}
@@ -32,7 +42,7 @@ func (s LocationSet) Remove(locations ...Location) {
return
}
for _, l := range locations {
delete(s.set, l)
delete(s.set, l.LocationData)
}
}
@@ -40,7 +50,7 @@ func (s LocationSet) Contains(l Location) bool {
if s.set == nil {
return false
}
_, ok := s.set[l]
_, ok := s.set[l.LocationData]
return ok
}
@@ -50,8 +60,11 @@ func (s LocationSet) ToSlice() []Location {
}
locations := make([]Location, len(s.set))
idx := 0
for v := range s.set {
locations[idx] = v
for dir := range s.set {
locations[idx] = Location{
LocationData: dir,
LocationMetadata: s.set[dir],
}
idx++
}
sort.Sort(Locations(locations))
+52 -34
View File
@@ -12,35 +12,43 @@ import (
func TestLocationSet(t *testing.T) {
etcHostsLinkVar := Location{
Coordinates: Coordinates{
RealPath: "/etc/hosts",
FileSystemID: "a",
LocationData: LocationData{
Coordinates: Coordinates{
RealPath: "/etc/hosts",
FileSystemID: "a",
},
VirtualPath: "/var/etc/hosts",
},
VirtualPath: "/var/etc/hosts",
}
etcHostsLinkHome := Location{
Coordinates: Coordinates{
RealPath: "/etc/hosts",
FileSystemID: "a",
LocationData: LocationData{
Coordinates: Coordinates{
RealPath: "/etc/hosts",
FileSystemID: "a",
},
VirtualPath: "/home/wagoodman/hosts",
},
VirtualPath: "/home/wagoodman/hosts",
}
binA := Location{
Coordinates: Coordinates{
RealPath: "/bin",
FileSystemID: "a",
LocationData: LocationData{
Coordinates: Coordinates{
RealPath: "/bin",
FileSystemID: "a",
},
VirtualPath: "/usr/bin",
},
VirtualPath: "/usr/bin",
}
binB := Location{
Coordinates: Coordinates{
RealPath: "/bin",
FileSystemID: "b",
LocationData: LocationData{
Coordinates: Coordinates{
RealPath: "/bin",
FileSystemID: "b",
},
VirtualPath: "/usr/bin",
},
VirtualPath: "/usr/bin",
}
tests := []struct {
@@ -87,41 +95,51 @@ func TestLocationSet(t *testing.T) {
func TestLocationSet_Hash(t *testing.T) {
etcAlink := Location{
Coordinates: Coordinates{
RealPath: "/etc/hosts",
FileSystemID: "a",
LocationData: LocationData{
Coordinates: Coordinates{
RealPath: "/etc/hosts",
FileSystemID: "a",
},
VirtualPath: "/var/etc/hosts",
},
VirtualPath: "/var/etc/hosts",
}
etcA := Location{
Coordinates: Coordinates{
RealPath: "/etc/hosts",
FileSystemID: "a",
LocationData: LocationData{
Coordinates: Coordinates{
RealPath: "/etc/hosts",
FileSystemID: "a",
},
},
}
etcB := Location{
Coordinates: Coordinates{
RealPath: "/etc/hosts",
FileSystemID: "b",
LocationData: LocationData{
Coordinates: Coordinates{
RealPath: "/etc/hosts",
FileSystemID: "b",
},
},
}
binA := Location{
Coordinates: Coordinates{
RealPath: "/bin",
FileSystemID: "a",
LocationData: LocationData{
Coordinates: Coordinates{
RealPath: "/bin",
FileSystemID: "a",
},
VirtualPath: "/usr/bin",
},
VirtualPath: "/usr/bin",
}
binB := Location{
Coordinates: Coordinates{
RealPath: "/bin",
FileSystemID: "b",
LocationData: LocationData{
Coordinates: Coordinates{
RealPath: "/bin",
FileSystemID: "b",
},
VirtualPath: "/usr/bin",
},
VirtualPath: "/usr/bin",
}
tests := []struct {
+5 -3
View File
@@ -37,9 +37,11 @@ func TestLocation_ID(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
l := Location{
Coordinates: test.coordinates,
VirtualPath: test.virtualPath,
ref: test.ref,
LocationData: LocationData{
Coordinates: test.coordinates,
VirtualPath: test.virtualPath,
ref: test.ref,
},
}
assert.Equal(t, l.ID(), test.coordinates.ID())
})
+6 -5
View File
@@ -18,7 +18,7 @@ var _ FileResolver = (*MockResolver)(nil)
// paths, which are typically paths to test fixtures.
type MockResolver struct {
locations []Location
metadata map[Location]FileMetadata
metadata map[Coordinates]FileMetadata
mimeTypeIndex map[string][]Location
extension map[string][]Location
basename map[string][]Location
@@ -41,18 +41,19 @@ func NewMockResolverForPaths(paths ...string) *MockResolver {
return &MockResolver{
locations: locations,
metadata: make(map[Location]FileMetadata),
metadata: make(map[Coordinates]FileMetadata),
extension: extension,
basename: basename,
}
}
func NewMockResolverForPathsWithMetadata(metadata map[Location]FileMetadata) *MockResolver {
func NewMockResolverForPathsWithMetadata(metadata map[Coordinates]FileMetadata) *MockResolver {
var locations []Location
var mimeTypeIndex = make(map[string][]Location)
extension := make(map[string][]Location)
basename := make(map[string][]Location)
for l, m := range metadata {
for c, m := range metadata {
l := NewLocationFromCoordinates(c)
locations = append(locations, l)
mimeTypeIndex[m.MIMEType] = append(mimeTypeIndex[m.MIMEType], l)
ext := path.Ext(l.RealPath)
@@ -89,7 +90,7 @@ func (r MockResolver) String() string {
// path does not exist, an error is returned.
func (r MockResolver) FileContentsByLocation(location Location) (io.ReadCloser, error) {
for _, l := range r.locations {
if l == location {
if l.Coordinates == location.Coordinates {
return os.Open(location.RealPath)
}
}