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:
Alex Goodman
2023-11-02 12:39:42 +00:00
committed by GitHub
parent 6c41f15975
commit b2f4d7eda2
144 changed files with 294 additions and 273 deletions
+14
View File
@@ -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))
}
+18
View File
@@ -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()
}
+28
View File
@@ -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
@@ -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
@@ -0,0 +1 @@
Apache-2.0 BSD BSD-2 CC-BY-SA-4.0 ISC MIT