mirror of
https://github.com/anchore/syft.git
synced 2026-02-12 10:36:45 +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:
parent
dfefd2ea4e
commit
706f291679
4
Makefile
4
Makefile
@ -17,8 +17,8 @@ SUCCESS := $(BOLD)$(GREEN)
|
|||||||
# the quality gate lower threshold for unit test total % coverage (by function statements)
|
# the quality gate lower threshold for unit test total % coverage (by function statements)
|
||||||
COVERAGE_THRESHOLD := 62
|
COVERAGE_THRESHOLD := 62
|
||||||
# CI cache busting values; change these if you want CI to not use previous stored cache
|
# CI cache busting values; change these if you want CI to not use previous stored cache
|
||||||
INTEGRATION_CACHE_BUSTER="88738d2f"
|
INTEGRATION_CACHE_BUSTER="894d8ca"
|
||||||
CLI_CACHE_BUSTER="9a2c03cf"
|
CLI_CACHE_BUSTER="894d8ca"
|
||||||
BOOTSTRAP_CACHE="c7afb99ad"
|
BOOTSTRAP_CACHE="c7afb99ad"
|
||||||
|
|
||||||
## Build variables
|
## Build variables
|
||||||
|
|||||||
@ -51,7 +51,7 @@ func generateCatalogPackagesTask() (task, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
results.PackageCatalog = packageCatalog
|
results.PackageCatalog = packageCatalog
|
||||||
results.Distro = theDistro
|
results.LinuxDistribution = theDistro
|
||||||
|
|
||||||
return relationships, nil
|
return relationships, nil
|
||||||
}
|
}
|
||||||
|
|||||||
2
go.mod
2
go.mod
@ -5,6 +5,7 @@ go 1.16
|
|||||||
require (
|
require (
|
||||||
github.com/CycloneDX/cyclonedx-go v0.4.0
|
github.com/CycloneDX/cyclonedx-go v0.4.0
|
||||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||||
|
github.com/acobaugh/osrelease v0.1.0
|
||||||
github.com/adrg/xdg v0.2.1
|
github.com/adrg/xdg v0.2.1
|
||||||
github.com/alecthomas/jsonschema v0.0.0-20210301060011-54c507b6f074
|
github.com/alecthomas/jsonschema v0.0.0-20210301060011-54c507b6f074
|
||||||
github.com/anchore/client-go v0.0.0-20210222170800-9c70f9b80bcf
|
github.com/anchore/client-go v0.0.0-20210222170800-9c70f9b80bcf
|
||||||
@ -26,7 +27,6 @@ require (
|
|||||||
github.com/google/uuid v1.2.0
|
github.com/google/uuid v1.2.0
|
||||||
github.com/gookit/color v1.2.7
|
github.com/gookit/color v1.2.7
|
||||||
github.com/hashicorp/go-multierror v1.1.0
|
github.com/hashicorp/go-multierror v1.1.0
|
||||||
github.com/hashicorp/go-version v1.2.0
|
|
||||||
github.com/jinzhu/copier v0.3.2
|
github.com/jinzhu/copier v0.3.2
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||||
github.com/mholt/archiver/v3 v3.5.1
|
github.com/mholt/archiver/v3 v3.5.1
|
||||||
|
|||||||
4
go.sum
4
go.sum
@ -90,6 +90,8 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdko
|
|||||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
|
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
|
||||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
|
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
|
||||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
|
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
|
||||||
|
github.com/acobaugh/osrelease v0.1.0 h1:Yb59HQDGGNhCj4suHaFQQfBps5wyoKLSSX/J/+UifRE=
|
||||||
|
github.com/acobaugh/osrelease v0.1.0/go.mod h1:4bFEs0MtgHNHBrmHCt67gNisnabCRAlzdVasCEGHTWY=
|
||||||
github.com/adrg/xdg v0.2.1 h1:VSVdnH7cQ7V+B33qSJHTCRlNgra1607Q8PzEmnvb2Ic=
|
github.com/adrg/xdg v0.2.1 h1:VSVdnH7cQ7V+B33qSJHTCRlNgra1607Q8PzEmnvb2Ic=
|
||||||
github.com/adrg/xdg v0.2.1/go.mod h1:ZuOshBmzV4Ta+s23hdfFZnBsdzmoR3US0d7ErpqSbTQ=
|
github.com/adrg/xdg v0.2.1/go.mod h1:ZuOshBmzV4Ta+s23hdfFZnBsdzmoR3US0d7ErpqSbTQ=
|
||||||
github.com/alecthomas/jsonschema v0.0.0-20210301060011-54c507b6f074 h1:Lw9q+WyJLFOR+AULchS5/2GKfM+6gOh4szzizdfH3MU=
|
github.com/alecthomas/jsonschema v0.0.0-20210301060011-54c507b6f074 h1:Lw9q+WyJLFOR+AULchS5/2GKfM+6gOh4szzizdfH3MU=
|
||||||
@ -501,8 +503,6 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX
|
|||||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
|
|
||||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
|
||||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
|||||||
@ -8,31 +8,82 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/sbom"
|
|
||||||
|
|
||||||
"github.com/anchore/syft/internal/formats/syftjson"
|
|
||||||
|
|
||||||
"github.com/wagoodman/go-progress"
|
|
||||||
|
|
||||||
"github.com/anchore/client-go/pkg/external"
|
"github.com/anchore/client-go/pkg/external"
|
||||||
|
"github.com/anchore/syft/internal/formats/syftjson"
|
||||||
|
syftjsonModel "github.com/anchore/syft/internal/formats/syftjson/model"
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/syft/sbom"
|
||||||
|
"github.com/wagoodman/go-progress"
|
||||||
)
|
)
|
||||||
|
|
||||||
type packageSBOMImportAPI interface {
|
type packageSBOMImportAPI interface {
|
||||||
ImportImagePackages(context.Context, string, external.ImagePackageManifest) (external.ImageImportContentResponse, *http.Response, error)
|
ImportImagePackages(context.Context, string, external.ImagePackageManifest) (external.ImageImportContentResponse, *http.Response, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// importSBOM mirrors all elements found on the syftjson model format object relative to the anchore engine import schema.
|
||||||
|
type importSBOM struct {
|
||||||
|
Artifacts []syftjsonModel.Package `json:"artifacts"` // Artifacts is the list of packages discovered and placed into the catalog
|
||||||
|
ArtifactRelationships []syftjsonModel.Relationship `json:"artifactRelationships"`
|
||||||
|
Files []syftjsonModel.File `json:"files,omitempty"` // note: must have omitempty
|
||||||
|
Secrets []syftjsonModel.Secrets `json:"secrets,omitempty"` // note: must have omitempty
|
||||||
|
Source syftjsonModel.Source `json:"source"` // Source represents the original object that was cataloged
|
||||||
|
Distro external.ImportDistribution `json:"distro"` // Distro represents the Linux distribution that was detected from the source
|
||||||
|
Descriptor syftjsonModel.Descriptor `json:"descriptor"` // Descriptor is a block containing self-describing information about syft
|
||||||
|
Schema syftjsonModel.Schema `json:"schema"` // Schema is a block reserved for defining the version for the shape of this JSON document and where to find the schema document to validate the shape
|
||||||
|
}
|
||||||
|
|
||||||
|
// toImportSBOMModel transforms the current sbom shape into what is needed for the current anchore import api shape.
|
||||||
|
func toImportSBOMModel(s sbom.SBOM) importSBOM {
|
||||||
|
m := syftjson.ToFormatModel(s)
|
||||||
|
|
||||||
|
var idLike string
|
||||||
|
if len(m.Distro.IDLike) > 0 {
|
||||||
|
idLike = m.Distro.IDLike[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
var version = m.Distro.VersionID // note: version is intentionally not used as the default
|
||||||
|
if version == "" {
|
||||||
|
version = m.Distro.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = m.Distro.ID // note: name is intentionally not used as the default
|
||||||
|
if name == "" {
|
||||||
|
name = m.Distro.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
return importSBOM{
|
||||||
|
Artifacts: m.Artifacts,
|
||||||
|
ArtifactRelationships: m.ArtifactRelationships,
|
||||||
|
Files: m.Files,
|
||||||
|
Secrets: m.Secrets,
|
||||||
|
Source: m.Source,
|
||||||
|
Distro: external.ImportDistribution{
|
||||||
|
Name: name,
|
||||||
|
Version: version,
|
||||||
|
IdLike: idLike,
|
||||||
|
},
|
||||||
|
Descriptor: m.Descriptor,
|
||||||
|
Schema: m.Schema,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func packageSbomModel(s sbom.SBOM) (*external.ImagePackageManifest, error) {
|
func packageSbomModel(s sbom.SBOM) (*external.ImagePackageManifest, error) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
||||||
err := syftjson.Format().Encode(&buf, s)
|
doc := toImportSBOMModel(s)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to serialize results: %w", err)
|
enc := json.NewEncoder(&buf)
|
||||||
|
// prevent > and < from being escaped in the payload
|
||||||
|
enc.SetEscapeHTML(false)
|
||||||
|
enc.SetIndent("", " ")
|
||||||
|
|
||||||
|
if err := enc.Encode(&doc); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to encode import JSON model: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// the model is 1:1 the JSON output of today. As the schema changes, this will need to be converted into individual mappings.
|
// the model is 1:1 the JSON output of today. As the schema changes, this will need to be converted into individual mappings.
|
||||||
var model external.ImagePackageManifest
|
var model external.ImagePackageManifest
|
||||||
if err = json.Unmarshal(buf.Bytes(), &model); err != nil {
|
if err := json.Unmarshal(buf.Bytes(), &model); err != nil {
|
||||||
return nil, fmt.Errorf("unable to convert JSON output to import model: %w", err)
|
return nil, fmt.Errorf("unable to convert JSON output to import model: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package anchore
|
package anchore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -9,16 +8,17 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/sbom"
|
|
||||||
|
|
||||||
"github.com/anchore/client-go/pkg/external"
|
"github.com/anchore/client-go/pkg/external"
|
||||||
"github.com/anchore/syft/internal/formats/syftjson"
|
"github.com/anchore/syft/internal/formats/syftjson"
|
||||||
syftjsonModel "github.com/anchore/syft/internal/formats/syftjson/model"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/distro"
|
"github.com/anchore/syft/syft/linux"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/sbom"
|
||||||
"github.com/anchore/syft/syft/source"
|
"github.com/anchore/syft/syft/source"
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
"github.com/docker/docker/pkg/ioutils"
|
||||||
"github.com/go-test/deep"
|
"github.com/go-test/deep"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/wagoodman/go-progress"
|
"github.com/wagoodman/go-progress"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,107 +29,6 @@ func must(c pkg.CPE, e error) pkg.CPE {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// this test is tailored towards the assumption that the import doc shape and the syft json shape are the same.
|
|
||||||
// TODO: replace this as the document shapes diverge.
|
|
||||||
func TestPackageSbomToModel(t *testing.T) {
|
|
||||||
|
|
||||||
m := source.Metadata{
|
|
||||||
Scheme: source.ImageScheme,
|
|
||||||
ImageMetadata: source.ImageMetadata{
|
|
||||||
UserInput: "user-in",
|
|
||||||
Layers: []source.LayerMetadata{
|
|
||||||
{
|
|
||||||
MediaType: "layer-metadata-type!",
|
|
||||||
Digest: "layer-digest",
|
|
||||||
Size: 20,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Size: 10,
|
|
||||||
ManifestDigest: "sha256:digest!",
|
|
||||||
MediaType: "mediatype!",
|
|
||||||
Tags: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
d, _ := distro.NewDistro(distro.CentOS, "8.0", "")
|
|
||||||
|
|
||||||
p := pkg.Package{
|
|
||||||
Name: "name",
|
|
||||||
Version: "version",
|
|
||||||
FoundBy: "foundBy",
|
|
||||||
Locations: []source.Location{
|
|
||||||
{
|
|
||||||
Coordinates: source.Coordinates{
|
|
||||||
RealPath: "path",
|
|
||||||
FileSystemID: "layerID",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Licenses: []string{"license"},
|
|
||||||
Language: pkg.Python,
|
|
||||||
Type: pkg.PythonPkg,
|
|
||||||
CPEs: []pkg.CPE{
|
|
||||||
must(pkg.NewCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*")),
|
|
||||||
},
|
|
||||||
PURL: "purl",
|
|
||||||
}
|
|
||||||
|
|
||||||
c := pkg.NewCatalog(p)
|
|
||||||
|
|
||||||
sbomResult := sbom.SBOM{
|
|
||||||
Artifacts: sbom.Artifacts{
|
|
||||||
PackageCatalog: c,
|
|
||||||
Distro: &d,
|
|
||||||
},
|
|
||||||
Source: m,
|
|
||||||
}
|
|
||||||
|
|
||||||
model, err := packageSbomModel(sbomResult)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to generate model from source material: %+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var modelJSON []byte
|
|
||||||
|
|
||||||
modelJSON, err = json.Marshal(&model)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to marshal model: %+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s := sbom.SBOM{
|
|
||||||
Artifacts: sbom.Artifacts{
|
|
||||||
PackageCatalog: c,
|
|
||||||
Distro: &d,
|
|
||||||
},
|
|
||||||
Source: m,
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if err := syftjson.Format().Encode(&buf, s); err != nil {
|
|
||||||
t.Fatalf("unable to get expected json: %+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// unmarshal expected result
|
|
||||||
var expectedDoc syftjsonModel.Document
|
|
||||||
if err := json.Unmarshal(buf.Bytes(), &expectedDoc); err != nil {
|
|
||||||
t.Fatalf("unable to parse json doc: %+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// unmarshal actual result
|
|
||||||
var actualDoc syftjsonModel.Document
|
|
||||||
if err := json.Unmarshal(modelJSON, &actualDoc); err != nil {
|
|
||||||
t.Fatalf("unable to parse json doc: %+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, d := range deep.Equal(actualDoc, expectedDoc) {
|
|
||||||
if strings.HasSuffix(d, "<nil slice> != []") {
|
|
||||||
// do not consider nil vs empty collection semantics as a "difference"
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
t.Errorf("diff: %+v", d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockPackageSBOMImportAPI struct {
|
type mockPackageSBOMImportAPI struct {
|
||||||
sessionID string
|
sessionID string
|
||||||
model external.ImagePackageManifest
|
model external.ImagePackageManifest
|
||||||
@ -150,72 +49,81 @@ func (m *mockPackageSBOMImportAPI) ImportImagePackages(ctx context.Context, sess
|
|||||||
return external.ImageImportContentResponse{Digest: m.responseDigest}, m.httpResponse, m.err
|
return external.ImageImportContentResponse{Digest: m.responseDigest}, m.httpResponse, m.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPackageSbomImport(t *testing.T) {
|
func sbomFixture() sbom.SBOM {
|
||||||
|
return sbom.SBOM{
|
||||||
catalog := pkg.NewCatalog(pkg.Package{
|
|
||||||
Name: "name",
|
|
||||||
Version: "version",
|
|
||||||
FoundBy: "foundBy",
|
|
||||||
Locations: []source.Location{
|
|
||||||
{
|
|
||||||
Coordinates: source.Coordinates{
|
|
||||||
RealPath: "path",
|
|
||||||
FileSystemID: "layerID",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Licenses: []string{"license"},
|
|
||||||
Language: pkg.Python,
|
|
||||||
Type: pkg.PythonPkg,
|
|
||||||
CPEs: []pkg.CPE{
|
|
||||||
must(pkg.NewCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*")),
|
|
||||||
},
|
|
||||||
PURL: "purl",
|
|
||||||
MetadataType: pkg.PythonPackageMetadataType,
|
|
||||||
Metadata: pkg.PythonPackageMetadata{
|
|
||||||
Name: "p-name",
|
|
||||||
Version: "p-version",
|
|
||||||
License: "p-license",
|
|
||||||
Author: "p-author",
|
|
||||||
AuthorEmail: "p-email",
|
|
||||||
Platform: "p-platform",
|
|
||||||
Files: []pkg.PythonFileRecord{
|
|
||||||
{
|
|
||||||
Path: "p-path",
|
|
||||||
Digest: &pkg.PythonFileDigest{
|
|
||||||
Algorithm: "p-alg",
|
|
||||||
Value: "p-digest",
|
|
||||||
},
|
|
||||||
Size: "p-size",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
SitePackagesRootPath: "p-site-packages-root",
|
|
||||||
TopLevelPackages: []string{"top-level"},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
m := source.Metadata{
|
|
||||||
Scheme: source.ImageScheme,
|
|
||||||
ImageMetadata: source.ImageMetadata{
|
|
||||||
UserInput: "user-in",
|
|
||||||
Layers: nil,
|
|
||||||
Size: 10,
|
|
||||||
ManifestDigest: "sha256:digest!",
|
|
||||||
MediaType: "mediatype!",
|
|
||||||
Tags: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
d, _ := distro.NewDistro(distro.CentOS, "8.0", "")
|
|
||||||
|
|
||||||
sbomResult := sbom.SBOM{
|
|
||||||
Artifacts: sbom.Artifacts{
|
Artifacts: sbom.Artifacts{
|
||||||
PackageCatalog: catalog,
|
PackageCatalog: pkg.NewCatalog(pkg.Package{
|
||||||
Distro: &d,
|
Name: "name",
|
||||||
|
Version: "version",
|
||||||
|
FoundBy: "foundBy",
|
||||||
|
Locations: []source.Location{
|
||||||
|
{
|
||||||
|
Coordinates: source.Coordinates{
|
||||||
|
RealPath: "path",
|
||||||
|
FileSystemID: "layerID",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Licenses: []string{"license"},
|
||||||
|
Language: pkg.Python,
|
||||||
|
Type: pkg.PythonPkg,
|
||||||
|
CPEs: []pkg.CPE{
|
||||||
|
must(pkg.NewCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*")),
|
||||||
|
},
|
||||||
|
PURL: "purl",
|
||||||
|
MetadataType: pkg.PythonPackageMetadataType,
|
||||||
|
Metadata: pkg.PythonPackageMetadata{
|
||||||
|
Name: "p-name",
|
||||||
|
Version: "p-version",
|
||||||
|
License: "p-license",
|
||||||
|
Author: "p-author",
|
||||||
|
AuthorEmail: "p-email",
|
||||||
|
Platform: "p-platform",
|
||||||
|
Files: []pkg.PythonFileRecord{
|
||||||
|
{
|
||||||
|
Path: "p-path",
|
||||||
|
Digest: &pkg.PythonFileDigest{
|
||||||
|
Algorithm: "p-alg",
|
||||||
|
Value: "p-digest",
|
||||||
|
},
|
||||||
|
Size: "p-size",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SitePackagesRootPath: "p-site-packages-root",
|
||||||
|
TopLevelPackages: []string{"top-level"},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
LinuxDistribution: &linux.Release{
|
||||||
|
ID: "centos",
|
||||||
|
Version: "8.0",
|
||||||
|
VersionID: "8.0",
|
||||||
|
IDLike: []string{"rhel"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Relationships: []artifact.Relationship{
|
||||||
|
{
|
||||||
|
From: source.NewLocation("/place1"),
|
||||||
|
To: source.NewLocation("/place2"),
|
||||||
|
Type: artifact.ContainsRelationship,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Source: source.Metadata{
|
||||||
|
Scheme: source.ImageScheme,
|
||||||
|
ImageMetadata: source.ImageMetadata{
|
||||||
|
UserInput: "user-in",
|
||||||
|
Layers: nil,
|
||||||
|
Size: 10,
|
||||||
|
ManifestDigest: "sha256:digest!",
|
||||||
|
MediaType: "mediatype!",
|
||||||
|
Tags: nil,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Source: m,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPackageSbomImport(t *testing.T) {
|
||||||
|
sbomResult := sbomFixture()
|
||||||
theModel, err := packageSbomModel(sbomResult)
|
theModel, err := packageSbomModel(sbomResult)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("could not get sbom model: %+v", err)
|
t.Fatalf("could not get sbom model: %+v", err)
|
||||||
@ -280,3 +188,210 @@ func TestPackageSbomImport(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type modelAssertion func(t *testing.T, model *external.ImagePackageManifest)
|
||||||
|
|
||||||
|
func Test_packageSbomModel(t *testing.T) {
|
||||||
|
fix := sbomFixture()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
sbom sbom.SBOM
|
||||||
|
traits []modelAssertion
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "distro: has single distro id-like",
|
||||||
|
sbom: sbom.SBOM{
|
||||||
|
Artifacts: sbom.Artifacts{
|
||||||
|
LinuxDistribution: &linux.Release{
|
||||||
|
Name: "centos-name",
|
||||||
|
ID: "centos-id",
|
||||||
|
IDLike: []string{
|
||||||
|
"centos-id-like-1",
|
||||||
|
},
|
||||||
|
Version: "version",
|
||||||
|
VersionID: "version-id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
traits: []modelAssertion{
|
||||||
|
hasDistroInfo("centos-id", "version-id", "centos-id-like-1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "distro: has multiple distro id-like",
|
||||||
|
sbom: sbom.SBOM{
|
||||||
|
Artifacts: sbom.Artifacts{
|
||||||
|
LinuxDistribution: &linux.Release{
|
||||||
|
Name: "centos-name",
|
||||||
|
ID: "centos-id",
|
||||||
|
IDLike: []string{
|
||||||
|
"centos-id-like-1",
|
||||||
|
"centos-id-like-2",
|
||||||
|
},
|
||||||
|
Version: "version",
|
||||||
|
VersionID: "version-id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
traits: []modelAssertion{
|
||||||
|
hasDistroInfo("centos-id", "version-id", "centos-id-like-1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "distro: has no distro id-like",
|
||||||
|
sbom: sbom.SBOM{
|
||||||
|
Artifacts: sbom.Artifacts{
|
||||||
|
LinuxDistribution: &linux.Release{
|
||||||
|
Name: "centos-name",
|
||||||
|
ID: "centos-id",
|
||||||
|
IDLike: []string{},
|
||||||
|
Version: "version",
|
||||||
|
VersionID: "version-id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
traits: []modelAssertion{
|
||||||
|
hasDistroInfo("centos-id", "version-id", ""),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "distro: has no version-id",
|
||||||
|
sbom: sbom.SBOM{
|
||||||
|
Artifacts: sbom.Artifacts{
|
||||||
|
LinuxDistribution: &linux.Release{
|
||||||
|
Name: "centos-name",
|
||||||
|
ID: "centos-id",
|
||||||
|
IDLike: []string{},
|
||||||
|
Version: "version",
|
||||||
|
VersionID: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
traits: []modelAssertion{
|
||||||
|
hasDistroInfo("centos-id", "version", ""),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "distro: has no id",
|
||||||
|
sbom: sbom.SBOM{
|
||||||
|
Artifacts: sbom.Artifacts{
|
||||||
|
LinuxDistribution: &linux.Release{
|
||||||
|
Name: "centos-name",
|
||||||
|
ID: "",
|
||||||
|
IDLike: []string{},
|
||||||
|
Version: "version",
|
||||||
|
VersionID: "version-id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
traits: []modelAssertion{
|
||||||
|
hasDistroInfo("centos-name", "version-id", ""),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should have expected packages",
|
||||||
|
sbom: fix,
|
||||||
|
traits: []modelAssertion{
|
||||||
|
func(t *testing.T, model *external.ImagePackageManifest) {
|
||||||
|
require.Len(t, model.Artifacts, 1)
|
||||||
|
|
||||||
|
modelPkg := model.Artifacts
|
||||||
|
modelBytes, err := json.Marshal(&modelPkg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
fixPkg := syftjson.ToFormatModel(fix).Artifacts
|
||||||
|
fixBytes, err := json.Marshal(&fixPkg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.JSONEq(t, string(fixBytes), string(modelBytes))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should have expected relationships",
|
||||||
|
sbom: fix,
|
||||||
|
traits: []modelAssertion{
|
||||||
|
func(t *testing.T, model *external.ImagePackageManifest) {
|
||||||
|
modelPkg := model.ArtifactRelationships
|
||||||
|
modelBytes, err := json.Marshal(&modelPkg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
fixPkg := syftjson.ToFormatModel(fix).ArtifactRelationships
|
||||||
|
fixBytes, err := json.Marshal(&fixPkg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.JSONEq(t, string(fixBytes), string(modelBytes))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should have expected schema",
|
||||||
|
sbom: fix,
|
||||||
|
traits: []modelAssertion{
|
||||||
|
func(t *testing.T, model *external.ImagePackageManifest) {
|
||||||
|
modelPkg := model.Schema
|
||||||
|
modelBytes, err := json.Marshal(&modelPkg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
fixPkg := syftjson.ToFormatModel(fix).Schema
|
||||||
|
fixBytes, err := json.Marshal(&fixPkg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.JSONEq(t, string(fixBytes), string(modelBytes))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should have expected descriptor",
|
||||||
|
sbom: fix,
|
||||||
|
traits: []modelAssertion{
|
||||||
|
func(t *testing.T, model *external.ImagePackageManifest) {
|
||||||
|
modelPkg := model.Descriptor
|
||||||
|
modelBytes, err := json.Marshal(&modelPkg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
fixPkg := syftjson.ToFormatModel(fix).Descriptor
|
||||||
|
fixBytes, err := json.Marshal(&fixPkg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.JSONEq(t, string(fixBytes), string(modelBytes))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should have expected source",
|
||||||
|
sbom: fix,
|
||||||
|
traits: []modelAssertion{
|
||||||
|
func(t *testing.T, model *external.ImagePackageManifest) {
|
||||||
|
modelPkg := model.Source
|
||||||
|
modelBytes, err := json.Marshal(&modelPkg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
fixPkg := syftjson.ToFormatModel(fix).Source
|
||||||
|
fixBytes, err := json.Marshal(&fixPkg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.JSONEq(t, string(fixBytes), string(modelBytes))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := packageSbomModel(tt.sbom)
|
||||||
|
require.NoError(t, err)
|
||||||
|
for _, fn := range tt.traits {
|
||||||
|
fn(t, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasDistroInfo(name, version, idLike string) modelAssertion {
|
||||||
|
return func(t *testing.T, model *external.ImagePackageManifest) {
|
||||||
|
assert.Equal(t, name, model.Distro.Name)
|
||||||
|
assert.Equal(t, version, model.Distro.Version)
|
||||||
|
assert.Equal(t, idLike, model.Distro.IdLike)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -6,5 +6,5 @@ const (
|
|||||||
|
|
||||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||||
JSONSchemaVersion = "2.0.2"
|
JSONSchemaVersion = "3.0.0"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -9,8 +9,8 @@ import (
|
|||||||
"github.com/anchore/stereoscope/pkg/filetree"
|
"github.com/anchore/stereoscope/pkg/filetree"
|
||||||
"github.com/anchore/stereoscope/pkg/image"
|
"github.com/anchore/stereoscope/pkg/image"
|
||||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||||
"github.com/anchore/syft/syft/distro"
|
|
||||||
"github.com/anchore/syft/syft/format"
|
"github.com/anchore/syft/syft/format"
|
||||||
|
"github.com/anchore/syft/syft/linux"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
"github.com/anchore/syft/syft/sbom"
|
"github.com/anchore/syft/syft/sbom"
|
||||||
"github.com/anchore/syft/syft/source"
|
"github.com/anchore/syft/syft/source"
|
||||||
@ -120,13 +120,17 @@ func ImageInput(t testing.TB, testImage string, options ...ImageOption) sbom.SBO
|
|||||||
src, err := source.NewFromImage(img, "user-image-input")
|
src, err := source.NewFromImage(img, "user-image-input")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
dist, err := distro.NewDistro(distro.Debian, "1.2.3", "like!")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
return sbom.SBOM{
|
return sbom.SBOM{
|
||||||
Artifacts: sbom.Artifacts{
|
Artifacts: sbom.Artifacts{
|
||||||
PackageCatalog: catalog,
|
PackageCatalog: catalog,
|
||||||
Distro: &dist,
|
LinuxDistribution: &linux.Release{
|
||||||
|
PrettyName: "debian",
|
||||||
|
Name: "debian",
|
||||||
|
ID: "debian",
|
||||||
|
IDLike: []string{"like!"},
|
||||||
|
Version: "1.2.3",
|
||||||
|
VersionID: "1.2.3",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Source: src.Metadata,
|
Source: src.Metadata,
|
||||||
Descriptor: sbom.Descriptor{
|
Descriptor: sbom.Descriptor{
|
||||||
@ -194,16 +198,20 @@ func populateImageCatalog(catalog *pkg.Catalog, img *image.Image) {
|
|||||||
func DirectoryInput(t testing.TB) sbom.SBOM {
|
func DirectoryInput(t testing.TB) sbom.SBOM {
|
||||||
catalog := newDirectoryCatalog()
|
catalog := newDirectoryCatalog()
|
||||||
|
|
||||||
dist, err := distro.NewDistro(distro.Debian, "1.2.3", "like!")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
src, err := source.NewFromDirectory("/some/path")
|
src, err := source.NewFromDirectory("/some/path")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
return sbom.SBOM{
|
return sbom.SBOM{
|
||||||
Artifacts: sbom.Artifacts{
|
Artifacts: sbom.Artifacts{
|
||||||
PackageCatalog: catalog,
|
PackageCatalog: catalog,
|
||||||
Distro: &dist,
|
LinuxDistribution: &linux.Release{
|
||||||
|
PrettyName: "debian",
|
||||||
|
Name: "debian",
|
||||||
|
ID: "debian",
|
||||||
|
IDLike: []string{"like!"},
|
||||||
|
Version: "1.2.3",
|
||||||
|
VersionID: "1.2.3",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Source: src.Metadata,
|
Source: src.Metadata,
|
||||||
Descriptor: sbom.Descriptor{
|
Descriptor: sbom.Descriptor{
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func encoder(output io.Writer, s sbom.SBOM) error {
|
func encoder(output io.Writer, s sbom.SBOM) error {
|
||||||
doc := toFormatModel(s)
|
doc := ToFormatModel(s)
|
||||||
|
|
||||||
enc := json.NewEncoder(output)
|
enc := json.NewEncoder(output)
|
||||||
// prevent > and < from being escaped in the payload
|
// prevent > and < from being escaped in the payload
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/distro"
|
"github.com/anchore/syft/syft/linux"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
"github.com/anchore/syft/syft/sbom"
|
"github.com/anchore/syft/syft/sbom"
|
||||||
"github.com/anchore/syft/syft/source"
|
"github.com/anchore/syft/syft/source"
|
||||||
@ -139,10 +139,13 @@ func TestEncodeFullJSONDocument(t *testing.T) {
|
|||||||
FileContents: map[source.Coordinates]string{
|
FileContents: map[source.Coordinates]string{
|
||||||
source.NewLocation("/a/place/a").Coordinates: "the-contents",
|
source.NewLocation("/a/place/a").Coordinates: "the-contents",
|
||||||
},
|
},
|
||||||
Distro: &distro.Distro{
|
LinuxDistribution: &linux.Release{
|
||||||
Type: distro.RedHat,
|
ID: "redhat",
|
||||||
RawVersion: "7",
|
Version: "7",
|
||||||
IDLike: "rhel",
|
VersionID: "7",
|
||||||
|
IDLike: []string{
|
||||||
|
"rhel",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Relationships: []artifact.Relationship{
|
Relationships: []artifact.Relationship{
|
||||||
|
|||||||
@ -1,8 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
// Distro provides information about a detected Linux Distro.
|
|
||||||
type Distro struct {
|
|
||||||
Name string `json:"name"` // Name of the Linux distribution
|
|
||||||
Version string `json:"version"` // Version of the Linux distribution (major or major.minor version)
|
|
||||||
IDLike string `json:"idLike"` // the ID_LIKE field found within the /etc/os-release file
|
|
||||||
}
|
|
||||||
@ -7,7 +7,7 @@ type Document struct {
|
|||||||
Files []File `json:"files,omitempty"` // note: must have omitempty
|
Files []File `json:"files,omitempty"` // note: must have omitempty
|
||||||
Secrets []Secrets `json:"secrets,omitempty"` // note: must have omitempty
|
Secrets []Secrets `json:"secrets,omitempty"` // note: must have omitempty
|
||||||
Source Source `json:"source"` // Source represents the original object that was cataloged
|
Source Source `json:"source"` // Source represents the original object that was cataloged
|
||||||
Distro Distro `json:"distro"` // Distro represents the Linux distribution that was detected from the source
|
Distro LinuxRelease `json:"distro"` // Distro represents the Linux distribution that was detected from the source
|
||||||
Descriptor Descriptor `json:"descriptor"` // Descriptor is a block containing self-describing information about syft
|
Descriptor Descriptor `json:"descriptor"` // Descriptor is a block containing self-describing information about syft
|
||||||
Schema Schema `json:"schema"` // Schema is a block reserved for defining the version for the shape of this JSON document and where to find the schema document to validate the shape
|
Schema Schema `json:"schema"` // Schema is a block reserved for defining the version for the shape of this JSON document and where to find the schema document to validate the shape
|
||||||
}
|
}
|
||||||
|
|||||||
38
internal/formats/syftjson/model/linux_release.go
Normal file
38
internal/formats/syftjson/model/linux_release.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IDLikes []string
|
||||||
|
|
||||||
|
type LinuxRelease struct {
|
||||||
|
PrettyName string `json:"prettyName,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
IDLike IDLikes `json:"idLike,omitempty"`
|
||||||
|
Version string `json:"version,omitempty"`
|
||||||
|
VersionID string `json:"versionID,omitempty"`
|
||||||
|
Variant string `json:"variant,omitempty"`
|
||||||
|
VariantID string `json:"variantID,omitempty"`
|
||||||
|
HomeURL string `json:"homeURL,omitempty"`
|
||||||
|
SupportURL string `json:"supportURL,omitempty"`
|
||||||
|
BugReportURL string `json:"bugReportURL,omitempty"`
|
||||||
|
PrivacyPolicyURL string `json:"privacyPolicyURL,omitempty"`
|
||||||
|
CPEName string `json:"cpeName,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IDLikes) UnmarshalJSON(data []byte) error {
|
||||||
|
var str string
|
||||||
|
var strSlice []string
|
||||||
|
|
||||||
|
// we support unmarshalling from a single value to support syft json schema v2
|
||||||
|
if err := json.Unmarshal(data, &str); err == nil {
|
||||||
|
*s = []string{str}
|
||||||
|
} else if err := json.Unmarshal(data, &strSlice); err == nil {
|
||||||
|
*s = strSlice
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
47
internal/formats/syftjson/model/linux_release_test.go
Normal file
47
internal/formats/syftjson/model/linux_release_test.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIDLikes_UnmarshalJSON(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
data interface{}
|
||||||
|
expected IDLikes
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "single string",
|
||||||
|
data: "well hello there!",
|
||||||
|
expected: IDLikes{
|
||||||
|
"well hello there!",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple strings",
|
||||||
|
data: []string{
|
||||||
|
"well hello there!",
|
||||||
|
"...hello there, john!",
|
||||||
|
},
|
||||||
|
expected: IDLikes{
|
||||||
|
"well hello there!",
|
||||||
|
"...hello there, john!",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
data, err := json.Marshal(&tt.data)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var obj IDLikes
|
||||||
|
require.NoError(t, json.Unmarshal(data, &obj))
|
||||||
|
|
||||||
|
assert.Equal(t, tt.expected, obj)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -71,9 +71,14 @@
|
|||||||
"target": "/some/path"
|
"target": "/some/path"
|
||||||
},
|
},
|
||||||
"distro": {
|
"distro": {
|
||||||
|
"prettyName": "debian",
|
||||||
"name": "debian",
|
"name": "debian",
|
||||||
|
"id": "debian",
|
||||||
|
"idLike": [
|
||||||
|
"like!"
|
||||||
|
],
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"idLike": "like!"
|
"versionID": "1.2.3"
|
||||||
},
|
},
|
||||||
"descriptor": {
|
"descriptor": {
|
||||||
"name": "syft",
|
"name": "syft",
|
||||||
@ -83,7 +88,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"schema": {
|
"schema": {
|
||||||
"version": "2.0.2",
|
"version": "3.0.0",
|
||||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-2.0.2.json"
|
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.0.0.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -167,9 +167,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"distro": {
|
"distro": {
|
||||||
"name": "redhat",
|
"id": "redhat",
|
||||||
|
"idLike": [
|
||||||
|
"rhel"
|
||||||
|
],
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"idLike": "rhel"
|
"versionID": "7"
|
||||||
},
|
},
|
||||||
"descriptor": {
|
"descriptor": {
|
||||||
"name": "syft",
|
"name": "syft",
|
||||||
@ -179,7 +182,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"schema": {
|
"schema": {
|
||||||
"version": "2.0.2",
|
"version": "3.0.0",
|
||||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-2.0.2.json"
|
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.0.0.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"artifacts": [
|
"artifacts": [
|
||||||
{
|
{
|
||||||
"id": "d16127444133b5c1",
|
"id": "d9527e708c11f8b9",
|
||||||
"name": "package-1",
|
"name": "package-1",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"type": "python",
|
"type": "python",
|
||||||
@ -9,7 +9,7 @@
|
|||||||
"locations": [
|
"locations": [
|
||||||
{
|
{
|
||||||
"path": "/somefile-1.txt",
|
"path": "/somefile-1.txt",
|
||||||
"layerID": "sha256:16e64541f2ddf59a90391ce7bb8af90313f7d373f2105d88f3d3267b72e0ebab"
|
"layerID": "sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"licenses": [
|
"licenses": [
|
||||||
@ -32,7 +32,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "24907357f3705420",
|
"id": "73f796c846875b9e",
|
||||||
"name": "package-2",
|
"name": "package-2",
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"type": "deb",
|
"type": "deb",
|
||||||
@ -40,7 +40,7 @@
|
|||||||
"locations": [
|
"locations": [
|
||||||
{
|
{
|
||||||
"path": "/somefile-2.txt",
|
"path": "/somefile-2.txt",
|
||||||
"layerID": "sha256:de6c235f76ea24c8503ec08891445b5d6a8bdf8249117ed8d8b0b6fb3ebe4f67"
|
"layerID": "sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"licenses": [],
|
"licenses": [],
|
||||||
@ -67,7 +67,7 @@
|
|||||||
"type": "image",
|
"type": "image",
|
||||||
"target": {
|
"target": {
|
||||||
"userInput": "user-image-input",
|
"userInput": "user-image-input",
|
||||||
"imageID": "sha256:9624b89704d23fa5f61b427379d172dac91dc7a508c4d7dea7aed0e04a4cf39e",
|
"imageID": "sha256:2480160b55bec40c44d3b145c7b2c1c47160db8575c3dcae086d76b9370ae7ca",
|
||||||
"manifestDigest": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
|
"manifestDigest": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
|
||||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||||
"tags": [
|
"tags": [
|
||||||
@ -77,24 +77,29 @@
|
|||||||
"layers": [
|
"layers": [
|
||||||
{
|
{
|
||||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||||
"digest": "sha256:16e64541f2ddf59a90391ce7bb8af90313f7d373f2105d88f3d3267b72e0ebab",
|
"digest": "sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59",
|
||||||
"size": 22
|
"size": 22
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||||
"digest": "sha256:de6c235f76ea24c8503ec08891445b5d6a8bdf8249117ed8d8b0b6fb3ebe4f67",
|
"digest": "sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec",
|
||||||
"size": 16
|
"size": 16
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NjcsImRpZ2VzdCI6InNoYTI1Njo5NjI0Yjg5NzA0ZDIzZmE1ZjYxYjQyNzM3OWQxNzJkYWM5MWRjN2E1MDhjNGQ3ZGVhN2FlZDBlMDRhNGNmMzllIn0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1NjoxNmU2NDU0MWYyZGRmNTlhOTAzOTFjZTdiYjhhZjkwMzEzZjdkMzczZjIxMDVkODhmM2QzMjY3YjcyZTBlYmFiIn0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2OmRlNmMyMzVmNzZlYTI0Yzg1MDNlYzA4ODkxNDQ1YjVkNmE4YmRmODI0OTExN2VkOGQ4YjBiNmZiM2ViZTRmNjcifV19",
|
"manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NjcsImRpZ2VzdCI6InNoYTI1NjoyNDgwMTYwYjU1YmVjNDBjNDRkM2IxNDVjN2IyYzFjNDcxNjBkYjg1NzVjM2RjYWUwODZkNzZiOTM3MGFlN2NhIn0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1NjpmYjZiZWVjYjc1YjM5ZjRiYjgxM2RiZjE3N2U1MDFlZGQ1ZGRiM2U2OWJiNDVjZWRlYjc4YzY3NmVlMWI3YTU5In0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2OjMxOWI1ODhjZTY0MjUzYTg3YjUzM2M4ZWQwMWNmMDAyNWUwZWFjOThlN2I1MTZlMTI1MzI5NTdlMTI0NGZkZWMifV19",
|
||||||
"config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjEtMTItMDFUMTI6MTM6NDMuNDAxMzkxNFoiLCJoaXN0b3J5IjpbeyJjcmVhdGVkIjoiMjAyMS0xMi0wMVQxMjoxMzo0My4zNDM2MzQyWiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0xLnR4dCAvc29tZWZpbGUtMS50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn0seyJjcmVhdGVkIjoiMjAyMS0xMi0wMVQxMjoxMzo0My40MDEzOTE0WiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0yLnR4dCAvc29tZWZpbGUtMi50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn1dLCJvcyI6ImxpbnV4Iiwicm9vdGZzIjp7InR5cGUiOiJsYXllcnMiLCJkaWZmX2lkcyI6WyJzaGEyNTY6MTZlNjQ1NDFmMmRkZjU5YTkwMzkxY2U3YmI4YWY5MDMxM2Y3ZDM3M2YyMTA1ZDg4ZjNkMzI2N2I3MmUwZWJhYiIsInNoYTI1NjpkZTZjMjM1Zjc2ZWEyNGM4NTAzZWMwODg5MTQ0NWI1ZDZhOGJkZjgyNDkxMTdlZDhkOGIwYjZmYjNlYmU0ZjY3Il19fQ==",
|
"config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjEtMTAtMDRUMTE6NDA6MDAuNjM4Mzk0NVoiLCJoaXN0b3J5IjpbeyJjcmVhdGVkIjoiMjAyMS0xMC0wNFQxMTo0MDowMC41OTA3MzE2WiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0xLnR4dCAvc29tZWZpbGUtMS50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn0seyJjcmVhdGVkIjoiMjAyMS0xMC0wNFQxMTo0MDowMC42MzgzOTQ1WiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0yLnR4dCAvc29tZWZpbGUtMi50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn1dLCJvcyI6ImxpbnV4Iiwicm9vdGZzIjp7InR5cGUiOiJsYXllcnMiLCJkaWZmX2lkcyI6WyJzaGEyNTY6ZmI2YmVlY2I3NWIzOWY0YmI4MTNkYmYxNzdlNTAxZWRkNWRkYjNlNjliYjQ1Y2VkZWI3OGM2NzZlZTFiN2E1OSIsInNoYTI1NjozMTliNTg4Y2U2NDI1M2E4N2I1MzNjOGVkMDFjZjAwMjVlMGVhYzk4ZTdiNTE2ZTEyNTMyOTU3ZTEyNDRmZGVjIl19fQ==",
|
||||||
"repoDigests": []
|
"repoDigests": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"distro": {
|
"distro": {
|
||||||
|
"prettyName": "debian",
|
||||||
"name": "debian",
|
"name": "debian",
|
||||||
|
"id": "debian",
|
||||||
|
"idLike": [
|
||||||
|
"like!"
|
||||||
|
],
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"idLike": "like!"
|
"versionID": "1.2.3"
|
||||||
},
|
},
|
||||||
"descriptor": {
|
"descriptor": {
|
||||||
"name": "syft",
|
"name": "syft",
|
||||||
@ -104,7 +109,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"schema": {
|
"schema": {
|
||||||
"version": "2.0.2",
|
"version": "3.0.0",
|
||||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-2.0.2.json"
|
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.0.0.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@ -5,6 +5,8 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/linux"
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
@ -14,12 +16,14 @@ import (
|
|||||||
"github.com/anchore/syft/internal"
|
"github.com/anchore/syft/internal"
|
||||||
"github.com/anchore/syft/internal/formats/syftjson/model"
|
"github.com/anchore/syft/internal/formats/syftjson/model"
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
"github.com/anchore/syft/syft/distro"
|
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
"github.com/anchore/syft/syft/source"
|
"github.com/anchore/syft/syft/source"
|
||||||
)
|
)
|
||||||
|
|
||||||
func toFormatModel(s sbom.SBOM) model.Document {
|
// ToFormatModel transforms the sbom import a format-specific model.
|
||||||
|
// note: this is needed for anchore import functionality
|
||||||
|
// TODO: unexport this when/if anchore import functionality is removed
|
||||||
|
func ToFormatModel(s sbom.SBOM) model.Document {
|
||||||
src, err := toSourceModel(s.Source)
|
src, err := toSourceModel(s.Source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("unable to create syft-json source object: %+v", err)
|
log.Warnf("unable to create syft-json source object: %+v", err)
|
||||||
@ -31,7 +35,7 @@ func toFormatModel(s sbom.SBOM) model.Document {
|
|||||||
Files: toFile(s),
|
Files: toFile(s),
|
||||||
Secrets: toSecrets(s.Artifacts.Secrets),
|
Secrets: toSecrets(s.Artifacts.Secrets),
|
||||||
Source: src,
|
Source: src,
|
||||||
Distro: toDistroModel(s.Artifacts.Distro),
|
Distro: toLinuxReleaser(s.Artifacts.LinuxDistribution),
|
||||||
Descriptor: toDescriptor(s.Descriptor),
|
Descriptor: toDescriptor(s.Descriptor),
|
||||||
Schema: model.Schema{
|
Schema: model.Schema{
|
||||||
Version: internal.JSONSchemaVersion,
|
Version: internal.JSONSchemaVersion,
|
||||||
@ -40,6 +44,27 @@ func toFormatModel(s sbom.SBOM) model.Document {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toLinuxReleaser(d *linux.Release) model.LinuxRelease {
|
||||||
|
if d == nil {
|
||||||
|
return model.LinuxRelease{}
|
||||||
|
}
|
||||||
|
return model.LinuxRelease{
|
||||||
|
PrettyName: d.PrettyName,
|
||||||
|
Name: d.Name,
|
||||||
|
ID: d.ID,
|
||||||
|
IDLike: d.IDLike,
|
||||||
|
Version: d.Version,
|
||||||
|
VersionID: d.VersionID,
|
||||||
|
Variant: d.Variant,
|
||||||
|
VariantID: d.VariantID,
|
||||||
|
HomeURL: d.HomeURL,
|
||||||
|
SupportURL: d.SupportURL,
|
||||||
|
BugReportURL: d.BugReportURL,
|
||||||
|
PrivacyPolicyURL: d.PrivacyPolicyURL,
|
||||||
|
CPEName: d.CPEName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func toDescriptor(d sbom.Descriptor) model.Descriptor {
|
func toDescriptor(d sbom.Descriptor) model.Descriptor {
|
||||||
return model.Descriptor{
|
return model.Descriptor{
|
||||||
Name: d.Name,
|
Name: d.Name,
|
||||||
@ -210,16 +235,3 @@ func toSourceModel(src source.Metadata) (model.Source, error) {
|
|||||||
return model.Source{}, fmt.Errorf("unsupported source: %q", src.Scheme)
|
return model.Source{}, fmt.Errorf("unsupported source: %q", src.Scheme)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// toDistroModel creates a struct with the Linux distribution to be represented in JSON.
|
|
||||||
func toDistroModel(d *distro.Distro) model.Distro {
|
|
||||||
if d == nil {
|
|
||||||
return model.Distro{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return model.Distro{
|
|
||||||
Name: d.Name(),
|
|
||||||
Version: d.FullVersion(),
|
|
||||||
IDLike: d.IDLike,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -3,28 +3,45 @@ package syftjson
|
|||||||
import (
|
import (
|
||||||
"github.com/anchore/syft/internal/formats/syftjson/model"
|
"github.com/anchore/syft/internal/formats/syftjson/model"
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
"github.com/anchore/syft/syft/distro"
|
"github.com/anchore/syft/syft/linux"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
"github.com/anchore/syft/syft/sbom"
|
"github.com/anchore/syft/syft/sbom"
|
||||||
"github.com/anchore/syft/syft/source"
|
"github.com/anchore/syft/syft/source"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func toSyftModel(doc model.Document) (*sbom.SBOM, error) {
|
func toSyftModel(doc model.Document) (*sbom.SBOM, error) {
|
||||||
dist, err := distro.NewDistro(distro.Type(doc.Distro.Name), doc.Distro.Version, doc.Distro.IDLike)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &sbom.SBOM{
|
return &sbom.SBOM{
|
||||||
Artifacts: sbom.Artifacts{
|
Artifacts: sbom.Artifacts{
|
||||||
PackageCatalog: toSyftCatalog(doc.Artifacts),
|
PackageCatalog: toSyftCatalog(doc.Artifacts),
|
||||||
Distro: &dist,
|
LinuxDistribution: toSyftLinuxRelease(doc.Distro),
|
||||||
},
|
},
|
||||||
Source: *toSyftSourceData(doc.Source),
|
Source: *toSyftSourceData(doc.Source),
|
||||||
Descriptor: toSyftDescriptor(doc.Descriptor),
|
Descriptor: toSyftDescriptor(doc.Descriptor),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toSyftLinuxRelease(d model.LinuxRelease) *linux.Release {
|
||||||
|
if cmp.Equal(d, model.LinuxRelease{}) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &linux.Release{
|
||||||
|
PrettyName: d.PrettyName,
|
||||||
|
Name: d.Name,
|
||||||
|
ID: d.ID,
|
||||||
|
IDLike: d.IDLike,
|
||||||
|
Version: d.Version,
|
||||||
|
VersionID: d.VersionID,
|
||||||
|
Variant: d.Variant,
|
||||||
|
VariantID: d.VariantID,
|
||||||
|
HomeURL: d.HomeURL,
|
||||||
|
SupportURL: d.SupportURL,
|
||||||
|
BugReportURL: d.BugReportURL,
|
||||||
|
PrivacyPolicyURL: d.PrivacyPolicyURL,
|
||||||
|
CPEName: d.CPEName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func toSyftDescriptor(d model.Descriptor) sbom.Descriptor {
|
func toSyftDescriptor(d model.Descriptor) sbom.Descriptor {
|
||||||
return sbom.Descriptor{
|
return sbom.Descriptor{
|
||||||
Name: d.Name,
|
Name: d.Name,
|
||||||
|
|||||||
1075
schema/json/schema-3.0.0.json
Normal file
1075
schema/json/schema-3.0.0.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -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)
|
|
||||||
}
|
|
||||||
14
syft/lib.go
14
syft/lib.go
@ -23,7 +23,7 @@ import (
|
|||||||
|
|
||||||
"github.com/anchore/syft/internal/bus"
|
"github.com/anchore/syft/internal/bus"
|
||||||
"github.com/anchore/syft/internal/log"
|
"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/logger"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger"
|
"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
|
// 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
|
// (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.
|
// 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)
|
resolver, err := src.FileResolver(cfg.Search.Scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, fmt.Errorf("unable to determine resolver while cataloging packages: %w", err)
|
return nil, nil, nil, fmt.Errorf("unable to determine resolver while cataloging packages: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// find the distro
|
// find the distro
|
||||||
theDistro := distro.Identify(resolver)
|
release := linux.IdentifyRelease(resolver)
|
||||||
if theDistro != nil {
|
if release != nil {
|
||||||
log.Infof("identified distro: %s", theDistro.String())
|
log.Infof("identified distro: %s", release.String())
|
||||||
} else {
|
} else {
|
||||||
log.Info("could not identify distro")
|
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)
|
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 {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
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.
|
// SetLogger sets the logger object used for all syft logging calls.
|
||||||
|
|||||||
184
syft/linux/identify_release.go
Normal file
184
syft/linux/identify_release.go
Normal file
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
533
syft/linux/identify_release_test.go
Normal file
533
syft/linux/identify_release_test.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
35
syft/linux/release.go
Normal file
35
syft/linux/release.go
Normal file
@ -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/bus"
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/distro"
|
|
||||||
"github.com/anchore/syft/syft/event"
|
"github.com/anchore/syft/syft/event"
|
||||||
|
"github.com/anchore/syft/syft/linux"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/common/cpe"
|
"github.com/anchore/syft/syft/pkg/cataloger/common/cpe"
|
||||||
"github.com/anchore/syft/syft/source"
|
"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
|
// 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
|
// done in bulk. Specifically, all files of interest are collected from each catalogers and accumulated into a single
|
||||||
// request.
|
// 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()
|
catalog := pkg.NewCatalog()
|
||||||
var allRelationships []artifact.Relationship
|
var allRelationships []artifact.Relationship
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ func Catalog(resolver source.FileResolver, theDistro *distro.Distro, catalogers
|
|||||||
p.CPEs = cpe.Generate(p)
|
p.CPEs = cpe.Generate(p)
|
||||||
|
|
||||||
// generate PURL (note: this is excluded from package ID, so is safe to mutate)
|
// 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
|
// create file-to-package relationships for files owned by the package
|
||||||
owningRelationships, err := packageFileOwnershipRelationships(p, resolver)
|
owningRelationships, err := packageFileOwnershipRelationships(p, resolver)
|
||||||
|
|||||||
@ -5,18 +5,18 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/anchore/packageurl-go"
|
"github.com/anchore/packageurl-go"
|
||||||
"github.com/anchore/syft/syft/distro"
|
"github.com/anchore/syft/syft/linux"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
// generatePackageURL returns a package-URL representation of the given package (see https://github.com/package-url/purl-spec)
|
// 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
|
// default to pURLs on the metadata
|
||||||
if p.Metadata != nil {
|
if p.Metadata != nil {
|
||||||
if i, ok := p.Metadata.(interface{ PackageURL() string }); ok {
|
if i, ok := p.Metadata.(interface{ PackageURL() string }); ok {
|
||||||
return i.PackageURL()
|
return i.PackageURL()
|
||||||
} else if i, ok := p.Metadata.(interface{ PackageURL(*distro.Distro) string }); ok {
|
} else if i, ok := p.Metadata.(interface{ PackageURL(*linux.Release) string }); ok {
|
||||||
return i.PackageURL(d)
|
return i.PackageURL(release)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package cataloger
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/distro"
|
"github.com/anchore/syft/syft/linux"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
"github.com/sergi/go-diff/diffmatchpatch"
|
"github.com/sergi/go-diff/diffmatchpatch"
|
||||||
)
|
)
|
||||||
@ -12,7 +12,7 @@ func TestPackageURL(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
pkg pkg.Package
|
pkg pkg.Package
|
||||||
distro *distro.Distro
|
distro *linux.Release
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@ -75,8 +75,8 @@ func TestPackageURL(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "deb with arch",
|
name: "deb with arch",
|
||||||
distro: &distro.Distro{
|
distro: &linux.Release{
|
||||||
Type: distro.Ubuntu,
|
ID: "ubuntu",
|
||||||
},
|
},
|
||||||
pkg: pkg.Package{
|
pkg: pkg.Package{
|
||||||
Name: "bad-name",
|
Name: "bad-name",
|
||||||
@ -92,8 +92,8 @@ func TestPackageURL(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "deb with epoch",
|
name: "deb with epoch",
|
||||||
distro: &distro.Distro{
|
distro: &linux.Release{
|
||||||
Type: distro.CentOS,
|
ID: "centos",
|
||||||
},
|
},
|
||||||
pkg: pkg.Package{
|
pkg: pkg.Package{
|
||||||
Name: "bad-name",
|
Name: "bad-name",
|
||||||
@ -111,8 +111,8 @@ func TestPackageURL(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "deb with nil epoch",
|
name: "deb with nil epoch",
|
||||||
distro: &distro.Distro{
|
distro: &linux.Release{
|
||||||
Type: distro.CentOS,
|
ID: "centos",
|
||||||
},
|
},
|
||||||
pkg: pkg.Package{
|
pkg: pkg.Package{
|
||||||
Name: "bad-name",
|
Name: "bad-name",
|
||||||
@ -129,10 +129,8 @@ func TestPackageURL(t *testing.T) {
|
|||||||
expected: "pkg:rpm/centos/name@0.1.0-3?arch=amd64",
|
expected: "pkg:rpm/centos/name@0.1.0-3?arch=amd64",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "deb with unknown distro",
|
name: "deb with unknown distro",
|
||||||
distro: &distro.Distro{
|
distro: nil,
|
||||||
Type: distro.UnknownDistroType,
|
|
||||||
},
|
|
||||||
pkg: pkg.Package{
|
pkg: pkg.Package{
|
||||||
Name: "name",
|
Name: "name",
|
||||||
Version: "v0.1.0",
|
Version: "v0.1.0",
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import (
|
|||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
|
|
||||||
"github.com/anchore/packageurl-go"
|
"github.com/anchore/packageurl-go"
|
||||||
"github.com/anchore/syft/syft/distro"
|
"github.com/anchore/syft/syft/linux"
|
||||||
"github.com/scylladb/go-set/strset"
|
"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)
|
// 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 {
|
func (m DpkgMetadata) PackageURL(distro *linux.Release) string {
|
||||||
if d == nil {
|
if distro == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
pURL := packageurl.NewPackageURL(
|
pURL := packageurl.NewPackageURL(
|
||||||
// TODO: replace with `packageurl.TypeDebian` upon merge of https://github.com/package-url/packageurl-go/pull/21
|
// 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.
|
// TODO: or, since we're now using an Anchore fork of this module, we could do this sooner.
|
||||||
"deb",
|
"deb",
|
||||||
d.Type.String(),
|
distro.ID,
|
||||||
m.Package,
|
m.Package,
|
||||||
m.Version,
|
m.Version,
|
||||||
packageurl.Qualifiers{
|
packageurl.Qualifiers{
|
||||||
|
|||||||
@ -6,19 +6,19 @@ import (
|
|||||||
|
|
||||||
"github.com/go-test/deep"
|
"github.com/go-test/deep"
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/distro"
|
"github.com/anchore/syft/syft/linux"
|
||||||
"github.com/sergi/go-diff/diffmatchpatch"
|
"github.com/sergi/go-diff/diffmatchpatch"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDpkgMetadata_pURL(t *testing.T) {
|
func TestDpkgMetadata_pURL(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
distro distro.Distro
|
distro linux.Release
|
||||||
metadata DpkgMetadata
|
metadata DpkgMetadata
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
distro: distro.Distro{
|
distro: linux.Release{
|
||||||
Type: distro.Debian,
|
ID: "debian",
|
||||||
},
|
},
|
||||||
metadata: DpkgMetadata{
|
metadata: DpkgMetadata{
|
||||||
Package: "p",
|
Package: "p",
|
||||||
@ -29,8 +29,8 @@ func TestDpkgMetadata_pURL(t *testing.T) {
|
|||||||
expected: "pkg:deb/debian/p@v?arch=a",
|
expected: "pkg:deb/debian/p@v?arch=a",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
distro: distro.Distro{
|
distro: linux.Release{
|
||||||
Type: distro.Ubuntu,
|
ID: "ubuntu",
|
||||||
},
|
},
|
||||||
metadata: DpkgMetadata{
|
metadata: DpkgMetadata{
|
||||||
Package: "p",
|
Package: "p",
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/scylladb/go-set/strset"
|
"github.com/scylladb/go-set/strset"
|
||||||
|
|
||||||
"github.com/anchore/packageurl-go"
|
"github.com/anchore/packageurl-go"
|
||||||
"github.com/anchore/syft/syft/distro"
|
"github.com/anchore/syft/syft/linux"
|
||||||
)
|
)
|
||||||
|
|
||||||
const RpmDBGlob = "**/var/lib/rpm/Packages"
|
const RpmDBGlob = "**/var/lib/rpm/Packages"
|
||||||
@ -46,8 +46,8 @@ type RpmdbFileRecord struct {
|
|||||||
type RpmdbFileMode uint16
|
type RpmdbFileMode uint16
|
||||||
|
|
||||||
// PackageURL returns the PURL for the specific RHEL package (see https://github.com/package-url/purl-spec)
|
// 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 {
|
func (m RpmdbMetadata) PackageURL(distro *linux.Release) string {
|
||||||
if d == nil {
|
if distro == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ func (m RpmdbMetadata) PackageURL(d *distro.Distro) string {
|
|||||||
|
|
||||||
pURL := packageurl.NewPackageURL(
|
pURL := packageurl.NewPackageURL(
|
||||||
packageurl.TypeRPM,
|
packageurl.TypeRPM,
|
||||||
d.Type.String(),
|
distro.ID,
|
||||||
m.Name,
|
m.Name,
|
||||||
// for purl the epoch is a qualifier, not part of the version
|
// 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
|
// 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/go-test/deep"
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/distro"
|
"github.com/anchore/syft/syft/linux"
|
||||||
"github.com/sergi/go-diff/diffmatchpatch"
|
"github.com/sergi/go-diff/diffmatchpatch"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRpmMetadata_pURL(t *testing.T) {
|
func TestRpmMetadata_pURL(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
distro distro.Distro
|
distro linux.Release
|
||||||
metadata RpmdbMetadata
|
metadata RpmdbMetadata
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
distro: distro.Distro{
|
distro: linux.Release{
|
||||||
Type: distro.CentOS,
|
ID: "centos",
|
||||||
},
|
},
|
||||||
metadata: RpmdbMetadata{
|
metadata: RpmdbMetadata{
|
||||||
Name: "p",
|
Name: "p",
|
||||||
@ -30,8 +30,8 @@ func TestRpmMetadata_pURL(t *testing.T) {
|
|||||||
expected: "pkg:rpm/centos/p@v-r?arch=a&epoch=1",
|
expected: "pkg:rpm/centos/p@v-r?arch=a&epoch=1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
distro: distro.Distro{
|
distro: linux.Release{
|
||||||
Type: distro.RedHat,
|
ID: "rhel",
|
||||||
},
|
},
|
||||||
metadata: RpmdbMetadata{
|
metadata: RpmdbMetadata{
|
||||||
Name: "p",
|
Name: "p",
|
||||||
@ -40,7 +40,7 @@ func TestRpmMetadata_pURL(t *testing.T) {
|
|||||||
Release: "r",
|
Release: "r",
|
||||||
Epoch: nil,
|
Epoch: nil,
|
||||||
},
|
},
|
||||||
expected: "pkg:rpm/redhat/p@v-r?arch=a",
|
expected: "pkg:rpm/rhel/p@v-r?arch=a",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,8 +2,8 @@ package sbom
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/distro"
|
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
|
"github.com/anchore/syft/syft/linux"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
"github.com/anchore/syft/syft/source"
|
"github.com/anchore/syft/syft/source"
|
||||||
)
|
)
|
||||||
@ -22,7 +22,7 @@ type Artifacts struct {
|
|||||||
FileClassifications map[source.Coordinates][]file.Classification
|
FileClassifications map[source.Coordinates][]file.Classification
|
||||||
FileContents map[source.Coordinates]string
|
FileContents map[source.Coordinates]string
|
||||||
Secrets map[source.Coordinates][]file.SearchResult
|
Secrets map[source.Coordinates][]file.SearchResult
|
||||||
Distro *distro.Distro
|
LinuxDistribution *linux.Release
|
||||||
}
|
}
|
||||||
|
|
||||||
type Descriptor struct {
|
type Descriptor struct {
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package integration
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/distro"
|
"github.com/anchore/syft/syft/linux"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger"
|
"github.com/anchore/syft/syft/pkg/cataloger"
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ func BenchmarkImagePackageCatalogers(b *testing.B) {
|
|||||||
b.Fatalf("unable to get resolver: %+v", err)
|
b.Fatalf("unable to get resolver: %+v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
theDistro := distro.Identify(resolver)
|
theDistro := linux.IdentifyRelease(resolver)
|
||||||
|
|
||||||
b.Run(c.Name(), func(b *testing.B) {
|
b.Run(c.Name(), func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
|
|||||||
@ -3,20 +3,22 @@ package integration
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/distro"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/go-test/deep"
|
|
||||||
|
"github.com/anchore/syft/syft/linux"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDistroImage(t *testing.T) {
|
func TestDistroImage(t *testing.T) {
|
||||||
sbom, _ := catalogFixtureImage(t, "image-distro-id")
|
sbom, _ := catalogFixtureImage(t, "image-distro-id")
|
||||||
|
|
||||||
expected, err := distro.NewDistro(distro.Busybox, "1.31.1", "")
|
expected := &linux.Release{
|
||||||
if err != nil {
|
PrettyName: "BusyBox v1.31.1",
|
||||||
t.Fatalf("could not create distro: %+v", err)
|
Name: "busybox",
|
||||||
}
|
ID: "busybox",
|
||||||
|
IDLike: []string{"busybox"},
|
||||||
for _, d := range deep.Equal(sbom.Artifacts.Distro, &expected) {
|
Version: "1.31.1",
|
||||||
t.Errorf("found distro difference: %+v", d)
|
VersionID: "1.31.1",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, expected, sbom.Artifacts.LinuxDistribution)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,3 +2,4 @@ FROM scratch
|
|||||||
COPY pkgs/ .
|
COPY pkgs/ .
|
||||||
# we duplicate to show a package count difference between all-layers and squashed scopes
|
# we duplicate to show a package count difference between all-layers and squashed scopes
|
||||||
COPY lib lib
|
COPY lib lib
|
||||||
|
COPY etc/ .
|
||||||
@ -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
|
||||||
@ -32,8 +32,8 @@ func catalogFixtureImage(t *testing.T, fixtureImageName string) (sbom.SBOM, *sou
|
|||||||
|
|
||||||
return sbom.SBOM{
|
return sbom.SBOM{
|
||||||
Artifacts: sbom.Artifacts{
|
Artifacts: sbom.Artifacts{
|
||||||
PackageCatalog: pkgCatalog,
|
PackageCatalog: pkgCatalog,
|
||||||
Distro: actualDistro,
|
LinuxDistribution: actualDistro,
|
||||||
},
|
},
|
||||||
Relationships: relationships,
|
Relationships: relationships,
|
||||||
Source: theSource.Metadata,
|
Source: theSource.Metadata,
|
||||||
@ -66,8 +66,8 @@ func catalogDirectory(t *testing.T, dir string) (sbom.SBOM, *source.Source) {
|
|||||||
|
|
||||||
return sbom.SBOM{
|
return sbom.SBOM{
|
||||||
Artifacts: sbom.Artifacts{
|
Artifacts: sbom.Artifacts{
|
||||||
PackageCatalog: pkgCatalog,
|
PackageCatalog: pkgCatalog,
|
||||||
Distro: actualDistro,
|
LinuxDistribution: actualDistro,
|
||||||
},
|
},
|
||||||
Relationships: relationships,
|
Relationships: relationships,
|
||||||
Source: theSource.Metadata,
|
Source: theSource.Metadata,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user