mirror of
https://github.com/anchore/syft.git
synced 2026-11-01 23:16:30 +01:00
Replace distro type (#742)
* remove strong distro type Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * bump json schema to v3 (breaking distro shape) Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * fix linting Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * allow for v2 decoding of distro idLikes field in v3 json decoder Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * fix casing in simple linux release name Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * use discovered name as pretty name in simple linux release Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
@@ -1,61 +0,0 @@
|
||||
package distro
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
hashiVer "github.com/hashicorp/go-version"
|
||||
)
|
||||
|
||||
// Distro represents a Linux Distribution.
|
||||
type Distro struct {
|
||||
Type Type
|
||||
Version *hashiVer.Version
|
||||
RawVersion string
|
||||
IDLike string
|
||||
}
|
||||
|
||||
// NewDistro creates a new Distro object populated with the given values.
|
||||
func NewDistro(t Type, ver, like string) (Distro, error) {
|
||||
if ver == "" {
|
||||
return Distro{Type: t}, nil
|
||||
}
|
||||
|
||||
verObj, err := hashiVer.NewVersion(ver)
|
||||
if err != nil {
|
||||
return Distro{}, fmt.Errorf("could not create distro version: %w", err)
|
||||
}
|
||||
|
||||
return Distro{
|
||||
Type: t,
|
||||
Version: verObj,
|
||||
RawVersion: ver,
|
||||
IDLike: like,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Name provides a string repr of the distro
|
||||
func (d Distro) Name() string {
|
||||
return string(d.Type)
|
||||
}
|
||||
|
||||
// MajorVersion returns the major version value from the pseudo-semantically versioned distro version value.
|
||||
func (d Distro) MajorVersion() string {
|
||||
if d.Version == nil {
|
||||
return "(version unknown)"
|
||||
}
|
||||
return fmt.Sprintf("%d", d.Version.Segments()[0])
|
||||
}
|
||||
|
||||
// FullVersion returns the original user version value.
|
||||
func (d Distro) FullVersion() string {
|
||||
return d.RawVersion
|
||||
}
|
||||
|
||||
// String returns a human-friendly representation of the Linux distribution.
|
||||
func (d Distro) String() string {
|
||||
versionStr := "(version unknown)"
|
||||
if d.RawVersion != "" {
|
||||
versionStr = d.RawVersion
|
||||
}
|
||||
return fmt.Sprintf("%s %s", d.Type, versionStr)
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package distro
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDistro_FullVersion(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
dist Type
|
||||
version string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
version: "8",
|
||||
expected: "8",
|
||||
},
|
||||
{
|
||||
version: "18.04",
|
||||
expected: "18.04",
|
||||
},
|
||||
{
|
||||
version: "0",
|
||||
expected: "0",
|
||||
},
|
||||
{
|
||||
version: "18.1.2",
|
||||
expected: "18.1.2",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
name := fmt.Sprintf("%s:%s", test.dist, test.version)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
d, err := NewDistro(test.dist, test.version, "")
|
||||
if err != nil {
|
||||
t.Errorf("could not create distro='%+v:%+v': %+v", test.dist, test.version, err)
|
||||
}
|
||||
|
||||
actual := d.FullVersion()
|
||||
if actual != test.expected {
|
||||
t.Errorf("mismatched distro raw version: '%s'!='%s'", actual, test.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestDistro_MajorVersion(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
dist Type
|
||||
version string
|
||||
expected string
|
||||
like string
|
||||
}{
|
||||
{
|
||||
version: "8",
|
||||
expected: "8",
|
||||
like: "",
|
||||
},
|
||||
{
|
||||
version: "18.04",
|
||||
expected: "18",
|
||||
like: "debian",
|
||||
},
|
||||
{
|
||||
version: "0",
|
||||
expected: "0",
|
||||
like: "",
|
||||
},
|
||||
{
|
||||
version: "18.1.2",
|
||||
expected: "18",
|
||||
like: "debian",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
name := fmt.Sprintf("%s:%s", test.dist, test.version)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
d, err := NewDistro(test.dist, test.version, test.like)
|
||||
if err != nil {
|
||||
t.Errorf("could not create distro='%+v:%+v': %+v", test.dist, test.version, err)
|
||||
}
|
||||
|
||||
actual := d.MajorVersion()
|
||||
if actual != test.expected {
|
||||
t.Errorf("mismatched major version: '%s'!='%s'", actual, test.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
package distro
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// returns a distro or nil
|
||||
type parseFunc func(string) *Distro
|
||||
|
||||
type parseEntry struct {
|
||||
path string
|
||||
fn parseFunc
|
||||
}
|
||||
|
||||
var identityFiles = []parseEntry{
|
||||
{
|
||||
// most distros provide a link at this location
|
||||
path: "/etc/os-release",
|
||||
fn: parseOsRelease,
|
||||
},
|
||||
{
|
||||
// standard location for rhel & debian distros
|
||||
path: "/usr/lib/os-release",
|
||||
fn: parseOsRelease,
|
||||
},
|
||||
{
|
||||
// check for busybox (important to check this last since other distros contain the busybox binary)
|
||||
path: "/bin/busybox",
|
||||
fn: parseBusyBox,
|
||||
},
|
||||
{
|
||||
// check for centos:6
|
||||
path: "/etc/system-release-cpe",
|
||||
fn: parseSystemReleaseCPE,
|
||||
},
|
||||
{
|
||||
// last ditch effort for determining older centos version distro information
|
||||
path: "/etc/redhat-release",
|
||||
fn: parseRedhatRelease,
|
||||
},
|
||||
}
|
||||
|
||||
// Identify parses distro-specific files to determine distro metadata like version and release.
|
||||
func Identify(resolver source.FileResolver) *Distro {
|
||||
var distro *Distro
|
||||
|
||||
identifyLoop:
|
||||
for _, entry := range identityFiles {
|
||||
locations, err := resolver.FilesByPath(entry.path)
|
||||
if err != nil {
|
||||
log.Errorf("unable to get path locations from %s: %s", entry.path, err)
|
||||
break
|
||||
}
|
||||
|
||||
if len(locations) == 0 {
|
||||
log.Debugf("path not found: %s", entry.path)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, location := range locations {
|
||||
contentReader, err := resolver.FileContentsByLocation(location)
|
||||
if err != nil {
|
||||
log.Debugf("unable to get contents from %s: %s", entry.path, err)
|
||||
continue
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadAll(contentReader)
|
||||
internal.CloseAndLogError(contentReader, location.VirtualPath)
|
||||
if err != nil {
|
||||
log.Errorf("unable to read %q: %+v", location.RealPath, err)
|
||||
break
|
||||
}
|
||||
|
||||
if len(content) == 0 {
|
||||
log.Debugf("no contents in file, skipping: %s", entry.path)
|
||||
continue
|
||||
}
|
||||
|
||||
if candidateDistro := entry.fn(string(content)); candidateDistro != nil {
|
||||
distro = candidateDistro
|
||||
break identifyLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if distro != nil && distro.Type == UnknownDistroType {
|
||||
return nil
|
||||
}
|
||||
|
||||
return distro
|
||||
}
|
||||
|
||||
func assemble(id, version, like string) *Distro {
|
||||
distroType, ok := IDMapping[id]
|
||||
|
||||
// Both distro and version must be present
|
||||
if len(id) == 0 && len(version) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If it's an unknown distro, try mapping the ID_LIKE
|
||||
if !ok && len(like) != 0 {
|
||||
distroType, ok = IDMapping[like]
|
||||
}
|
||||
|
||||
// If we still can't match allow name to be used in constructor
|
||||
if !ok {
|
||||
distroType = Type(id)
|
||||
}
|
||||
|
||||
distro, err := NewDistro(distroType, version, like)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &distro
|
||||
}
|
||||
|
||||
func parseOsRelease(contents string) *Distro {
|
||||
id, vers, like := "", "", ""
|
||||
for _, line := range strings.Split(contents, "\n") {
|
||||
parts := strings.Split(line, "=")
|
||||
prefix := parts[0]
|
||||
value := strings.ReplaceAll(parts[len(parts)-1], `"`, "")
|
||||
|
||||
switch prefix {
|
||||
case "ID":
|
||||
id = strings.TrimSpace(value)
|
||||
case "VERSION_ID":
|
||||
vers = strings.TrimSpace(value)
|
||||
case "ID_LIKE":
|
||||
like = strings.TrimSpace(value)
|
||||
}
|
||||
}
|
||||
|
||||
return assemble(id, vers, like)
|
||||
}
|
||||
|
||||
var busyboxVersionMatcher = regexp.MustCompile(`BusyBox v[\d.]+`)
|
||||
|
||||
func parseBusyBox(contents string) *Distro {
|
||||
matches := busyboxVersionMatcher.FindAllString(contents, -1)
|
||||
for _, match := range matches {
|
||||
parts := strings.Split(match, " ")
|
||||
version := strings.ReplaceAll(parts[1], "v", "")
|
||||
distro := assemble("busybox", version, "")
|
||||
if distro != nil {
|
||||
return distro
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: we should update parseSystemReleaseCPE to use the CPE struct, pkg.CPE, which requires a refactor to avoid a circular import:
|
||||
// TODO: pkg depends on distro to support pURLs. To avoid the circular import, either try to make pkg to not depend on distro (medium lift-ish)
|
||||
// TODO: or migrate the cpe code out of the pkg package (small lift).
|
||||
// example CPE: cpe:/o:centos:linux:6:GA
|
||||
var systemReleaseCpeMatcher = regexp.MustCompile(`cpe:\/o:(.*?):.*?:(.*?):.*?$`)
|
||||
|
||||
// parseSystemReleaseCPE parses the older centos (6) file to determine distro metadata
|
||||
func parseSystemReleaseCPE(contents string) *Distro {
|
||||
matches := systemReleaseCpeMatcher.FindAllStringSubmatch(contents, -1)
|
||||
for _, match := range matches {
|
||||
if len(match) < 3 {
|
||||
log.Warnf("system release cpe does not match expected format")
|
||||
return nil
|
||||
}
|
||||
// note: in SubMatches (capture groups), the 0th index is the full match string
|
||||
// see https://pkg.go.dev/regexp#pkg-overview for more info
|
||||
distro := assemble(match[1], match[2], "")
|
||||
if distro != nil {
|
||||
return distro
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// example: "CentOS release 6.10 (Final)"
|
||||
var redhatReleaseMatcher = regexp.MustCompile(`(.*?)\srelease\s(\d\.\d+)`)
|
||||
|
||||
// parseRedhatRelease is a fallback parsing method for determining distro information in older redhat versions
|
||||
func parseRedhatRelease(contents string) *Distro {
|
||||
matches := redhatReleaseMatcher.FindAllStringSubmatch(contents, -1)
|
||||
for _, match := range matches {
|
||||
if len(match) < 3 {
|
||||
log.Warnf("failed to parse redhat-release file, unexpected format")
|
||||
return nil
|
||||
}
|
||||
// note: in SubMatches (capture groups), the 0th index is the full match string
|
||||
// see https://pkg.go.dev/regexp#pkg-overview for more info
|
||||
distro := assemble(strings.ToLower(match[1]), match[2], "")
|
||||
if distro != nil {
|
||||
return distro
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,372 +0,0 @@
|
||||
package distro
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
hashiVer "github.com/hashicorp/go-version"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const CustomDistro Type = "scientific"
|
||||
|
||||
func TestIdentifyDistro(t *testing.T) {
|
||||
tests := []struct {
|
||||
fixture string
|
||||
Type Type
|
||||
Version string
|
||||
}{
|
||||
{
|
||||
fixture: "test-fixtures/os/alpine",
|
||||
Type: Alpine,
|
||||
Version: "3.11.6",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/amazon",
|
||||
Type: AmazonLinux,
|
||||
Version: "2.0.0",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/busybox",
|
||||
Type: Busybox,
|
||||
Version: "1.31.1",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/centos",
|
||||
Type: CentOS,
|
||||
Version: "8.0.0",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/debian",
|
||||
Type: Debian,
|
||||
Version: "8.0.0",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/fedora",
|
||||
Type: Fedora,
|
||||
Version: "31.0.0",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/redhat",
|
||||
Type: RedHat,
|
||||
Version: "7.3.0",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/ubuntu",
|
||||
Type: Ubuntu,
|
||||
Version: "20.4.0",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/oraclelinux",
|
||||
Type: OracleLinux,
|
||||
Version: "8.3.0",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/empty",
|
||||
Type: UnknownDistroType,
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/custom",
|
||||
Type: CustomDistro,
|
||||
Version: "8.0.0",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/opensuse-leap",
|
||||
Type: OpenSuseLeap,
|
||||
Version: "15.2.0",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/sles",
|
||||
Type: SLES,
|
||||
Version: "15.2.0",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/photon",
|
||||
Type: Photon,
|
||||
Version: "2.0.0",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/arch",
|
||||
Type: ArchLinux,
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/partial-fields/missing-id",
|
||||
Type: Debian,
|
||||
Version: "8.0.0",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/partial-fields/unknown-id",
|
||||
Type: Debian,
|
||||
Version: "8.0.0",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/partial-fields/missing-version",
|
||||
Type: UnknownDistroType,
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/centos6",
|
||||
Type: CentOS,
|
||||
Version: "6.0.0",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/centos5",
|
||||
Type: CentOS,
|
||||
Version: "5.7.0",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/mariner",
|
||||
Type: Mariner,
|
||||
Version: "1.0.0",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/rockylinux",
|
||||
Type: RockyLinux,
|
||||
Version: "8.4.0",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/almalinux",
|
||||
Type: AlmaLinux,
|
||||
Version: "8.4.0",
|
||||
},
|
||||
}
|
||||
|
||||
observedDistros := internal.NewStringSet()
|
||||
definedDistros := internal.NewStringSet()
|
||||
|
||||
for _, distroType := range All {
|
||||
definedDistros.Add(string(distroType))
|
||||
}
|
||||
|
||||
// Somewhat cheating with Windows. There is no support for detecting/parsing a Windows OS, so it is not
|
||||
// possible to comply with this test unless it is added manually to the "observed distros"
|
||||
definedDistros.Remove(string(Windows))
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.fixture, func(t *testing.T) {
|
||||
s, err := source.NewFromDirectory(test.fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to produce a new source for testing: %s", test.fixture)
|
||||
}
|
||||
|
||||
resolver, err := s.FileResolver(source.SquashedScope)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get resolver: %+v", err)
|
||||
}
|
||||
|
||||
d := Identify(resolver)
|
||||
if d == nil {
|
||||
if test.Type == UnknownDistroType {
|
||||
return
|
||||
}
|
||||
t.Fatalf("expected a distro but got none")
|
||||
}
|
||||
observedDistros.Add(d.String())
|
||||
|
||||
if d.Type != test.Type {
|
||||
t.Errorf("expected distro doesn't match: %v != %v", d.Type, test.Type)
|
||||
}
|
||||
|
||||
if d.Type == UnknownDistroType && d.Version != nil {
|
||||
t.Fatalf("version should be nil for unknown distros")
|
||||
} else if d.Type == UnknownDistroType && d.Version == nil {
|
||||
// don't check versions for unknown distro types
|
||||
return
|
||||
}
|
||||
|
||||
if d.Version == nil && test.Version == "" {
|
||||
// this distro does not have a version
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, d.Version.String(), test.Version)
|
||||
})
|
||||
}
|
||||
|
||||
// ensure that test cases stay in sync with the distros that can be identified
|
||||
if len(observedDistros) < len(definedDistros) {
|
||||
for _, d := range definedDistros.ToSlice() {
|
||||
t.Logf(" defined: %s", d)
|
||||
}
|
||||
for _, d := range observedDistros.ToSlice() {
|
||||
t.Logf(" observed: %s", d)
|
||||
}
|
||||
t.Errorf("distro coverage incomplete (defined=%d, coverage=%d)", len(definedDistros), len(observedDistros))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseOsRelease(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
fixture string
|
||||
name string
|
||||
RawVersion string
|
||||
}{
|
||||
{
|
||||
fixture: "test-fixtures/ubuntu-20.04",
|
||||
name: "ubuntu",
|
||||
RawVersion: "20.04",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/debian-8",
|
||||
name: "debian",
|
||||
RawVersion: "8",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/centos-8",
|
||||
name: "centos",
|
||||
RawVersion: "8",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/rhel-8",
|
||||
name: "redhat",
|
||||
RawVersion: "8.1",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/unprintable",
|
||||
name: "debian",
|
||||
RawVersion: "8",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
name := fmt.Sprintf("%s:%s", test.name, test.RawVersion)
|
||||
contents := retrieveFixtureContentsAsString(test.fixture, t)
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
distro := parseOsRelease(contents)
|
||||
if distro.Name() != test.name {
|
||||
t.Errorf("mismatched name in distro: '%s' != '%s'", distro.Name(), test.name)
|
||||
}
|
||||
if distro.RawVersion != test.RawVersion {
|
||||
t.Errorf("mismatched distro version: '%s' != '%s'", distro.RawVersion, test.RawVersion)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestParseOsReleaseFailures(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
fixture string
|
||||
name string
|
||||
}{
|
||||
{
|
||||
fixture: "test-fixtures/bad-id",
|
||||
name: "No name ID",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
name := fmt.Sprintf("%s:%s", test.name, test.fixture)
|
||||
contents := retrieveFixtureContentsAsString(test.fixture, t)
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
distro := parseOsRelease(contents)
|
||||
if distro != nil {
|
||||
t.Errorf("unexpected non-nil distro: '%s' != nil", distro)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSystemReleaseCPE(t *testing.T) {
|
||||
centos6Version, _ := hashiVer.NewVersion("6")
|
||||
tests := []struct {
|
||||
fixture string
|
||||
name string
|
||||
expected *Distro
|
||||
}{
|
||||
{
|
||||
fixture: "test-fixtures/os/centos6/etc/system-release-cpe",
|
||||
name: "Centos 6",
|
||||
expected: &Distro{
|
||||
Type: CentOS,
|
||||
Version: centos6Version,
|
||||
RawVersion: "6",
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/bad-system-release-cpe",
|
||||
name: "Centos 6 Bad CPE",
|
||||
expected: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
contents := retrieveFixtureContentsAsString(test.fixture, t)
|
||||
actual := parseSystemReleaseCPE(contents)
|
||||
|
||||
if test.expected == nil {
|
||||
assert.Nil(t, actual)
|
||||
return
|
||||
}
|
||||
|
||||
// not comparing the full distro object because the hashiVer is a pointer
|
||||
assert.Equal(t, test.expected.Type, actual.Type)
|
||||
assert.Equal(t, &test.expected.Version, &actual.Version)
|
||||
assert.Equal(t, test.expected.RawVersion, actual.RawVersion)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRedhatRelease(t *testing.T) {
|
||||
centos5Version, _ := hashiVer.NewVersion("5.7")
|
||||
tests := []struct {
|
||||
fixture string
|
||||
name string
|
||||
expected *Distro
|
||||
}{
|
||||
{
|
||||
fixture: "test-fixtures/os/centos5/etc/redhat-release",
|
||||
name: "Centos 5",
|
||||
expected: &Distro{
|
||||
Type: CentOS,
|
||||
Version: centos5Version,
|
||||
RawVersion: "5.7",
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/bad-redhat-release",
|
||||
name: "Centos 5 Bad Redhat Release",
|
||||
expected: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
contents := retrieveFixtureContentsAsString(test.fixture, t)
|
||||
actual := parseRedhatRelease(contents)
|
||||
|
||||
if test.expected == nil {
|
||||
assert.Nil(t, actual)
|
||||
return
|
||||
}
|
||||
|
||||
// not comparing the full distro object because the hashiVer is a pointer
|
||||
assert.Equal(t, test.expected.Type, actual.Type)
|
||||
assert.Equal(t, &test.expected.Version, &actual.Version)
|
||||
assert.Equal(t, test.expected.RawVersion, actual.RawVersion)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func retrieveFixtureContentsAsString(fixturePath string, t *testing.T) string {
|
||||
fixture, err := os.Open(fixturePath)
|
||||
if err != nil {
|
||||
t.Fatalf("could not open test fixture=%s: %+v", fixturePath, err)
|
||||
}
|
||||
defer fixture.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to read fixture file: %+v", err)
|
||||
}
|
||||
|
||||
return string(b)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
NAME="Red Hat Enterprise Linux"
|
||||
VERSION="8.1 (Ootpa)"
|
||||
ID_LIKE="fedora"
|
||||
PLATFORM_ID="platform:el8"
|
||||
PRETTY_NAME="Red Hat Enterprise Linux 8.1 (Ootpa)"
|
||||
ANSI_COLOR="0;31"
|
||||
CPE_NAME="cpe:/o:redhat:enterprise_linux:8.1:GA"
|
||||
HOME_URL="https://www.redhat.com/"
|
||||
BUG_REPORT_URL="https://bugzilla.redhat.com/"
|
||||
|
||||
REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 8"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION=8.1
|
||||
REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION="8.1"
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
package distro
|
||||
|
||||
// Type represents the different Linux distribution options
|
||||
type Type string
|
||||
|
||||
const (
|
||||
// represents the set of valid/supported Linux Distributions
|
||||
UnknownDistroType Type = "UnknownDistroType"
|
||||
Debian Type = "debian"
|
||||
Ubuntu Type = "ubuntu"
|
||||
RedHat Type = "redhat"
|
||||
CentOS Type = "centos"
|
||||
Fedora Type = "fedora"
|
||||
Alpine Type = "alpine"
|
||||
Busybox Type = "busybox"
|
||||
AmazonLinux Type = "amazonlinux"
|
||||
OracleLinux Type = "oraclelinux"
|
||||
ArchLinux Type = "archlinux"
|
||||
OpenSuseLeap Type = "opensuseleap"
|
||||
SLES Type = "sles"
|
||||
Photon Type = "photon"
|
||||
Windows Type = "windows"
|
||||
Mariner Type = "mariner"
|
||||
RockyLinux Type = "rockylinux"
|
||||
AlmaLinux Type = "almalinux"
|
||||
)
|
||||
|
||||
// All contains all Linux distribution options
|
||||
var All = []Type{
|
||||
Debian,
|
||||
Ubuntu,
|
||||
RedHat,
|
||||
CentOS,
|
||||
Fedora,
|
||||
Alpine,
|
||||
Busybox,
|
||||
AmazonLinux,
|
||||
OracleLinux,
|
||||
ArchLinux,
|
||||
OpenSuseLeap,
|
||||
SLES,
|
||||
Photon,
|
||||
Windows,
|
||||
Mariner,
|
||||
RockyLinux,
|
||||
AlmaLinux,
|
||||
}
|
||||
|
||||
// IDMapping connects a distro ID like "ubuntu" to a Distro type
|
||||
var IDMapping = map[string]Type{
|
||||
"debian": Debian,
|
||||
"ubuntu": Ubuntu,
|
||||
"rhel": RedHat,
|
||||
"centos": CentOS,
|
||||
"fedora": Fedora,
|
||||
"alpine": Alpine,
|
||||
"busybox": Busybox,
|
||||
"amzn": AmazonLinux,
|
||||
"ol": OracleLinux,
|
||||
"arch": ArchLinux,
|
||||
"opensuse-leap": OpenSuseLeap,
|
||||
"sles": SLES,
|
||||
"photon": Photon,
|
||||
"windows": Windows,
|
||||
"mariner": Mariner,
|
||||
"rocky": RockyLinux,
|
||||
"almalinux": AlmaLinux,
|
||||
}
|
||||
|
||||
// String returns the string representation of the given Linux distribution.
|
||||
func (t Type) String() string {
|
||||
return string(t)
|
||||
}
|
||||
+7
-7
@@ -23,7 +23,7 @@ import (
|
||||
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/logger"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger"
|
||||
@@ -34,16 +34,16 @@ import (
|
||||
// CatalogPackages takes an inventory of packages from the given image from a particular perspective
|
||||
// (e.g. squashed source, all-layers source). Returns the discovered set of packages, the identified Linux
|
||||
// distribution, and the source object used to wrap the data source.
|
||||
func CatalogPackages(src *source.Source, cfg cataloger.Config) (*pkg.Catalog, []artifact.Relationship, *distro.Distro, error) {
|
||||
func CatalogPackages(src *source.Source, cfg cataloger.Config) (*pkg.Catalog, []artifact.Relationship, *linux.Release, error) {
|
||||
resolver, err := src.FileResolver(cfg.Search.Scope)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("unable to determine resolver while cataloging packages: %w", err)
|
||||
}
|
||||
|
||||
// find the distro
|
||||
theDistro := distro.Identify(resolver)
|
||||
if theDistro != nil {
|
||||
log.Infof("identified distro: %s", theDistro.String())
|
||||
release := linux.IdentifyRelease(resolver)
|
||||
if release != nil {
|
||||
log.Infof("identified distro: %s", release.String())
|
||||
} else {
|
||||
log.Info("could not identify distro")
|
||||
}
|
||||
@@ -64,12 +64,12 @@ func CatalogPackages(src *source.Source, cfg cataloger.Config) (*pkg.Catalog, []
|
||||
return nil, nil, nil, fmt.Errorf("unable to determine cataloger set from scheme=%+v", src.Metadata.Scheme)
|
||||
}
|
||||
|
||||
catalog, relationships, err := cataloger.Catalog(resolver, theDistro, catalogers...)
|
||||
catalog, relationships, err := cataloger.Catalog(resolver, release, catalogers...)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return catalog, relationships, theDistro, nil
|
||||
return catalog, relationships, release, nil
|
||||
}
|
||||
|
||||
// SetLogger sets the logger object used for all syft logging calls.
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
package linux
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/acobaugh/osrelease"
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
// returns a distro or nil
|
||||
type parseFunc func(string) (*Release, error)
|
||||
|
||||
type parseEntry struct {
|
||||
path string
|
||||
fn parseFunc
|
||||
}
|
||||
|
||||
var identityFiles = []parseEntry{
|
||||
{
|
||||
// most distros provide a link at this location
|
||||
path: "/etc/os-release",
|
||||
fn: parseOsRelease,
|
||||
},
|
||||
{
|
||||
// standard location for rhel & debian distros
|
||||
path: "/usr/lib/os-release",
|
||||
fn: parseOsRelease,
|
||||
},
|
||||
{
|
||||
// check for centos:6
|
||||
path: "/etc/system-release-cpe",
|
||||
fn: parseSystemReleaseCPE,
|
||||
},
|
||||
{
|
||||
// last ditch effort for determining older centos version distro information
|
||||
path: "/etc/redhat-release",
|
||||
fn: parseRedhatRelease,
|
||||
},
|
||||
// /////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// IMPORTANT! checking busybox must be last since other distros contain the busybox binary
|
||||
{
|
||||
// check for busybox
|
||||
path: "/bin/busybox",
|
||||
fn: parseBusyBox,
|
||||
},
|
||||
// /////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
}
|
||||
|
||||
// IdentifyRelease parses distro-specific files to discover and raise linux distribution release details.
|
||||
func IdentifyRelease(resolver source.FileResolver) *Release {
|
||||
for _, entry := range identityFiles {
|
||||
locations, err := resolver.FilesByPath(entry.path)
|
||||
if err != nil {
|
||||
log.Warnf("unable to get path locations from %s: %+v", entry.path, err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, location := range locations {
|
||||
contentReader, err := resolver.FileContentsByLocation(location)
|
||||
if err != nil {
|
||||
log.Debugf("unable to get contents from %s: %s", entry.path, err)
|
||||
continue
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadAll(contentReader)
|
||||
internal.CloseAndLogError(contentReader, location.VirtualPath)
|
||||
if err != nil {
|
||||
log.Warnf("unable to read %q: %+v", location.RealPath, err)
|
||||
break
|
||||
}
|
||||
|
||||
release, err := entry.fn(string(content))
|
||||
if err != nil {
|
||||
log.Warnf("unable to parse %q", location.RealPath)
|
||||
}
|
||||
|
||||
if release != nil {
|
||||
return release
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseOsRelease(contents string) (*Release, error) {
|
||||
values, err := osrelease.ReadString(contents)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read os-release file: %w", err)
|
||||
}
|
||||
|
||||
var idLike []string
|
||||
for _, s := range strings.Split(values["ID_LIKE"], " ") {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
idLike = append(idLike, s)
|
||||
}
|
||||
|
||||
r := Release{
|
||||
PrettyName: values["PRETTY_NAME"],
|
||||
Name: values["NAME"],
|
||||
ID: values["ID"],
|
||||
IDLike: idLike,
|
||||
Version: values["VERSION"],
|
||||
VersionID: values["VERSION_ID"],
|
||||
Variant: values["VARIANT"],
|
||||
VariantID: values["VARIANT_ID"],
|
||||
HomeURL: values["HOME_URL"],
|
||||
SupportURL: values["SUPPORT_URL"],
|
||||
BugReportURL: values["BUG_REPORT_URL"],
|
||||
PrivacyPolicyURL: values["PRIVACY_POLICY_URL"],
|
||||
CPEName: values["CPE_NAME"],
|
||||
}
|
||||
|
||||
// don't allow for empty contents to result in a Release object being created
|
||||
if cmp.Equal(r, Release{}) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
var busyboxVersionMatcher = regexp.MustCompile(`BusyBox v[\d.]+`)
|
||||
|
||||
func parseBusyBox(contents string) (*Release, error) {
|
||||
matches := busyboxVersionMatcher.FindAllString(contents, -1)
|
||||
for _, match := range matches {
|
||||
parts := strings.Split(match, " ")
|
||||
version := strings.ReplaceAll(parts[1], "v", "")
|
||||
|
||||
return simpleRelease(match, "busybox", version, ""), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// example CPE: cpe:/o:centos:linux:6:GA
|
||||
var systemReleaseCpeMatcher = regexp.MustCompile(`cpe:\/o:(.*?):.*?:(.*?):.*?$`)
|
||||
|
||||
// parseSystemReleaseCPE parses the older centos (6) file to determine distro metadata
|
||||
func parseSystemReleaseCPE(contents string) (*Release, error) {
|
||||
matches := systemReleaseCpeMatcher.FindAllStringSubmatch(contents, -1)
|
||||
for _, match := range matches {
|
||||
if len(match) < 3 {
|
||||
continue
|
||||
}
|
||||
return simpleRelease(match[1], strings.ToLower(match[1]), match[2], match[0]), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// example: "CentOS release 6.10 (Final)"
|
||||
var redhatReleaseMatcher = regexp.MustCompile(`(.*?)\srelease\s(\d\.\d+)`)
|
||||
|
||||
// parseRedhatRelease is a fallback parsing method for determining distro information in older redhat versions
|
||||
func parseRedhatRelease(contents string) (*Release, error) {
|
||||
matches := redhatReleaseMatcher.FindAllStringSubmatch(contents, -1)
|
||||
for _, match := range matches {
|
||||
if len(match) < 3 {
|
||||
continue
|
||||
}
|
||||
return simpleRelease(match[1], strings.ToLower(match[1]), match[2], ""), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func simpleRelease(prettyName, name, version, cpe string) *Release {
|
||||
return &Release{
|
||||
PrettyName: prettyName,
|
||||
Name: name,
|
||||
ID: name,
|
||||
IDLike: []string{name},
|
||||
Version: version,
|
||||
VersionID: version,
|
||||
CPEName: cpe,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,533 @@
|
||||
package linux
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIdentifyRelease(t *testing.T) {
|
||||
tests := []struct {
|
||||
fixture string
|
||||
release *Release
|
||||
}{
|
||||
{
|
||||
fixture: "test-fixtures/os/alpine",
|
||||
release: &Release{
|
||||
PrettyName: "Alpine Linux v3.11",
|
||||
Name: "Alpine Linux",
|
||||
ID: "alpine",
|
||||
IDLike: nil,
|
||||
VersionID: "3.11.6",
|
||||
HomeURL: "https://alpinelinux.org/",
|
||||
BugReportURL: "https://bugs.alpinelinux.org/",
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/amazon",
|
||||
release: &Release{
|
||||
PrettyName: "Amazon Linux 2",
|
||||
Name: "Amazon Linux",
|
||||
ID: "amzn",
|
||||
IDLike: []string{
|
||||
"centos",
|
||||
"rhel",
|
||||
"fedora",
|
||||
},
|
||||
Version: "2",
|
||||
VersionID: "2",
|
||||
HomeURL: "https://amazonlinux.com/",
|
||||
CPEName: "cpe:2.3:o:amazon:amazon_linux:2",
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/busybox",
|
||||
release: &Release{
|
||||
PrettyName: "BusyBox v1.31.1",
|
||||
Name: "busybox",
|
||||
ID: "busybox",
|
||||
IDLike: []string{"busybox"},
|
||||
Version: "1.31.1",
|
||||
VersionID: "1.31.1",
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/centos",
|
||||
release: &Release{
|
||||
PrettyName: "CentOS Linux 8 (Core)",
|
||||
Name: "CentOS Linux",
|
||||
ID: "centos",
|
||||
IDLike: []string{"rhel",
|
||||
"fedora",
|
||||
},
|
||||
Version: "8 (Core)",
|
||||
VersionID: "8",
|
||||
HomeURL: "https://www.centos.org/",
|
||||
BugReportURL: "https://bugs.centos.org/",
|
||||
CPEName: "cpe:/o:centos:centos:8",
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/debian",
|
||||
release: &Release{
|
||||
PrettyName: "Debian GNU/Linux 8 (jessie)",
|
||||
Name: "Debian GNU/Linux",
|
||||
ID: "debian",
|
||||
IDLike: nil,
|
||||
Version: "8 (jessie)",
|
||||
VersionID: "8",
|
||||
HomeURL: "http://www.debian.org/",
|
||||
SupportURL: "http://www.debian.org/support",
|
||||
BugReportURL: "https://bugs.debian.org/",
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/fedora",
|
||||
release: &Release{
|
||||
PrettyName: "Fedora 31 (Container Image)",
|
||||
Name: "Fedora",
|
||||
ID: "fedora",
|
||||
IDLike: nil,
|
||||
Version: "31 (Container Image)",
|
||||
VersionID: "31",
|
||||
Variant: "Container Image",
|
||||
VariantID: "container",
|
||||
HomeURL: "https://fedoraproject.org/",
|
||||
SupportURL: "https://fedoraproject.org/wiki/Communicating_and_getting_help",
|
||||
BugReportURL: "https://bugzilla.redhat.com/",
|
||||
PrivacyPolicyURL: "https://fedoraproject.org/wiki/Legal:PrivacyPolicy",
|
||||
CPEName: "cpe:/o:fedoraproject:fedora:31",
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/redhat",
|
||||
release: &Release{
|
||||
PrettyName: "Red Hat Enterprise Linux Server 7.3 (Maipo)",
|
||||
Name: "Red Hat Enterprise Linux Server",
|
||||
ID: "rhel",
|
||||
IDLike: []string{"fedora"},
|
||||
Version: "7.3 (Maipo)",
|
||||
VersionID: "7.3",
|
||||
HomeURL: "https://www.redhat.com/",
|
||||
BugReportURL: "https://bugzilla.redhat.com/",
|
||||
CPEName: "cpe:/o:redhat:enterprise_linux:7.3:GA:server",
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/ubuntu",
|
||||
release: &Release{
|
||||
PrettyName: "Ubuntu 20.04 LTS",
|
||||
Name: "Ubuntu",
|
||||
ID: "ubuntu",
|
||||
IDLike: []string{"debian"},
|
||||
Version: "20.04 LTS (Focal Fossa)",
|
||||
VersionID: "20.04",
|
||||
HomeURL: "https://www.ubuntu.com/",
|
||||
SupportURL: "https://help.ubuntu.com/",
|
||||
BugReportURL: "https://bugs.launchpad.net/ubuntu/",
|
||||
PrivacyPolicyURL: "https://www.ubuntu.com/legal/terms-and-policies/privacy-policy",
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/oraclelinux",
|
||||
release: &Release{
|
||||
PrettyName: "Oracle Linux Server 8.3",
|
||||
Name: "Oracle Linux Server",
|
||||
ID: "ol",
|
||||
IDLike: []string{"fedora"},
|
||||
Version: "8.3",
|
||||
VersionID: "8.3",
|
||||
Variant: "Server",
|
||||
VariantID: "server",
|
||||
HomeURL: "https://linux.oracle.com/",
|
||||
BugReportURL: "https://bugzilla.oracle.com/",
|
||||
CPEName: "cpe:/o:oracle:linux:8:3:server",
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/empty",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/custom",
|
||||
release: &Release{
|
||||
PrettyName: "CentOS Linux 8 (Core)",
|
||||
Name: "Scientific Linux",
|
||||
ID: "scientific",
|
||||
IDLike: []string{
|
||||
"rhel",
|
||||
"fedora",
|
||||
},
|
||||
Version: "16 (Core)",
|
||||
VersionID: "8",
|
||||
HomeURL: "https://www.centos.org/",
|
||||
BugReportURL: "https://bugs.centos.org/",
|
||||
CPEName: "cpe:/o:centos:centos:8",
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/opensuse-leap",
|
||||
release: &Release{
|
||||
PrettyName: "openSUSE Leap 15.2",
|
||||
Name: "openSUSE Leap",
|
||||
ID: "opensuse-leap",
|
||||
IDLike: []string{
|
||||
"suse",
|
||||
"opensuse",
|
||||
},
|
||||
Version: "15.2",
|
||||
VersionID: "15.2",
|
||||
HomeURL: "https://www.opensuse.org/",
|
||||
BugReportURL: "https://bugs.opensuse.org",
|
||||
CPEName: "cpe:/o:opensuse:leap:15.2",
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/sles",
|
||||
release: &Release{
|
||||
PrettyName: "SUSE Linux Enterprise Server 15 SP2",
|
||||
Name: "SLES",
|
||||
ID: "sles",
|
||||
IDLike: []string{"suse"},
|
||||
Version: "15-SP2",
|
||||
VersionID: "15.2",
|
||||
CPEName: "cpe:/o:suse:sles:15:sp2",
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/photon",
|
||||
release: &Release{
|
||||
PrettyName: "VMware Photon OS/Linux",
|
||||
Name: "VMware Photon OS",
|
||||
ID: "photon",
|
||||
IDLike: nil,
|
||||
Version: "2.0",
|
||||
VersionID: "2.0",
|
||||
HomeURL: "https://vmware.github.io/photon/",
|
||||
BugReportURL: "https://github.com/vmware/photon/issues",
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/arch",
|
||||
release: &Release{
|
||||
PrettyName: "Arch Linux",
|
||||
Name: "Arch Linux",
|
||||
ID: "arch",
|
||||
IDLike: nil,
|
||||
HomeURL: "https://www.archlinux.org/",
|
||||
SupportURL: "https://bbs.archlinux.org/",
|
||||
BugReportURL: "https://bugs.archlinux.org/",
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/partial-fields/missing-id",
|
||||
release: &Release{
|
||||
Name: "Debian GNU/Linux",
|
||||
IDLike: []string{"debian"},
|
||||
VersionID: "8",
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/partial-fields/unknown-id",
|
||||
release: &Release{
|
||||
Name: "Debian GNU/Linux",
|
||||
ID: "my-awesome-distro",
|
||||
IDLike: []string{"debian"},
|
||||
VersionID: "8",
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/partial-fields/missing-version",
|
||||
release: &Release{
|
||||
Name: "Debian GNU/Linux",
|
||||
IDLike: []string{"debian"},
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/centos6",
|
||||
release: &Release{
|
||||
PrettyName: "centos",
|
||||
Name: "centos",
|
||||
ID: "centos",
|
||||
IDLike: []string{"centos"},
|
||||
Version: "6",
|
||||
VersionID: "6",
|
||||
CPEName: "cpe:/o:centos:linux:6:GA",
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/centos5",
|
||||
release: &Release{
|
||||
PrettyName: "CentOS",
|
||||
Name: "centos",
|
||||
ID: "centos",
|
||||
IDLike: []string{"centos"},
|
||||
Version: "5.7",
|
||||
VersionID: "5.7",
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/mariner",
|
||||
release: &Release{
|
||||
PrettyName: "CBL-Mariner/Linux",
|
||||
Name: "Common Base Linux Mariner",
|
||||
ID: "mariner",
|
||||
IDLike: nil,
|
||||
Version: "1.0.20210901",
|
||||
VersionID: "1.0",
|
||||
HomeURL: "https://aka.ms/cbl-mariner",
|
||||
SupportURL: "https://aka.ms/cbl-mariner",
|
||||
BugReportURL: "https://aka.ms/cbl-mariner",
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/rockylinux",
|
||||
release: &Release{
|
||||
PrettyName: "Rocky Linux 8.4 (Green Obsidian)",
|
||||
Name: "Rocky Linux",
|
||||
ID: "rocky",
|
||||
IDLike: []string{
|
||||
"rhel",
|
||||
"fedora",
|
||||
},
|
||||
Version: "8.4 (Green Obsidian)",
|
||||
VersionID: "8.4",
|
||||
HomeURL: "https://rockylinux.org/",
|
||||
BugReportURL: "https://bugs.rockylinux.org/",
|
||||
CPEName: "cpe:/o:rocky:rocky:8.4:GA",
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/almalinux",
|
||||
release: &Release{
|
||||
PrettyName: "AlmaLinux 8.4 (Electric Cheetah)",
|
||||
Name: "AlmaLinux",
|
||||
ID: "almalinux",
|
||||
IDLike: []string{
|
||||
"rhel",
|
||||
"centos",
|
||||
"fedora",
|
||||
},
|
||||
Version: "8.4 (Electric Cheetah)",
|
||||
VersionID: "8.4",
|
||||
HomeURL: "https://almalinux.org/",
|
||||
BugReportURL: "https://bugs.almalinux.org/",
|
||||
CPEName: "cpe:/o:almalinux:almalinux:8.4:GA",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.fixture, func(t *testing.T) {
|
||||
s, err := source.NewFromDirectory(test.fixture)
|
||||
require.NoError(t, err)
|
||||
|
||||
resolver, err := s.FileResolver(source.SquashedScope)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.release, IdentifyRelease(resolver))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseOsRelease(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
fixture string
|
||||
release *Release
|
||||
}{
|
||||
{
|
||||
fixture: "test-fixtures/ubuntu-20.04",
|
||||
|
||||
release: &Release{
|
||||
PrettyName: "Ubuntu 20.04 LTS",
|
||||
Name: "Ubuntu",
|
||||
ID: "ubuntu",
|
||||
IDLike: []string{"debian"},
|
||||
Version: "20.04 LTS (Focal Fossa)",
|
||||
VersionID: "20.04",
|
||||
HomeURL: "https://www.ubuntu.com/",
|
||||
SupportURL: "https://help.ubuntu.com/",
|
||||
BugReportURL: "https://bugs.launchpad.net/ubuntu/",
|
||||
PrivacyPolicyURL: "https://www.ubuntu.com/legal/terms-and-policies/privacy-policy",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
fixture: "test-fixtures/debian-8",
|
||||
|
||||
release: &Release{
|
||||
PrettyName: "Debian GNU/Linux 8 (jessie)",
|
||||
Name: "Debian GNU/Linux",
|
||||
ID: "debian",
|
||||
IDLike: nil,
|
||||
Version: "8 (jessie)",
|
||||
VersionID: "8",
|
||||
HomeURL: "http://www.debian.org/",
|
||||
SupportURL: "http://www.debian.org/support",
|
||||
BugReportURL: "https://bugs.debian.org/",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
fixture: "test-fixtures/centos-8",
|
||||
|
||||
release: &Release{
|
||||
PrettyName: "CentOS Linux 8 (Core)",
|
||||
Name: "CentOS Linux",
|
||||
ID: "centos",
|
||||
IDLike: []string{
|
||||
"rhel",
|
||||
"fedora",
|
||||
},
|
||||
Version: "8 (Core)",
|
||||
VersionID: "8",
|
||||
HomeURL: "https://www.centos.org/",
|
||||
BugReportURL: "https://bugs.centos.org/",
|
||||
CPEName: "cpe:/o:centos:centos:8",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
fixture: "test-fixtures/rhel-8",
|
||||
|
||||
release: &Release{
|
||||
PrettyName: "Red Hat Enterprise Linux 8.1 (Ootpa)",
|
||||
Name: "Red Hat Enterprise Linux",
|
||||
ID: "rhel",
|
||||
IDLike: []string{"fedora"},
|
||||
Version: "8.1 (Ootpa)",
|
||||
VersionID: "8.1",
|
||||
HomeURL: "https://www.redhat.com/",
|
||||
BugReportURL: "https://bugzilla.redhat.com/",
|
||||
CPEName: "cpe:/o:redhat:enterprise_linux:8.1:GA",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
fixture: "test-fixtures/unprintable",
|
||||
|
||||
release: &Release{
|
||||
PrettyName: "Debian GNU/Linux 8 (jessie)",
|
||||
Name: "Debian GNU/Linux",
|
||||
ID: "debian",
|
||||
IDLike: nil,
|
||||
Version: "8 (jessie)",
|
||||
VersionID: "8",
|
||||
HomeURL: "http://www.debian.org/",
|
||||
SupportURL: "http://www.debian.org/support",
|
||||
BugReportURL: "https://bugs.debian.org/",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.fixture,
|
||||
func(t *testing.T) {
|
||||
release,
|
||||
err := parseOsRelease(retrieveFixtureContentsAsString(test.fixture,
|
||||
t))
|
||||
require.NoError(t,
|
||||
err)
|
||||
assert.Equal(t,
|
||||
test.release,
|
||||
release)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestParseSystemReleaseCPE(t *testing.T) {
|
||||
tests := []struct {
|
||||
fixture string
|
||||
release *Release
|
||||
}{
|
||||
{
|
||||
fixture: "test-fixtures/os/centos6/etc/system-release-cpe",
|
||||
release: &Release{
|
||||
PrettyName: "centos",
|
||||
Name: "centos",
|
||||
ID: "centos",
|
||||
IDLike: []string{"centos"},
|
||||
Version: "6",
|
||||
VersionID: "6",
|
||||
CPEName: "cpe:/o:centos:linux:6:GA",
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/bad-system-release-cpe",
|
||||
release: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.fixture, func(t *testing.T) {
|
||||
contents := retrieveFixtureContentsAsString(test.fixture, t)
|
||||
release, err := parseSystemReleaseCPE(contents)
|
||||
require.NoError(t, err)
|
||||
if test.release == nil {
|
||||
assert.Nil(t, release)
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, test.release, release)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRedhatRelease(t *testing.T) {
|
||||
tests := []struct {
|
||||
fixture string
|
||||
name string
|
||||
release *Release
|
||||
}{
|
||||
{
|
||||
fixture: "test-fixtures/os/centos5/etc/redhat-release",
|
||||
name: "Centos 5",
|
||||
release: &Release{
|
||||
PrettyName: "CentOS",
|
||||
Name: "centos",
|
||||
ID: "centos",
|
||||
IDLike: []string{"centos"},
|
||||
Version: "5.7",
|
||||
VersionID: "5.7",
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/bad-redhat-release",
|
||||
name: "Centos 5 Bad Redhat Release",
|
||||
release: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
release, err := parseRedhatRelease(retrieveFixtureContentsAsString(test.fixture, t))
|
||||
require.NoError(t, err)
|
||||
if test.release == nil {
|
||||
assert.Nil(t, release)
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, test.release, release)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func retrieveFixtureContentsAsString(fixturePath string, t *testing.T) string {
|
||||
fixture, err := os.Open(fixturePath)
|
||||
if err != nil {
|
||||
t.Fatalf("could not open test fixture=%s: %+v", fixturePath, err)
|
||||
}
|
||||
defer fixture.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to read fixture file: %+v", err)
|
||||
}
|
||||
|
||||
return string(b)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package linux
|
||||
|
||||
// Release represents Linux Distribution release information as specified from https://www.freedesktop.org/software/systemd/man/os-release.html
|
||||
type Release struct {
|
||||
PrettyName string // A pretty operating system name in a format suitable for presentation to the user.
|
||||
Name string // identifies the operating system, without a version component, and suitable for presentation to the user.
|
||||
ID string // identifies the operating system, excluding any version information and suitable for processing by scripts or usage in generated filenames.
|
||||
IDLike []string // list of operating system identifiers in the same syntax as the ID= setting. It should list identifiers of operating systems that are closely related to the local operating system in regards to packaging and programming interfaces.
|
||||
Version string // identifies the operating system version, excluding any OS name information, possibly including a release code name, and suitable for presentation to the user.
|
||||
VersionID string // identifies the operating system version, excluding any OS name information or release code name, and suitable for processing by scripts or usage in generated filenames.
|
||||
Variant string // identifies a specific variant or edition of the operating system suitable for presentation to the user.
|
||||
VariantID string // identifies a specific variant or edition of the operating system. This may be interpreted by other packages in order to determine a divergent default configuration.
|
||||
HomeURL string
|
||||
SupportURL string
|
||||
BugReportURL string
|
||||
PrivacyPolicyURL string
|
||||
CPEName string // A CPE name for the operating system, in URI binding syntax
|
||||
}
|
||||
|
||||
func (r *Release) String() string {
|
||||
if r == nil {
|
||||
return "unknown"
|
||||
}
|
||||
if r.PrettyName != "" {
|
||||
return r.PrettyName
|
||||
}
|
||||
if r.Name != "" {
|
||||
return r.Name
|
||||
}
|
||||
if r.Version != "" {
|
||||
return r.ID + " " + r.Version
|
||||
}
|
||||
|
||||
return r.ID + " " + r.VersionID
|
||||
}
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/event"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/common/cpe"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
@@ -41,7 +41,7 @@ func newMonitor() (*progress.Manual, *progress.Manual) {
|
||||
// In order to efficiently retrieve contents from a underlying container image the content fetch requests are
|
||||
// done in bulk. Specifically, all files of interest are collected from each catalogers and accumulated into a single
|
||||
// request.
|
||||
func Catalog(resolver source.FileResolver, theDistro *distro.Distro, catalogers ...Cataloger) (*pkg.Catalog, []artifact.Relationship, error) {
|
||||
func Catalog(resolver source.FileResolver, release *linux.Release, catalogers ...Cataloger) (*pkg.Catalog, []artifact.Relationship, error) {
|
||||
catalog := pkg.NewCatalog()
|
||||
var allRelationships []artifact.Relationship
|
||||
|
||||
@@ -68,7 +68,7 @@ func Catalog(resolver source.FileResolver, theDistro *distro.Distro, catalogers
|
||||
p.CPEs = cpe.Generate(p)
|
||||
|
||||
// generate PURL (note: this is excluded from package ID, so is safe to mutate)
|
||||
p.PURL = generatePackageURL(p, theDistro)
|
||||
p.PURL = generatePackageURL(p, release)
|
||||
|
||||
// create file-to-package relationships for files owned by the package
|
||||
owningRelationships, err := packageFileOwnershipRelationships(p, resolver)
|
||||
|
||||
@@ -5,18 +5,18 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
// generatePackageURL returns a package-URL representation of the given package (see https://github.com/package-url/purl-spec)
|
||||
func generatePackageURL(p pkg.Package, d *distro.Distro) string {
|
||||
func generatePackageURL(p pkg.Package, release *linux.Release) string {
|
||||
// default to pURLs on the metadata
|
||||
if p.Metadata != nil {
|
||||
if i, ok := p.Metadata.(interface{ PackageURL() string }); ok {
|
||||
return i.PackageURL()
|
||||
} else if i, ok := p.Metadata.(interface{ PackageURL(*distro.Distro) string }); ok {
|
||||
return i.PackageURL(d)
|
||||
} else if i, ok := p.Metadata.(interface{ PackageURL(*linux.Release) string }); ok {
|
||||
return i.PackageURL(release)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package cataloger
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
)
|
||||
@@ -12,7 +12,7 @@ func TestPackageURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pkg pkg.Package
|
||||
distro *distro.Distro
|
||||
distro *linux.Release
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
@@ -75,8 +75,8 @@ func TestPackageURL(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "deb with arch",
|
||||
distro: &distro.Distro{
|
||||
Type: distro.Ubuntu,
|
||||
distro: &linux.Release{
|
||||
ID: "ubuntu",
|
||||
},
|
||||
pkg: pkg.Package{
|
||||
Name: "bad-name",
|
||||
@@ -92,8 +92,8 @@ func TestPackageURL(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "deb with epoch",
|
||||
distro: &distro.Distro{
|
||||
Type: distro.CentOS,
|
||||
distro: &linux.Release{
|
||||
ID: "centos",
|
||||
},
|
||||
pkg: pkg.Package{
|
||||
Name: "bad-name",
|
||||
@@ -111,8 +111,8 @@ func TestPackageURL(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "deb with nil epoch",
|
||||
distro: &distro.Distro{
|
||||
Type: distro.CentOS,
|
||||
distro: &linux.Release{
|
||||
ID: "centos",
|
||||
},
|
||||
pkg: pkg.Package{
|
||||
Name: "bad-name",
|
||||
@@ -129,10 +129,8 @@ func TestPackageURL(t *testing.T) {
|
||||
expected: "pkg:rpm/centos/name@0.1.0-3?arch=amd64",
|
||||
},
|
||||
{
|
||||
name: "deb with unknown distro",
|
||||
distro: &distro.Distro{
|
||||
Type: distro.UnknownDistroType,
|
||||
},
|
||||
name: "deb with unknown distro",
|
||||
distro: nil,
|
||||
pkg: pkg.Package{
|
||||
Name: "name",
|
||||
Version: "v0.1.0",
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/anchore/syft/syft/file"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/scylladb/go-set/strset"
|
||||
)
|
||||
|
||||
@@ -35,15 +35,15 @@ type DpkgFileRecord struct {
|
||||
}
|
||||
|
||||
// PackageURL returns the PURL for the specific Debian package (see https://github.com/package-url/purl-spec)
|
||||
func (m DpkgMetadata) PackageURL(d *distro.Distro) string {
|
||||
if d == nil {
|
||||
func (m DpkgMetadata) PackageURL(distro *linux.Release) string {
|
||||
if distro == nil {
|
||||
return ""
|
||||
}
|
||||
pURL := packageurl.NewPackageURL(
|
||||
// TODO: replace with `packageurl.TypeDebian` upon merge of https://github.com/package-url/packageurl-go/pull/21
|
||||
// TODO: or, since we're now using an Anchore fork of this module, we could do this sooner.
|
||||
"deb",
|
||||
d.Type.String(),
|
||||
distro.ID,
|
||||
m.Package,
|
||||
m.Version,
|
||||
packageurl.Qualifiers{
|
||||
|
||||
@@ -6,19 +6,19 @@ import (
|
||||
|
||||
"github.com/go-test/deep"
|
||||
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
)
|
||||
|
||||
func TestDpkgMetadata_pURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
distro distro.Distro
|
||||
distro linux.Release
|
||||
metadata DpkgMetadata
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
distro: distro.Distro{
|
||||
Type: distro.Debian,
|
||||
distro: linux.Release{
|
||||
ID: "debian",
|
||||
},
|
||||
metadata: DpkgMetadata{
|
||||
Package: "p",
|
||||
@@ -29,8 +29,8 @@ func TestDpkgMetadata_pURL(t *testing.T) {
|
||||
expected: "pkg:deb/debian/p@v?arch=a",
|
||||
},
|
||||
{
|
||||
distro: distro.Distro{
|
||||
Type: distro.Ubuntu,
|
||||
distro: linux.Release{
|
||||
ID: "ubuntu",
|
||||
},
|
||||
metadata: DpkgMetadata{
|
||||
Package: "p",
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
)
|
||||
|
||||
const RpmDBGlob = "**/var/lib/rpm/Packages"
|
||||
@@ -46,8 +46,8 @@ type RpmdbFileRecord struct {
|
||||
type RpmdbFileMode uint16
|
||||
|
||||
// PackageURL returns the PURL for the specific RHEL package (see https://github.com/package-url/purl-spec)
|
||||
func (m RpmdbMetadata) PackageURL(d *distro.Distro) string {
|
||||
if d == nil {
|
||||
func (m RpmdbMetadata) PackageURL(distro *linux.Release) string {
|
||||
if distro == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ func (m RpmdbMetadata) PackageURL(d *distro.Distro) string {
|
||||
|
||||
pURL := packageurl.NewPackageURL(
|
||||
packageurl.TypeRPM,
|
||||
d.Type.String(),
|
||||
distro.ID,
|
||||
m.Name,
|
||||
// for purl the epoch is a qualifier, not part of the version
|
||||
// see https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst under the RPM section
|
||||
|
||||
@@ -6,19 +6,19 @@ import (
|
||||
|
||||
"github.com/go-test/deep"
|
||||
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
)
|
||||
|
||||
func TestRpmMetadata_pURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
distro distro.Distro
|
||||
distro linux.Release
|
||||
metadata RpmdbMetadata
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
distro: distro.Distro{
|
||||
Type: distro.CentOS,
|
||||
distro: linux.Release{
|
||||
ID: "centos",
|
||||
},
|
||||
metadata: RpmdbMetadata{
|
||||
Name: "p",
|
||||
@@ -30,8 +30,8 @@ func TestRpmMetadata_pURL(t *testing.T) {
|
||||
expected: "pkg:rpm/centos/p@v-r?arch=a&epoch=1",
|
||||
},
|
||||
{
|
||||
distro: distro.Distro{
|
||||
Type: distro.RedHat,
|
||||
distro: linux.Release{
|
||||
ID: "rhel",
|
||||
},
|
||||
metadata: RpmdbMetadata{
|
||||
Name: "p",
|
||||
@@ -40,7 +40,7 @@ func TestRpmMetadata_pURL(t *testing.T) {
|
||||
Release: "r",
|
||||
Epoch: nil,
|
||||
},
|
||||
expected: "pkg:rpm/redhat/p@v-r?arch=a",
|
||||
expected: "pkg:rpm/rhel/p@v-r?arch=a",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@ package sbom
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
@@ -22,7 +22,7 @@ type Artifacts struct {
|
||||
FileClassifications map[source.Coordinates][]file.Classification
|
||||
FileContents map[source.Coordinates]string
|
||||
Secrets map[source.Coordinates][]file.SearchResult
|
||||
Distro *distro.Distro
|
||||
LinuxDistribution *linux.Release
|
||||
}
|
||||
|
||||
type Descriptor struct {
|
||||
|
||||
Reference in New Issue
Block a user