mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 16:33:21 +01:00
Merge pull request #57 from anchore/issue-33
Initial take on distro detection
This commit is contained in:
commit
812934c964
@ -56,11 +56,11 @@ func doRunCmd(_ *cobra.Command, args []string) int {
|
|||||||
defer stereoscope.Cleanup()
|
defer stereoscope.Cleanup()
|
||||||
|
|
||||||
log.Info("Identifying Distro")
|
log.Info("Identifying Distro")
|
||||||
distro, err := imgbom.IdentifyDistro(img)
|
distro := imgbom.IdentifyDistro(img)
|
||||||
if err != nil {
|
if distro == nil {
|
||||||
log.Errorf("error identifying Distro: %w", err)
|
log.Errorf("error identifying distro")
|
||||||
} else {
|
} else {
|
||||||
log.Info(" Distro: %s", distro)
|
log.Infof(" Distro: %s", distro)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Cataloging image")
|
log.Info("Cataloging image")
|
||||||
|
|||||||
@ -33,5 +33,10 @@ func (d Distro) FullVersion() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d Distro) String() string {
|
func (d Distro) String() string {
|
||||||
return fmt.Sprintf("%s %s", d.Type, d.Version)
|
return fmt.Sprintf("%s %s", d.Type, d.RawVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name provides a string repr of the distro
|
||||||
|
func (d Distro) Name() string {
|
||||||
|
return fmt.Sprintf("%s", d.Type)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,104 @@
|
|||||||
package distro
|
package distro
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/anchore/imgbom/internal/log"
|
||||||
|
"github.com/anchore/stereoscope/pkg/file"
|
||||||
"github.com/anchore/stereoscope/pkg/image"
|
"github.com/anchore/stereoscope/pkg/image"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Identify(img *image.Image) (Distro, error) {
|
// returns a distro or nil
|
||||||
|
type parseFunc func(string) *Distro
|
||||||
|
|
||||||
|
// Identify parses distro-specific files to determine distro metadata like version and release
|
||||||
|
func Identify(img *image.Image) *Distro {
|
||||||
// TODO: implement me based off of https://github.com/anchore/anchore-engine/blob/78b23d7e8f007005c070673405b5e23730a660e0/anchore_engine/analyzers/utils.py#L131
|
// TODO: implement me based off of https://github.com/anchore/anchore-engine/blob/78b23d7e8f007005c070673405b5e23730a660e0/anchore_engine/analyzers/utils.py#L131
|
||||||
|
|
||||||
return NewDistro(UnknownDistro, "0.0.0")
|
identityFiles := map[file.Path]parseFunc{
|
||||||
|
"/etc/os-release": parseOsRelease,
|
||||||
|
// Debian and Debian-based distros have the same contents linked from this path
|
||||||
|
"/usr/lib/os-release": parseOsRelease,
|
||||||
|
// TODO: change this to /bin/busybox when stereoscope deals with hardlinks
|
||||||
|
"/bin/[": parseBusyBox,
|
||||||
|
}
|
||||||
|
|
||||||
|
for path, fn := range identityFiles {
|
||||||
|
|
||||||
|
contents, err := img.FileContentsFromSquash(path)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("unable to get contents from %s: %s", path, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if contents == "" {
|
||||||
|
log.Debugf("no contents in file, skipping: %s", path)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
distro := fn(contents)
|
||||||
|
|
||||||
|
if distro == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return distro
|
||||||
|
|
||||||
|
}
|
||||||
|
// TODO: is it useful to know partially detected distros? where the ID is known but not the version (and viceversa?)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func assembleDistro(name, version string) *Distro {
|
||||||
|
distroType, ok := Mappings[name]
|
||||||
|
|
||||||
|
// Both distro and version must be present
|
||||||
|
if len(name) == 0 || len(version) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
distro, err := NewDistro(distroType, version)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &distro
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseOsRelease(contents string) *Distro {
|
||||||
|
id, vers := "", ""
|
||||||
|
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 = value
|
||||||
|
case "VERSION_ID":
|
||||||
|
vers = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return assembleDistro(id, vers)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 := assembleDistro("busybox", version)
|
||||||
|
if distro != nil {
|
||||||
|
return distro
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
108
imgbom/distro/identify_test.go
Normal file
108
imgbom/distro/identify_test.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
package distro
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
name := fmt.Sprintf("%s:%s", test.name, test.RawVersion)
|
||||||
|
fixture, err := os.Open(test.fixture)
|
||||||
|
defer fixture.Close()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to open fixture: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := ioutil.ReadAll(fixture)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to read fixture file: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
contents := string(b)
|
||||||
|
|
||||||
|
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-version",
|
||||||
|
name: "No version",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fixture: "test-fixtures/bad-id",
|
||||||
|
name: "No name ID",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
name := fmt.Sprintf("%s:%s", test.name, test.fixture)
|
||||||
|
fixture, err := os.Open(test.fixture)
|
||||||
|
defer fixture.Close()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to open fixture: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := ioutil.ReadAll(fixture)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to read fixture file: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
contents := string(b)
|
||||||
|
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
distro := parseOsRelease(contents)
|
||||||
|
if distro != nil {
|
||||||
|
t.Errorf("unexpected non-nil distro: '%s' != nil", distro)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
15
imgbom/distro/test-fixtures/bad-id
Normal file
15
imgbom/distro/test-fixtures/bad-id
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
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"
|
||||||
|
|
||||||
15
imgbom/distro/test-fixtures/bad-version
Normal file
15
imgbom/distro/test-fixtures/bad-version
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
NAME="Red Hat Enterprise Linux"
|
||||||
|
VERSION="8.1 (Ootpa)"
|
||||||
|
ID="rhel"
|
||||||
|
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"
|
||||||
17
imgbom/distro/test-fixtures/centos-8
Normal file
17
imgbom/distro/test-fixtures/centos-8
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
NAME="CentOS Linux"
|
||||||
|
VERSION="8 (Core)"
|
||||||
|
ID="centos"
|
||||||
|
ID_LIKE="rhel fedora"
|
||||||
|
VERSION_ID="8"
|
||||||
|
PLATFORM_ID="platform:el8"
|
||||||
|
PRETTY_NAME="CentOS Linux 8 (Core)"
|
||||||
|
ANSI_COLOR="0;31"
|
||||||
|
CPE_NAME="cpe:/o:centos:centos:8"
|
||||||
|
HOME_URL="https://www.centos.org/"
|
||||||
|
BUG_REPORT_URL="https://bugs.centos.org/"
|
||||||
|
|
||||||
|
CENTOS_MANTISBT_PROJECT="CentOS-8"
|
||||||
|
CENTOS_MANTISBT_PROJECT_VERSION="8"
|
||||||
|
REDHAT_SUPPORT_PRODUCT="centos"
|
||||||
|
REDHAT_SUPPORT_PRODUCT_VERSION="8"
|
||||||
|
|
||||||
8
imgbom/distro/test-fixtures/debian-8
Normal file
8
imgbom/distro/test-fixtures/debian-8
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
PRETTY_NAME="Debian GNU/Linux 8 (jessie)"
|
||||||
|
NAME="Debian GNU/Linux"
|
||||||
|
VERSION_ID="8"
|
||||||
|
VERSION="8 (jessie)"
|
||||||
|
ID=debian
|
||||||
|
HOME_URL="http://www.debian.org/"
|
||||||
|
SUPPORT_URL="http://www.debian.org/support"
|
||||||
|
BUG_REPORT_URL="https://bugs.debian.org/"
|
||||||
16
imgbom/distro/test-fixtures/rhel-8
Normal file
16
imgbom/distro/test-fixtures/rhel-8
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
NAME="Red Hat Enterprise Linux"
|
||||||
|
VERSION="8.1 (Ootpa)"
|
||||||
|
ID="rhel"
|
||||||
|
ID_LIKE="fedora"
|
||||||
|
VERSION_ID="8.1"
|
||||||
|
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"
|
||||||
12
imgbom/distro/test-fixtures/ubuntu-20.04
Normal file
12
imgbom/distro/test-fixtures/ubuntu-20.04
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
NAME="Ubuntu"
|
||||||
|
VERSION="20.04 LTS (Focal Fossa)"
|
||||||
|
ID=ubuntu
|
||||||
|
ID_LIKE=debian
|
||||||
|
PRETTY_NAME="Ubuntu 20.04 LTS"
|
||||||
|
VERSION_ID="20.04"
|
||||||
|
HOME_URL="https://www.ubuntu.com/"
|
||||||
|
SUPPORT_URL="https://help.ubuntu.com/"
|
||||||
|
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
|
||||||
|
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
|
||||||
|
VERSION_CODENAME=focal
|
||||||
|
UBUNTU_CODENAME=focal
|
||||||
@ -3,41 +3,46 @@ package distro
|
|||||||
const (
|
const (
|
||||||
UnknownDistro Type = iota
|
UnknownDistro Type = iota
|
||||||
Debian
|
Debian
|
||||||
// Ubuntu
|
Ubuntu
|
||||||
// RedHat
|
RedHat
|
||||||
// CentOS
|
CentOS
|
||||||
// Fedora
|
// Fedora
|
||||||
// Alpine
|
// Alpine
|
||||||
// Busybox
|
Busybox
|
||||||
// AmazonLinux
|
// AmazonLinux
|
||||||
// OracleLinux
|
// OracleLinux
|
||||||
// ArchLinux
|
// ArchLinux
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// UnknownVersion is a default of 0.0.0 when it can't be parsed
|
||||||
|
UnknownVersion string = "0.0.0"
|
||||||
|
)
|
||||||
|
|
||||||
type Type int
|
type Type int
|
||||||
|
|
||||||
var distroStr = []string{
|
var distroStr = []string{
|
||||||
"UnknownDistro",
|
"UnknownDistro",
|
||||||
"Debian",
|
"debian",
|
||||||
// "Ubuntu",
|
"ubuntu",
|
||||||
// "RedHat",
|
"redhat",
|
||||||
// "CentOS",
|
"centos",
|
||||||
// "Fedora",
|
// "fedora",
|
||||||
// "Alpine",
|
// "alpine",
|
||||||
// "Busybox",
|
"busybox",
|
||||||
// "AmazonLinux",
|
// "amazn",
|
||||||
// "OracleLinux",
|
// "oraclelinux",
|
||||||
// "ArchLinux",
|
// "archlinux",
|
||||||
}
|
}
|
||||||
|
|
||||||
var All = []Type{
|
var All = []Type{
|
||||||
Debian,
|
Debian,
|
||||||
// Ubuntu,
|
Ubuntu,
|
||||||
// RedHat,
|
RedHat,
|
||||||
// CentOS,
|
CentOS,
|
||||||
// Fedora,
|
// Fedora,
|
||||||
// Alpine,
|
// Alpine,
|
||||||
// Busybox,
|
Busybox,
|
||||||
// AmazonLinux,
|
// AmazonLinux,
|
||||||
// OracleLinux,
|
// OracleLinux,
|
||||||
// ArchLinux,
|
// ArchLinux,
|
||||||
@ -50,3 +55,17 @@ func (t Type) String() string {
|
|||||||
|
|
||||||
return distroStr[t]
|
return distroStr[t]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mappings connects a distro ID like "ubuntu" to a Distro type
|
||||||
|
var Mappings = map[string]Type{
|
||||||
|
"debian": Debian,
|
||||||
|
"ubuntu": Ubuntu,
|
||||||
|
"rhel": RedHat,
|
||||||
|
"centos": CentOS,
|
||||||
|
// "fedora": Fedora,
|
||||||
|
// "alpine": Alpine,
|
||||||
|
"busybox": Busybox,
|
||||||
|
// "amazn": AmazonLinux,
|
||||||
|
// "oraclelinux": OracleLinux,
|
||||||
|
// "archlinux": ArchLinux,
|
||||||
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/anchore/stereoscope/pkg/image"
|
"github.com/anchore/stereoscope/pkg/image"
|
||||||
)
|
)
|
||||||
|
|
||||||
func IdentifyDistro(img *image.Image) (distro.Distro, error) {
|
func IdentifyDistro(img *image.Image) *distro.Distro {
|
||||||
return distro.Identify(img)
|
return distro.Identify(img)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user