mirror of
https://github.com/anchore/syft.git
synced 2026-11-05 00:46:38 +01:00
Follow convention for naming catalogers (#2277)
* follow convention for naming catalogers Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix cataloger name example Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
Package gentoo provides a concrete Cataloger implementation related to packages within the Gentoo linux ecosystem.
|
||||
*/
|
||||
package gentoo
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
// NewPortageCataloger returns a new cataloger object initialized for Gentoo Portage package manager files (a flat-file store).
|
||||
func NewPortageCataloger() *generic.Cataloger {
|
||||
return generic.NewCataloger("portage-cataloger").
|
||||
WithParserByGlobs(parsePortageContents, "**/var/db/pkg/*/*/CONTENTS")
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package gentoo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
)
|
||||
|
||||
func TestPortageCataloger(t *testing.T) {
|
||||
expectedLicenseLocation := file.NewLocation("var/db/pkg/app-containers/skopeo-1.5.1/LICENSE")
|
||||
expectedPkgs := []pkg.Package{
|
||||
{
|
||||
Name: "app-containers/skopeo",
|
||||
Version: "1.5.1",
|
||||
FoundBy: "portage-cataloger",
|
||||
PURL: "pkg:ebuild/app-containers/skopeo@1.5.1",
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewLocation("var/db/pkg/app-containers/skopeo-1.5.1/CONTENTS"),
|
||||
file.NewLocation("var/db/pkg/app-containers/skopeo-1.5.1/SIZE"),
|
||||
expectedLicenseLocation,
|
||||
),
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(expectedLicenseLocation, "Apache-2.0", "BSD", "BSD-2", "CC-BY-SA-4.0", "ISC", "MIT")...),
|
||||
Type: pkg.PortagePkg,
|
||||
Metadata: pkg.PortageEntry{
|
||||
InstalledSize: 27937835,
|
||||
Files: []pkg.PortageFileRecord{
|
||||
{
|
||||
Path: "/usr/bin/skopeo",
|
||||
Digest: &file.Digest{
|
||||
Algorithm: "md5",
|
||||
Value: "376c02bd3b22804df8fdfdc895e7dbfb",
|
||||
},
|
||||
},
|
||||
{
|
||||
Path: "/etc/containers/policy.json",
|
||||
Digest: &file.Digest{
|
||||
Algorithm: "md5",
|
||||
Value: "c01eb6950f03419e09d4fc88cb42ff6f",
|
||||
},
|
||||
},
|
||||
{
|
||||
Path: "/etc/containers/registries.d/default.yaml",
|
||||
Digest: &file.Digest{
|
||||
Algorithm: "md5",
|
||||
Value: "e6e66cd3c24623e0667f26542e0e08f6",
|
||||
},
|
||||
},
|
||||
{
|
||||
Path: "/var/lib/atomic/sigstore/.keep_app-containers_skopeo-0",
|
||||
Digest: &file.Digest{
|
||||
Algorithm: "md5",
|
||||
Value: "d41d8cd98f00b204e9800998ecf8427e",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// TODO: relationships are not under test yet
|
||||
var expectedRelationships []artifact.Relationship
|
||||
|
||||
pkgtest.NewCatalogTester().
|
||||
FromDirectory(t, "test-fixtures/image-portage").
|
||||
Expects(expectedPkgs, expectedRelationships).
|
||||
TestCataloger(t, NewPortageCataloger())
|
||||
|
||||
}
|
||||
|
||||
func TestCataloger_Globs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "obtain portage contents file",
|
||||
fixture: "test-fixtures/glob-paths",
|
||||
expected: []string{
|
||||
"var/db/pkg/x/y/CONTENTS",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
pkgtest.NewCatalogTester().
|
||||
FromDirectory(t, test.fixture).
|
||||
ExpectsResolverContentQueries(test.expected).
|
||||
TestCataloger(t, NewPortageCataloger())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package gentoo
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
var (
|
||||
cpvRe = regexp.MustCompile(`/([^/]*/[\w+][\w+-]*)-((\d+)((\.\d+)*)([a-z]?)((_(pre|p|beta|alpha|rc)\d*)*)(-r\d+)?)/CONTENTS$`)
|
||||
_ generic.Parser = parsePortageContents
|
||||
)
|
||||
|
||||
// parses individual CONTENTS files from the portage flat-file store (e.g. /var/db/pkg/*/*/CONTENTS).
|
||||
func parsePortageContents(resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
cpvMatch := cpvRe.FindStringSubmatch(reader.Location.RealPath)
|
||||
if cpvMatch == nil {
|
||||
return nil, nil, fmt.Errorf("failed to match package and version in %s", reader.Location.RealPath)
|
||||
}
|
||||
|
||||
name, version := cpvMatch[1], cpvMatch[2]
|
||||
if name == "" || version == "" {
|
||||
log.WithFields("path", reader.Location.RealPath).Warnf("failed to parse portage name and version")
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
p := pkg.Package{
|
||||
Name: name,
|
||||
Version: version,
|
||||
PURL: packageURL(name, version),
|
||||
Locations: file.NewLocationSet(
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
Type: pkg.PortagePkg,
|
||||
Metadata: pkg.PortageEntry{
|
||||
// ensure the default value for a collection is never nil since this may be shown as JSON
|
||||
Files: make([]pkg.PortageFileRecord, 0),
|
||||
},
|
||||
}
|
||||
addLicenses(resolver, reader.Location, &p)
|
||||
addSize(resolver, reader.Location, &p)
|
||||
addFiles(resolver, reader.Location, &p)
|
||||
|
||||
p.SetID()
|
||||
|
||||
return []pkg.Package{p}, nil, nil
|
||||
}
|
||||
|
||||
func addFiles(resolver file.Resolver, dbLocation file.Location, p *pkg.Package) {
|
||||
contentsReader, err := resolver.FileContentsByLocation(dbLocation)
|
||||
if err != nil {
|
||||
log.WithFields("path", dbLocation.RealPath).Warnf("failed to fetch portage contents (package=%s): %+v", p.Name, err)
|
||||
return
|
||||
}
|
||||
|
||||
entry, ok := p.Metadata.(pkg.PortageEntry)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(contentsReader)
|
||||
for scanner.Scan() {
|
||||
line := strings.Trim(scanner.Text(), "\n")
|
||||
fields := strings.Split(line, " ")
|
||||
|
||||
if fields[0] == "obj" {
|
||||
record := pkg.PortageFileRecord{
|
||||
Path: fields[1],
|
||||
}
|
||||
record.Digest = &file.Digest{
|
||||
Algorithm: "md5",
|
||||
Value: fields[2],
|
||||
}
|
||||
entry.Files = append(entry.Files, record)
|
||||
}
|
||||
}
|
||||
|
||||
p.Metadata = entry
|
||||
p.Locations.Add(dbLocation)
|
||||
}
|
||||
|
||||
func addLicenses(resolver file.Resolver, dbLocation file.Location, p *pkg.Package) {
|
||||
parentPath := filepath.Dir(dbLocation.RealPath)
|
||||
|
||||
location := resolver.RelativeFileByPath(dbLocation, path.Join(parentPath, "LICENSE"))
|
||||
|
||||
if location == nil {
|
||||
return
|
||||
}
|
||||
|
||||
licenseReader, err := resolver.FileContentsByLocation(*location)
|
||||
if err != nil {
|
||||
log.WithFields("path", dbLocation.RealPath).Warnf("failed to fetch portage LICENSE: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
findings := strset.New()
|
||||
scanner := bufio.NewScanner(licenseReader)
|
||||
scanner.Split(bufio.ScanWords)
|
||||
for scanner.Scan() {
|
||||
token := scanner.Text()
|
||||
if token != "||" && token != "(" && token != ")" {
|
||||
findings.Add(token)
|
||||
}
|
||||
}
|
||||
|
||||
licenseCandidates := findings.List()
|
||||
p.Licenses = pkg.NewLicenseSet(pkg.NewLicensesFromLocation(*location, licenseCandidates...)...)
|
||||
p.Locations.Add(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation))
|
||||
}
|
||||
|
||||
func addSize(resolver file.Resolver, dbLocation file.Location, p *pkg.Package) {
|
||||
parentPath := filepath.Dir(dbLocation.RealPath)
|
||||
|
||||
location := resolver.RelativeFileByPath(dbLocation, path.Join(parentPath, "SIZE"))
|
||||
|
||||
if location == nil {
|
||||
return
|
||||
}
|
||||
|
||||
entry, ok := p.Metadata.(pkg.PortageEntry)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
sizeReader, err := resolver.FileContentsByLocation(*location)
|
||||
if err != nil {
|
||||
log.WithFields("name", p.Name).Warnf("failed to fetch portage SIZE: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(sizeReader)
|
||||
for scanner.Scan() {
|
||||
line := strings.Trim(scanner.Text(), "\n")
|
||||
size, err := strconv.Atoi(line)
|
||||
if err == nil {
|
||||
entry.InstalledSize = size
|
||||
}
|
||||
}
|
||||
|
||||
p.Metadata = entry
|
||||
p.Locations.Add(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation))
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package gentoo
|
||||
|
||||
import (
|
||||
"github.com/anchore/packageurl-go"
|
||||
)
|
||||
|
||||
func packageURL(name, version string) string {
|
||||
var qualifiers packageurl.Qualifiers
|
||||
|
||||
return packageurl.NewPackageURL(
|
||||
"ebuild", // currently this is the proposed type for portage packages at https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst
|
||||
"",
|
||||
name,
|
||||
version,
|
||||
qualifiers,
|
||||
"",
|
||||
).ToString()
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package gentoo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_packageURL(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
version string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
"app-admin/eselect",
|
||||
"1.4.15",
|
||||
"pkg:ebuild/app-admin/eselect@1.4.15",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(fmt.Sprintf("%s@%s", tt.name, tt.version), func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, packageURL(tt.name, tt.version))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
bogus contents
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
dir /usr
|
||||
dir /usr/bin
|
||||
obj /usr/bin/skopeo 376c02bd3b22804df8fdfdc895e7dbfb 1649284374
|
||||
dir /etc
|
||||
dir /etc/containers
|
||||
obj /etc/containers/policy.json c01eb6950f03419e09d4fc88cb42ff6f 1649284375
|
||||
dir /etc/containers/registries.d
|
||||
obj /etc/containers/registries.d/default.yaml e6e66cd3c24623e0667f26542e0e08f6 1649284375
|
||||
dir /var
|
||||
dir /var/lib
|
||||
dir /var/lib/atomic
|
||||
dir /var/lib/atomic/sigstore
|
||||
obj /var/lib/atomic/sigstore/.keep_app-containers_skopeo-0 d41d8cd98f00b204e9800998ecf8427e 1649284375
|
||||
+1
@@ -0,0 +1 @@
|
||||
Apache-2.0 BSD BSD-2 CC-BY-SA-4.0 ISC MIT
|
||||
+1
@@ -0,0 +1 @@
|
||||
27937835
|
||||
Reference in New Issue
Block a user