mirror of
https://github.com/anchore/syft.git
synced 2025-11-18 00:43:20 +01:00
Merge pull request #251 from anchore/add-rpm-file-info
Add RPM file info sourced from the RPM DB
This commit is contained in:
commit
b6eb589b78
6
go.mod
6
go.mod
@ -4,14 +4,14 @@ go 1.14
|
||||
|
||||
require (
|
||||
github.com/adrg/xdg v0.2.1
|
||||
github.com/anchore/go-rpmdb v0.0.0-20200811175839-cbc751c28e8e
|
||||
github.com/anchore/go-rpmdb v0.0.0-20201106153645-0043963c2e12
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||
github.com/anchore/stereoscope v0.0.0-20200925184903-c82da54e98fe
|
||||
github.com/anchore/stereoscope v0.0.0-20201106140100-12e75c48f409
|
||||
github.com/bmatcuk/doublestar v1.3.3
|
||||
github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/go-test/deep v1.0.6
|
||||
github.com/go-test/deep v1.0.7
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/gookit/color v1.2.7
|
||||
github.com/hashicorp/go-multierror v1.1.0
|
||||
|
||||
6
go.sum
6
go.sum
@ -126,12 +126,16 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/anchore/go-rpmdb v0.0.0-20200811175839-cbc751c28e8e h1:kty6r0R2JeaNPeWKSYDC+HW3hkqwFh4PP5TQ8pUPYFw=
|
||||
github.com/anchore/go-rpmdb v0.0.0-20200811175839-cbc751c28e8e/go.mod h1:iYuIG0Nai8dR0ri3LhZQKUyO1loxUWAGvoWhXDmjy1A=
|
||||
github.com/anchore/go-rpmdb v0.0.0-20201106153645-0043963c2e12 h1:xbeIbn5F52JVx3RUIajxCj8b0y+9lywspql4sFhcxWQ=
|
||||
github.com/anchore/go-rpmdb v0.0.0-20201106153645-0043963c2e12/go.mod h1:juoyWXIj7sJ1IDl4E/KIfyLtovbs5XQVSIdaQifFQT8=
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 h1:VzprUTpc0vW0nnNKJfJieyH/TZ9UYAnTZs5/gHTdAe8=
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04/go.mod h1:6dK64g27Qi1qGQZ67gFmBFvEHScy0/C8qhQhNe5B5pQ=
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZVsCYMrIZBpFxwV26CbsuoEh5muXD5I1Ods=
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
|
||||
github.com/anchore/stereoscope v0.0.0-20200925184903-c82da54e98fe h1:m4NSyTo2fVUoUHAV/ZVqE/PFMr/y8oz9HRrhWLk9It0=
|
||||
github.com/anchore/stereoscope v0.0.0-20200925184903-c82da54e98fe/go.mod h1:2Jja/4l0zYggW52og+nn0rut4i+OYjCf9vTyrM8RT4E=
|
||||
github.com/anchore/stereoscope v0.0.0-20201106140100-12e75c48f409 h1:xKSpDRjmYrEFrdMeDh4AuSUAFc99pdro6YFBKxy2um0=
|
||||
github.com/anchore/stereoscope v0.0.0-20201106140100-12e75c48f409/go.mod h1:2Jja/4l0zYggW52og+nn0rut4i+OYjCf9vTyrM8RT4E=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/apex/log v1.1.4/go.mod h1:AlpoD9aScyQfJDVHmLMEcx4oU6LqzkWp4Mg9GdAcEvQ=
|
||||
github.com/apex/log v1.3.0 h1:1fyfbPvUwD10nMoh3hY6MXzvZShJQn9/ck7ATgAt5pA=
|
||||
@ -299,6 +303,8 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-test/deep v1.0.6 h1:UHSEyLZUwX9Qoi99vVwvewiMC8mM2bf7XEM2nqvzEn8=
|
||||
github.com/go-test/deep v1.0.6/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
|
||||
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
|
||||
github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
|
||||
github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g=
|
||||
github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
|
||||
github.com/go-toolsmith/astcopy v1.0.0 h1:OMgl1b1MEpjFQ1m5ztEO06rz5CUd3oBv9RF7+DyvdG8=
|
||||
|
||||
@ -4,13 +4,47 @@ Package rpmdb provides a concrete Cataloger implementation for RPM "Package" DB
|
||||
package rpmdb
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/cataloger/common"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/scope"
|
||||
)
|
||||
|
||||
const (
|
||||
packagesGlob = "**/var/lib/rpm/Packages"
|
||||
)
|
||||
|
||||
type Cataloger struct{}
|
||||
|
||||
// NewRpmdbCataloger returns a new RPM DB cataloger object.
|
||||
func NewRpmdbCataloger() *common.GenericCataloger {
|
||||
globParsers := map[string]common.ParserFn{
|
||||
"**/var/lib/rpm/Packages": parseRpmDB,
|
||||
}
|
||||
return common.NewGenericCataloger(nil, globParsers, "rpmdb-cataloger")
|
||||
func NewRpmdbCataloger() *Cataloger {
|
||||
return &Cataloger{}
|
||||
}
|
||||
|
||||
// Name returns a string that uniquely describes a cataloger
|
||||
func (c *Cataloger) Name() string {
|
||||
return "rpmdb-cataloger"
|
||||
}
|
||||
|
||||
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing rpm db installation.
|
||||
func (c *Cataloger) Catalog(resolver scope.Resolver) ([]pkg.Package, error) {
|
||||
fileMatches, err := resolver.FilesByGlob(packagesGlob)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find rpmdb's by glob: %w", err)
|
||||
}
|
||||
|
||||
var pkgs []pkg.Package
|
||||
for _, ref := range fileMatches {
|
||||
dbContents, err := resolver.FileContentsByRef(ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pkgs, err = parseRpmDB(resolver, strings.NewReader(dbContents))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to catalog rpmdb package=%+v: %w", ref.Path, err)
|
||||
}
|
||||
}
|
||||
return pkgs, nil
|
||||
}
|
||||
|
||||
@ -6,18 +6,18 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
|
||||
"github.com/anchore/syft/syft/scope"
|
||||
|
||||
rpmdb "github.com/anchore/go-rpmdb/pkg"
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/cataloger/common"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
// integrity check
|
||||
var _ common.ParserFn = parseRpmDB
|
||||
|
||||
// parseApkDb parses an "Packages" RPM DB and returns the Packages listed within it.
|
||||
func parseRpmDB(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
func parseRpmDB(resolver scope.FileResolver, reader io.Reader) ([]pkg.Package, error) {
|
||||
f, err := ioutil.TempFile("", internal.ApplicationName+"-rpmdb")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create temp rpmdb file: %w", err)
|
||||
@ -48,6 +48,11 @@ func parseRpmDB(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
allPkgs := make([]pkg.Package, 0)
|
||||
|
||||
for _, entry := range pkgList {
|
||||
records, err := extractRpmdbFileRecords(resolver, entry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := pkg.Package{
|
||||
Name: entry.Name,
|
||||
Version: fmt.Sprintf("%s-%s", entry.Version, entry.Release), // this is what engine does
|
||||
@ -64,6 +69,7 @@ func parseRpmDB(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
Vendor: entry.Vendor,
|
||||
License: entry.License,
|
||||
Size: entry.Size,
|
||||
Files: records,
|
||||
},
|
||||
}
|
||||
|
||||
@ -72,3 +78,26 @@ func parseRpmDB(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
|
||||
return allPkgs, nil
|
||||
}
|
||||
|
||||
func extractRpmdbFileRecords(resolver scope.FileResolver, entry *rpmdb.PackageInfo) ([]pkg.RpmdbFileRecord, error) {
|
||||
var records = make([]pkg.RpmdbFileRecord, 0)
|
||||
|
||||
for _, record := range entry.Files {
|
||||
refs, err := resolver.FilesByPath(file.Path(record.Path))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve path=%+v: %w", record.Path, err)
|
||||
}
|
||||
//only persist RPMDB file records which exist in the image/directory, otherwise ignore them
|
||||
if len(refs) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
records = append(records, pkg.RpmdbFileRecord{
|
||||
Path: record.Path,
|
||||
Mode: pkg.RpmdbFileMode(record.Mode),
|
||||
Size: int(record.Size),
|
||||
SHA256: record.SHA256,
|
||||
})
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
@ -1,58 +1,141 @@
|
||||
package rpmdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/go-test/deep"
|
||||
)
|
||||
|
||||
type rpmdbTestFileResolverMock struct {
|
||||
ignorePaths bool
|
||||
}
|
||||
|
||||
func newTestFileResolver(ignorePaths bool) *rpmdbTestFileResolverMock {
|
||||
return &rpmdbTestFileResolverMock{
|
||||
ignorePaths: ignorePaths,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rpmdbTestFileResolverMock) FilesByPath(paths ...file.Path) ([]file.Reference, error) {
|
||||
if r.ignorePaths {
|
||||
// act as if no paths exist
|
||||
return nil, nil
|
||||
}
|
||||
// act as if all files exist
|
||||
var refs = make([]file.Reference, len(paths))
|
||||
for i, p := range paths {
|
||||
refs[i] = file.NewFileReference(p)
|
||||
}
|
||||
return refs, nil
|
||||
}
|
||||
|
||||
func (r *rpmdbTestFileResolverMock) FilesByGlob(...string) ([]file.Reference, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
func (r *rpmdbTestFileResolverMock) RelativeFileByPath(file.Reference, string) (*file.Reference, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func TestParseRpmDB(t *testing.T) {
|
||||
expected := map[string]pkg.Package{
|
||||
"dive": {
|
||||
Name: "dive",
|
||||
Version: "0.9.2-1",
|
||||
Type: pkg.RpmPkg,
|
||||
MetadataType: pkg.RpmdbMetadataType,
|
||||
Metadata: pkg.RpmdbMetadata{
|
||||
Name: "dive",
|
||||
Epoch: 0,
|
||||
Arch: "x86_64",
|
||||
Release: "1",
|
||||
Version: "0.9.2",
|
||||
SourceRpm: "dive-0.9.2-1.src.rpm",
|
||||
Size: 12406784,
|
||||
License: "MIT",
|
||||
Vendor: "",
|
||||
tests := []struct {
|
||||
fixture string
|
||||
expected map[string]pkg.Package
|
||||
ignorePaths bool
|
||||
}{
|
||||
{
|
||||
fixture: "test-fixtures/Packages",
|
||||
// we only surface package paths for files that exist (here we DO NOT expect a path)
|
||||
ignorePaths: true,
|
||||
expected: map[string]pkg.Package{
|
||||
"dive": {
|
||||
Name: "dive",
|
||||
Version: "0.9.2-1",
|
||||
Type: pkg.RpmPkg,
|
||||
MetadataType: pkg.RpmdbMetadataType,
|
||||
Metadata: pkg.RpmdbMetadata{
|
||||
Name: "dive",
|
||||
Epoch: 0,
|
||||
Arch: "x86_64",
|
||||
Release: "1",
|
||||
Version: "0.9.2",
|
||||
SourceRpm: "dive-0.9.2-1.src.rpm",
|
||||
Size: 12406784,
|
||||
License: "MIT",
|
||||
Vendor: "",
|
||||
Files: []pkg.RpmdbFileRecord{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/Packages",
|
||||
// we only surface package paths for files that exist (here we expect a path)
|
||||
ignorePaths: false,
|
||||
expected: map[string]pkg.Package{
|
||||
"dive": {
|
||||
Name: "dive",
|
||||
Version: "0.9.2-1",
|
||||
Type: pkg.RpmPkg,
|
||||
MetadataType: pkg.RpmdbMetadataType,
|
||||
Metadata: pkg.RpmdbMetadata{
|
||||
Name: "dive",
|
||||
Epoch: 0,
|
||||
Arch: "x86_64",
|
||||
Release: "1",
|
||||
Version: "0.9.2",
|
||||
SourceRpm: "dive-0.9.2-1.src.rpm",
|
||||
Size: 12406784,
|
||||
License: "MIT",
|
||||
Vendor: "",
|
||||
Files: []pkg.RpmdbFileRecord{
|
||||
{
|
||||
Path: "/usr/local/bin/dive",
|
||||
Mode: 33261,
|
||||
Size: 12406784,
|
||||
SHA256: "81d29f327ba23096b3c52ff6fe1c425641e618bc87b5c05ee377edc650afaa55",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fixture, err := os.Open("test-fixtures/Packages")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
}
|
||||
|
||||
actual, err := parseRpmDB(fixture.Name(), fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse rpmdb: %+v", err)
|
||||
}
|
||||
|
||||
if len(actual) != len(expected) {
|
||||
for _, a := range actual {
|
||||
t.Log(" ", a)
|
||||
}
|
||||
t.Fatalf("unexpected package count: %d!=%d", len(actual), len(expected))
|
||||
}
|
||||
|
||||
for _, a := range actual {
|
||||
e := expected[a.Name]
|
||||
diffs := deep.Equal(a, e)
|
||||
if len(diffs) > 0 {
|
||||
for _, d := range diffs {
|
||||
t.Errorf("diff: %+v", d)
|
||||
for _, test := range tests {
|
||||
t.Run(test.fixture, func(t *testing.T) {
|
||||
fixture, err := os.Open(test.fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
fileResolver := newTestFileResolver(test.ignorePaths)
|
||||
|
||||
actual, err := parseRpmDB(fileResolver, fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse rpmdb: %+v", err)
|
||||
}
|
||||
|
||||
if len(actual) != len(test.expected) {
|
||||
for _, a := range actual {
|
||||
t.Log(" ", a)
|
||||
}
|
||||
t.Fatalf("unexpected package count: %d!=%d", len(actual), len(test.expected))
|
||||
}
|
||||
|
||||
for _, a := range actual {
|
||||
e := test.expected[a.Name]
|
||||
diffs := deep.Equal(a, e)
|
||||
if len(diffs) > 0 {
|
||||
for _, d := range diffs {
|
||||
t.Errorf("diff: %+v", d)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -9,17 +9,27 @@ import (
|
||||
|
||||
// RpmdbMetadata represents all captured data for a RPM DB package entry.
|
||||
type RpmdbMetadata struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Epoch int `json:"epoch"`
|
||||
Arch string `json:"architecture"`
|
||||
Release string `json:"release"`
|
||||
SourceRpm string `json:"sourceRpm"`
|
||||
Size int `json:"size"`
|
||||
License string `json:"license"`
|
||||
Vendor string `json:"vendor"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Epoch int `json:"epoch"`
|
||||
Arch string `json:"architecture"`
|
||||
Release string `json:"release"`
|
||||
SourceRpm string `json:"sourceRpm"`
|
||||
Size int `json:"size"`
|
||||
License string `json:"license"`
|
||||
Vendor string `json:"vendor"`
|
||||
Files []RpmdbFileRecord `json:"files"`
|
||||
}
|
||||
|
||||
type RpmdbFileRecord struct {
|
||||
Path string `json:"path"`
|
||||
Mode RpmdbFileMode `json:"mode"`
|
||||
Size int `json:"size"`
|
||||
SHA256 string `json:"sha256"`
|
||||
}
|
||||
|
||||
type RpmdbFileMode uint16
|
||||
|
||||
func (m RpmdbMetadata) PackageURL(d distro.Distro) string {
|
||||
pURL := packageurl.NewPackageURL(
|
||||
packageurl.TypeRPM,
|
||||
|
||||
@ -67,12 +67,26 @@ func (r *AllLayersResolver) FilesByPath(paths ...file.Path) ([]file.Reference, e
|
||||
|
||||
for _, path := range paths {
|
||||
for idx, layerIdx := range r.layers {
|
||||
ref := r.img.Layers[layerIdx].Tree.File(path)
|
||||
tree := r.img.Layers[layerIdx].Tree
|
||||
ref := tree.File(path)
|
||||
if ref == nil {
|
||||
// no file found, keep looking through layers
|
||||
continue
|
||||
}
|
||||
|
||||
// don't consider directories (special case: there is no path information for /)
|
||||
if ref.Path == "/" {
|
||||
continue
|
||||
} else if r.img.FileCatalog.Exists(*ref) {
|
||||
metadata, err := r.img.FileCatalog.Get(*ref)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get file metadata for path=%q: %w", ref.Path, err)
|
||||
}
|
||||
if metadata.Metadata.IsDir {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
results, err := r.fileByRef(*ref, uniqueFileIDs, idx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -97,6 +111,19 @@ func (r *AllLayersResolver) FilesByGlob(patterns ...string) ([]file.Reference, e
|
||||
}
|
||||
|
||||
for _, ref := range refs {
|
||||
// don't consider directories (special case: there is no path information for /)
|
||||
if ref.Path == "/" {
|
||||
continue
|
||||
} else if r.img.FileCatalog.Exists(ref) {
|
||||
metadata, err := r.img.FileCatalog.Get(ref)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get file metadata for path=%q: %w", ref.Path, err)
|
||||
}
|
||||
if metadata.Metadata.IsDir {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
results, err := r.fileByRef(ref, uniqueFileIDs, idx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
package resolvers
|
||||
|
||||
import (
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
)
|
||||
|
||||
@ -80,6 +81,11 @@ func TestAllLayersResolver_FilesByPath(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ignore directories",
|
||||
linkPath: "/bin",
|
||||
resolutions: []resolution{},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
@ -188,6 +194,11 @@ func TestAllLayersResolver_FilesByGlob(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ignore directories",
|
||||
glob: "**/bin",
|
||||
resolutions: []resolution{},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
|
||||
@ -33,12 +33,18 @@ func (s DirectoryResolver) FilesByPath(userPaths ...file.Path) ([]file.Reference
|
||||
// a path relative to root should be prefixed with the resolvers directory path, otherwise it should be left as is
|
||||
userStrPath = path.Join(s.Path, userStrPath)
|
||||
}
|
||||
_, err := os.Stat(userStrPath)
|
||||
fileMeta, err := os.Stat(userStrPath)
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
} else if err != nil {
|
||||
log.Errorf("path (%s) is not valid: %v", userStrPath, err)
|
||||
}
|
||||
|
||||
// don't consider directories
|
||||
if fileMeta.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
references = append(references, file.NewFileReference(file.Path(userStrPath)))
|
||||
}
|
||||
|
||||
@ -69,9 +75,12 @@ func (s DirectoryResolver) FilesByGlob(patterns ...string) ([]file.Reference, er
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// don't consider directories
|
||||
if fileMeta.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
matchedPath := file.Path(match)
|
||||
result = append(result, file.NewFileReference(matchedPath))
|
||||
}
|
||||
|
||||
@ -48,6 +48,12 @@ func TestDirectoryResolver_FilesByPath(t *testing.T) {
|
||||
expected: "test-fixtures/image-symlinks/file-1.txt",
|
||||
refCount: 1,
|
||||
},
|
||||
{
|
||||
name: "directories ignored",
|
||||
root: "./test-fixtures/",
|
||||
input: "/image-symlinks",
|
||||
refCount: 0,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
|
||||
@ -26,16 +26,32 @@ func (r *ImageSquashResolver) FilesByPath(paths ...file.Path) ([]file.Reference,
|
||||
uniqueFiles := make([]file.Reference, 0)
|
||||
|
||||
for _, path := range paths {
|
||||
ref := r.img.SquashedTree().File(path)
|
||||
tree := r.img.SquashedTree()
|
||||
ref := tree.File(path)
|
||||
if ref == nil {
|
||||
// no file found, keep looking through layers
|
||||
continue
|
||||
}
|
||||
|
||||
// don't consider directories (special case: there is no path information for /)
|
||||
if ref.Path == "/" {
|
||||
continue
|
||||
} else if r.img.FileCatalog.Exists(*ref) {
|
||||
metadata, err := r.img.FileCatalog.Get(*ref)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get file metadata for path=%q: %w", ref.Path, err)
|
||||
}
|
||||
if metadata.Metadata.IsDir {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// a file may be a symlink, process it as such and resolve it
|
||||
resolvedRef, err := r.img.ResolveLinkByImageSquash(*ref)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve link from img (ref=%+v): %w", ref, err)
|
||||
}
|
||||
|
||||
if resolvedRef != nil && !uniqueFileIDs.Contains(*resolvedRef) {
|
||||
uniqueFileIDs.Add(*resolvedRef)
|
||||
uniqueFiles = append(uniqueFiles, *resolvedRef)
|
||||
@ -57,6 +73,19 @@ func (r *ImageSquashResolver) FilesByGlob(patterns ...string) ([]file.Reference,
|
||||
}
|
||||
|
||||
for _, ref := range refs {
|
||||
// don't consider directories (special case: there is no path information for /)
|
||||
if ref.Path == "/" {
|
||||
continue
|
||||
} else if r.img.FileCatalog.Exists(ref) {
|
||||
metadata, err := r.img.FileCatalog.Get(ref)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get file metadata for path=%q: %w", ref.Path, err)
|
||||
}
|
||||
if metadata.Metadata.IsDir {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
resolvedRefs, err := r.FilesByPath(ref.Path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find files by path (ref=%+v): %w", ref, err)
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
package resolvers
|
||||
|
||||
import (
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
)
|
||||
|
||||
@ -44,6 +45,11 @@ func TestImageSquashResolver_FilesByPath(t *testing.T) {
|
||||
resolveLayer: 8,
|
||||
resolvePath: "/link-dead",
|
||||
},
|
||||
{
|
||||
name: "ignore directories",
|
||||
linkPath: "/bin",
|
||||
resolvePath: "",
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
@ -60,10 +66,20 @@ func TestImageSquashResolver_FilesByPath(t *testing.T) {
|
||||
t.Fatalf("could not use resolver: %+v", err)
|
||||
}
|
||||
|
||||
if len(refs) != 1 {
|
||||
expectedRefs := 1
|
||||
if c.resolvePath == "" {
|
||||
expectedRefs = 0
|
||||
}
|
||||
|
||||
if len(refs) != expectedRefs {
|
||||
t.Fatalf("unexpected number of resolutions: %d", len(refs))
|
||||
}
|
||||
|
||||
if expectedRefs == 0 {
|
||||
// nothing else to assert
|
||||
return
|
||||
}
|
||||
|
||||
actual := refs[0]
|
||||
|
||||
if actual.Path != file.Path(c.resolvePath) {
|
||||
@ -119,6 +135,11 @@ func TestImageSquashResolver_FilesByGlob(t *testing.T) {
|
||||
resolveLayer: 8,
|
||||
resolvePath: "/link-dead",
|
||||
},
|
||||
{
|
||||
name: "ignore directories",
|
||||
glob: "**/bin",
|
||||
resolvePath: "",
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
@ -135,10 +156,20 @@ func TestImageSquashResolver_FilesByGlob(t *testing.T) {
|
||||
t.Fatalf("could not use resolver: %+v", err)
|
||||
}
|
||||
|
||||
if len(refs) != 1 {
|
||||
expectedRefs := 1
|
||||
if c.resolvePath == "" {
|
||||
expectedRefs = 0
|
||||
}
|
||||
|
||||
if len(refs) != expectedRefs {
|
||||
t.Fatalf("unexpected number of resolutions: %d", len(refs))
|
||||
}
|
||||
|
||||
if expectedRefs == 0 {
|
||||
// nothing else to assert
|
||||
return
|
||||
}
|
||||
|
||||
actual := refs[0]
|
||||
|
||||
if actual.Path != file.Path(c.resolvePath) {
|
||||
|
||||
@ -61,9 +61,15 @@ func TestDirectoryScope(t *testing.T) {
|
||||
{
|
||||
desc: "path detected",
|
||||
input: "test-fixtures",
|
||||
inputPaths: []file.Path{file.Path("test-fixtures/path-detected")},
|
||||
inputPaths: []file.Path{file.Path("test-fixtures/path-detected/.vimrc")},
|
||||
expRefs: 1,
|
||||
},
|
||||
{
|
||||
desc: "directory ignored",
|
||||
input: "test-fixtures",
|
||||
inputPaths: []file.Path{file.Path("test-fixtures/path-detected")},
|
||||
expRefs: 0,
|
||||
},
|
||||
{
|
||||
desc: "no files-by-path detected",
|
||||
input: "test-fixtures",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user