mirror of
https://github.com/anchore/syft.git
synced 2026-02-12 10:36:45 +01:00
Add CycloneDX decoder (#811)
This commit is contained in:
parent
4b16737b2f
commit
20c1d14f6e
@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Author(p pkg.Package) string {
|
func encodeAuthor(p pkg.Package) string {
|
||||||
if hasMetadata(p) {
|
if hasMetadata(p) {
|
||||||
switch metadata := p.Metadata.(type) {
|
switch metadata := p.Metadata.(type) {
|
||||||
case pkg.NpmPackageJSONMetadata:
|
case pkg.NpmPackageJSONMetadata:
|
||||||
@ -30,3 +30,18 @@ func Author(p pkg.Package) string {
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func decodeAuthor(author string, metadata interface{}) {
|
||||||
|
switch meta := metadata.(type) {
|
||||||
|
case *pkg.NpmPackageJSONMetadata:
|
||||||
|
meta.Author = author
|
||||||
|
case *pkg.PythonPackageMetadata:
|
||||||
|
parts := strings.SplitN(author, " <", 2)
|
||||||
|
meta.Author = parts[0]
|
||||||
|
if len(parts) > 1 {
|
||||||
|
meta.AuthorEmail = strings.TrimSuffix(parts[1], ">")
|
||||||
|
}
|
||||||
|
case *pkg.GemMetadata:
|
||||||
|
meta.Authors = strings.Split(author, ",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_Author(t *testing.T) {
|
func Test_encodeAuthor(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
input pkg.Package
|
input pkg.Package
|
||||||
@ -81,7 +81,7 @@ func Test_Author(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
assert.Equal(t, test.expected, Author(test.input))
|
assert.Equal(t, test.expected, encodeAuthor(test.input))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,27 +1,164 @@
|
|||||||
package cyclonedxhelpers
|
package cyclonedxhelpers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/CycloneDX/cyclonedx-go"
|
"github.com/CycloneDX/cyclonedx-go"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal/log"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Component(p pkg.Package) cyclonedx.Component {
|
func encodeComponent(p pkg.Package) cyclonedx.Component {
|
||||||
return cyclonedx.Component{
|
return cyclonedx.Component{
|
||||||
Type: cyclonedx.ComponentTypeLibrary,
|
Type: cyclonedx.ComponentTypeLibrary,
|
||||||
Name: p.Name,
|
Name: p.Name,
|
||||||
Group: Group(p),
|
Group: encodeGroup(p),
|
||||||
Version: p.Version,
|
Version: p.Version,
|
||||||
PackageURL: p.PURL,
|
PackageURL: p.PURL,
|
||||||
Licenses: Licenses(p),
|
Licenses: encodeLicenses(p),
|
||||||
CPE: CPE(p),
|
CPE: encodeCPE(p),
|
||||||
Author: Author(p),
|
Author: encodeAuthor(p),
|
||||||
Publisher: Publisher(p),
|
Publisher: encodePublisher(p),
|
||||||
Description: Description(p),
|
Description: encodeDescription(p),
|
||||||
ExternalReferences: ExternalReferences(p),
|
ExternalReferences: encodeExternalReferences(p),
|
||||||
Properties: Properties(p),
|
Properties: encodeProperties(p),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasMetadata(p pkg.Package) bool {
|
func hasMetadata(p pkg.Package) bool {
|
||||||
return p.Metadata != nil
|
return p.Metadata != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func decodeComponent(c *cyclonedx.Component) *pkg.Package {
|
||||||
|
typ := pkg.Type(findPropertyValue(c, "type"))
|
||||||
|
purl := c.PackageURL
|
||||||
|
if typ == "" && purl != "" {
|
||||||
|
typ = pkg.TypeFromPURL(purl)
|
||||||
|
}
|
||||||
|
|
||||||
|
metaType, meta := decodePackageMetadata(c)
|
||||||
|
|
||||||
|
p := &pkg.Package{
|
||||||
|
Name: c.Name,
|
||||||
|
Version: c.Version,
|
||||||
|
FoundBy: findPropertyValue(c, "foundBy"),
|
||||||
|
Locations: decodeLocations(c),
|
||||||
|
Licenses: decodeLicenses(c),
|
||||||
|
Language: pkg.Language(findPropertyValue(c, "language")),
|
||||||
|
Type: typ,
|
||||||
|
CPEs: decodeCPEs(c),
|
||||||
|
PURL: purl,
|
||||||
|
MetadataType: metaType,
|
||||||
|
Metadata: meta,
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeLocations(c *cyclonedx.Component) (out []source.Location) {
|
||||||
|
if c.Properties != nil {
|
||||||
|
props := *c.Properties
|
||||||
|
for i := 0; i < len(props)-1; i++ {
|
||||||
|
if props[i].Name == "path" && props[i+1].Name == "layerID" {
|
||||||
|
out = append(out, source.Location{
|
||||||
|
Coordinates: source.Coordinates{
|
||||||
|
RealPath: props[i].Value,
|
||||||
|
FileSystemID: props[i+1].Value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapAllProps(c *cyclonedx.Component, obj reflect.Value) {
|
||||||
|
value := obj
|
||||||
|
if value.Kind() == reflect.Ptr {
|
||||||
|
value = value.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
structType := value.Type()
|
||||||
|
if structType.Kind() != reflect.Struct {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := 0; i < value.NumField(); i++ {
|
||||||
|
field := structType.Field(i)
|
||||||
|
fieldType := field.Type
|
||||||
|
fieldValue := value.Field(i)
|
||||||
|
|
||||||
|
name, mapped := field.Tag.Lookup("cyclonedx")
|
||||||
|
if !mapped {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if fieldType.Kind() == reflect.Ptr {
|
||||||
|
fieldType = fieldType.Elem()
|
||||||
|
if fieldValue.IsNil() {
|
||||||
|
newValue := reflect.New(fieldType)
|
||||||
|
fieldValue.Set(newValue)
|
||||||
|
}
|
||||||
|
fieldValue = fieldValue.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
propertyValue := findPropertyValue(c, name)
|
||||||
|
switch fieldType.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
if fieldValue.CanSet() {
|
||||||
|
fieldValue.SetString(propertyValue)
|
||||||
|
} else {
|
||||||
|
msg := fmt.Sprintf("unable to set field: %s.%s", structType.Name(), field.Name)
|
||||||
|
log.Info(msg)
|
||||||
|
}
|
||||||
|
case reflect.Bool:
|
||||||
|
if b, err := strconv.ParseBool(propertyValue); err == nil {
|
||||||
|
fieldValue.SetBool(b)
|
||||||
|
}
|
||||||
|
case reflect.Int:
|
||||||
|
if i, err := strconv.Atoi(propertyValue); err == nil {
|
||||||
|
fieldValue.SetInt(int64(i))
|
||||||
|
}
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
if i, err := strconv.ParseFloat(propertyValue, 64); err == nil {
|
||||||
|
fieldValue.SetFloat(i)
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
mapAllProps(c, fieldValue)
|
||||||
|
case reflect.Complex128, reflect.Complex64:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Ptr:
|
||||||
|
msg := fmt.Sprintf("decoding CycloneDX properties to a pointer is not supported: %s.%s", field.Type.Name(), field.Name)
|
||||||
|
log.Warnf(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodePackageMetadata(c *cyclonedx.Component) (pkg.MetadataType, interface{}) {
|
||||||
|
if c.Properties != nil {
|
||||||
|
typ := pkg.MetadataType(findPropertyValue(c, "metadataType"))
|
||||||
|
if typ != "" {
|
||||||
|
meta := reflect.New(pkg.MetadataTypeByName[typ])
|
||||||
|
metaPtr := meta.Interface()
|
||||||
|
|
||||||
|
// Map all dynamic properties
|
||||||
|
mapAllProps(c, meta.Elem())
|
||||||
|
|
||||||
|
// Map all explicit metadata properties
|
||||||
|
decodeAuthor(c.Author, metaPtr)
|
||||||
|
decodeGroup(c.Group, metaPtr)
|
||||||
|
decodePublisher(c.Publisher, metaPtr)
|
||||||
|
decodeDescription(c.Description, metaPtr)
|
||||||
|
decodeExternalReferences(c, metaPtr)
|
||||||
|
|
||||||
|
// return the actual interface{} | struct ( not interface{} | *struct )
|
||||||
|
return typ, meta.Elem().Interface()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkg.UnknownMetadataType, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
package cyclonedxhelpers
|
package cyclonedxhelpers
|
||||||
|
|
||||||
import "github.com/anchore/syft/syft/pkg"
|
import (
|
||||||
|
"github.com/CycloneDX/cyclonedx-go"
|
||||||
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
func CPE(p pkg.Package) string {
|
func encodeCPE(p pkg.Package) string {
|
||||||
// Since the CPEs in a package are sorted by specificity
|
// Since the CPEs in a package are sorted by specificity
|
||||||
// we can extract the first CPE as the one to output in cyclonedx
|
// we can extract the first CPE as the one to output in cyclonedx
|
||||||
if len(p.CPEs) > 0 {
|
if len(p.CPEs) > 0 {
|
||||||
@ -10,3 +14,17 @@ func CPE(p pkg.Package) string {
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func decodeCPEs(c *cyclonedx.Component) []pkg.CPE {
|
||||||
|
// FIXME we not encoding all the CPEs (see above), so here we just use the single provided one
|
||||||
|
if c.CPE != "" {
|
||||||
|
cp, err := pkg.NewCPE(c.CPE)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("invalid CPE: %s", c.CPE)
|
||||||
|
} else {
|
||||||
|
return []pkg.CPE{cp}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []pkg.CPE{}
|
||||||
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_CPE(t *testing.T) {
|
func Test_encodeCPE(t *testing.T) {
|
||||||
testCPE := pkg.MustCPE("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*")
|
testCPE := pkg.MustCPE("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*")
|
||||||
testCPE2 := pkg.MustCPE("cpe:2.3:a:name:name2:3.2:*:*:*:*:*:*:*")
|
testCPE2 := pkg.MustCPE("cpe:2.3:a:name:name2:3.2:*:*:*:*:*:*:*")
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@ -51,7 +51,7 @@ func Test_CPE(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
assert.Equal(t, test.expected, CPE(test.input))
|
assert.Equal(t, test.expected, encodeCPE(test.input))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
222
internal/formats/common/cyclonedxhelpers/decoder.go
Normal file
222
internal/formats/common/cyclonedxhelpers/decoder.go
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
package cyclonedxhelpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/CycloneDX/cyclonedx-go"
|
||||||
|
"github.com/anchore/syft/syft/artifact"
|
||||||
|
"github.com/anchore/syft/syft/format"
|
||||||
|
"github.com/anchore/syft/syft/linux"
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/sbom"
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetValidator(format cyclonedx.BOMFileFormat) format.Validator {
|
||||||
|
return func(reader io.Reader) error {
|
||||||
|
bom := &cyclonedx.BOM{}
|
||||||
|
err := cyclonedx.NewBOMDecoder(reader, format).Decode(bom)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// random JSON does not necessarily cause an error (e.g. SPDX)
|
||||||
|
if (cyclonedx.BOM{} == *bom) {
|
||||||
|
return fmt.Errorf("not a valid CycloneDX document")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDecoder(format cyclonedx.BOMFileFormat) format.Decoder {
|
||||||
|
return func(reader io.Reader) (*sbom.SBOM, error) {
|
||||||
|
bom := &cyclonedx.BOM{}
|
||||||
|
err := cyclonedx.NewBOMDecoder(reader, format).Decode(bom)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s, err := toSyftModel(bom)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toSyftModel(bom *cyclonedx.BOM) (*sbom.SBOM, error) {
|
||||||
|
meta := source.Metadata{}
|
||||||
|
if bom.Metadata != nil {
|
||||||
|
meta = decodeMetadata(bom.Metadata.Component)
|
||||||
|
}
|
||||||
|
s := &sbom.SBOM{
|
||||||
|
Artifacts: sbom.Artifacts{
|
||||||
|
PackageCatalog: pkg.NewCatalog(),
|
||||||
|
LinuxDistribution: linuxReleaseFromComponents(*bom.Components),
|
||||||
|
},
|
||||||
|
Source: meta,
|
||||||
|
//Descriptor: sbom.Descriptor{},
|
||||||
|
}
|
||||||
|
|
||||||
|
idMap := make(map[string]interface{})
|
||||||
|
|
||||||
|
if err := collectBomPackages(bom, s, idMap); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
collectRelationships(bom, s, idMap)
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectBomPackages(bom *cyclonedx.BOM, s *sbom.SBOM, idMap map[string]interface{}) error {
|
||||||
|
if bom.Components == nil {
|
||||||
|
return fmt.Errorf("no components are defined in the CycloneDX BOM")
|
||||||
|
}
|
||||||
|
for i := range *bom.Components {
|
||||||
|
collectPackages(&(*bom.Components)[i], s, idMap)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectPackages(component *cyclonedx.Component, s *sbom.SBOM, idMap map[string]interface{}) {
|
||||||
|
switch component.Type {
|
||||||
|
case cyclonedx.ComponentTypeOS:
|
||||||
|
case cyclonedx.ComponentTypeContainer:
|
||||||
|
case cyclonedx.ComponentTypeApplication, cyclonedx.ComponentTypeFramework, cyclonedx.ComponentTypeLibrary:
|
||||||
|
p := decodeComponent(component)
|
||||||
|
idMap[component.BOMRef] = p
|
||||||
|
// TODO there must be a better way than needing to call this manually:
|
||||||
|
p.SetID()
|
||||||
|
s.Artifacts.PackageCatalog.Add(*p)
|
||||||
|
}
|
||||||
|
|
||||||
|
if component.Components != nil {
|
||||||
|
for i := range *component.Components {
|
||||||
|
collectPackages(&(*component.Components)[i], s, idMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func linuxReleaseFromComponents(components []cyclonedx.Component) *linux.Release {
|
||||||
|
for i := range components {
|
||||||
|
component := &components[i]
|
||||||
|
if component.Type == cyclonedx.ComponentTypeOS {
|
||||||
|
return linuxReleaseFromOSComponent(component)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func linuxReleaseFromOSComponent(component *cyclonedx.Component) *linux.Release {
|
||||||
|
if component == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var name string
|
||||||
|
var version string
|
||||||
|
if component.SWID != nil {
|
||||||
|
name = component.SWID.Name
|
||||||
|
version = component.SWID.Version
|
||||||
|
}
|
||||||
|
if name == "" {
|
||||||
|
name = component.Name
|
||||||
|
}
|
||||||
|
if name == "" {
|
||||||
|
name = getPropertyValue(component, "id")
|
||||||
|
}
|
||||||
|
if version == "" {
|
||||||
|
version = component.Version
|
||||||
|
}
|
||||||
|
if version == "" {
|
||||||
|
version = getPropertyValue(component, "versionID")
|
||||||
|
}
|
||||||
|
|
||||||
|
rel := &linux.Release{
|
||||||
|
CPEName: component.CPE,
|
||||||
|
PrettyName: name,
|
||||||
|
Name: name,
|
||||||
|
ID: name,
|
||||||
|
IDLike: []string{name},
|
||||||
|
Version: version,
|
||||||
|
VersionID: version,
|
||||||
|
}
|
||||||
|
if component.ExternalReferences != nil {
|
||||||
|
for _, ref := range *component.ExternalReferences {
|
||||||
|
switch ref.Type {
|
||||||
|
case cyclonedx.ERTypeIssueTracker:
|
||||||
|
rel.BugReportURL = ref.URL
|
||||||
|
case cyclonedx.ERTypeWebsite:
|
||||||
|
rel.HomeURL = ref.URL
|
||||||
|
case cyclonedx.ERTypeOther:
|
||||||
|
switch ref.Comment {
|
||||||
|
case "support":
|
||||||
|
rel.SupportURL = ref.URL
|
||||||
|
case "privacyPolicy":
|
||||||
|
rel.PrivacyPolicyURL = ref.URL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rel
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPropertyValue(component *cyclonedx.Component, name string) string {
|
||||||
|
if component.Properties != nil {
|
||||||
|
for _, p := range *component.Properties {
|
||||||
|
if p.Name == name {
|
||||||
|
return p.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectRelationships(bom *cyclonedx.BOM, s *sbom.SBOM, idMap map[string]interface{}) {
|
||||||
|
if bom.Dependencies == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, d := range *bom.Dependencies {
|
||||||
|
from, fromOk := idMap[d.Ref].(artifact.Identifiable)
|
||||||
|
if fromOk {
|
||||||
|
if d.Dependencies == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, t := range *d.Dependencies {
|
||||||
|
to, toOk := idMap[t.Ref].(artifact.Identifiable)
|
||||||
|
if toOk {
|
||||||
|
s.Relationships = append(s.Relationships, artifact.Relationship{
|
||||||
|
From: from,
|
||||||
|
To: to,
|
||||||
|
Type: artifact.DependencyOfRelationship, // FIXME this information is lost
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeMetadata(component *cyclonedx.Component) source.Metadata {
|
||||||
|
switch component.Type {
|
||||||
|
case cyclonedx.ComponentTypeContainer:
|
||||||
|
return source.Metadata{
|
||||||
|
Scheme: source.ImageScheme,
|
||||||
|
ImageMetadata: source.ImageMetadata{
|
||||||
|
UserInput: component.Name,
|
||||||
|
ID: component.BOMRef,
|
||||||
|
ManifestDigest: component.Version,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
case cyclonedx.ComponentTypeFile:
|
||||||
|
return source.Metadata{
|
||||||
|
Scheme: source.FileScheme, // or source.DirectoryScheme
|
||||||
|
Path: component.Name,
|
||||||
|
ImageMetadata: source.ImageMetadata{
|
||||||
|
UserInput: component.Name,
|
||||||
|
ID: component.BOMRef,
|
||||||
|
ManifestDigest: component.Version,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return source.Metadata{}
|
||||||
|
}
|
||||||
260
internal/formats/common/cyclonedxhelpers/decoder_test.go
Normal file
260
internal/formats/common/cyclonedxhelpers/decoder_test.go
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
package cyclonedxhelpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/CycloneDX/cyclonedx-go"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_decode(t *testing.T) {
|
||||||
|
type expected struct {
|
||||||
|
os string
|
||||||
|
pkg string
|
||||||
|
ver string
|
||||||
|
relation string
|
||||||
|
purl string
|
||||||
|
cpe string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input cyclonedx.BOM
|
||||||
|
expected []expected
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic mapping from cyclonedx",
|
||||||
|
input: cyclonedx.BOM{
|
||||||
|
Metadata: nil,
|
||||||
|
Components: &[]cyclonedx.Component{
|
||||||
|
{
|
||||||
|
BOMRef: "p1",
|
||||||
|
Type: cyclonedx.ComponentTypeLibrary,
|
||||||
|
Name: "package-1",
|
||||||
|
Version: "1.0.1",
|
||||||
|
Description: "",
|
||||||
|
Hashes: nil,
|
||||||
|
Licenses: &cyclonedx.Licenses{
|
||||||
|
{
|
||||||
|
License: &cyclonedx.License{
|
||||||
|
ID: "MIT",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CPE: "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*",
|
||||||
|
PackageURL: "pkg:some/package-1@1.0.1?arch=arm64&upstream=upstream1&distro=alpine-1",
|
||||||
|
ExternalReferences: &[]cyclonedx.ExternalReference{
|
||||||
|
{
|
||||||
|
URL: "",
|
||||||
|
Comment: "",
|
||||||
|
Hashes: nil,
|
||||||
|
Type: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Properties: &[]cyclonedx.Property{
|
||||||
|
{
|
||||||
|
Name: "foundBy",
|
||||||
|
Value: "the-cataloger-1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "language",
|
||||||
|
Value: "python",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "type",
|
||||||
|
Value: "python",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "metadataType",
|
||||||
|
Value: "PythonPackageMetadata",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "path",
|
||||||
|
Value: "/some/path/pkg1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Components: nil,
|
||||||
|
Evidence: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BOMRef: "p2",
|
||||||
|
Type: cyclonedx.ComponentTypeLibrary,
|
||||||
|
Name: "package-2",
|
||||||
|
Version: "2.0.2",
|
||||||
|
Hashes: nil,
|
||||||
|
Licenses: &cyclonedx.Licenses{
|
||||||
|
{
|
||||||
|
License: &cyclonedx.License{
|
||||||
|
ID: "MIT",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CPE: "cpe:2.3:*:another:package:2:*:*:*:*:*:*:*",
|
||||||
|
PackageURL: "pkg:alpine/alpine-baselayout@3.2.0-r16?arch=x86_64&upstream=alpine-baselayout&distro=alpine-3.14.2",
|
||||||
|
Properties: &[]cyclonedx.Property{
|
||||||
|
|
||||||
|
{
|
||||||
|
Name: "foundBy",
|
||||||
|
Value: "apkdb-cataloger",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "type",
|
||||||
|
Value: "apk",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "metadataType",
|
||||||
|
Value: "ApkMetadata",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "path",
|
||||||
|
Value: "/lib/apk/db/installed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "layerID",
|
||||||
|
Value: "sha256:9fb3aa2f8b8023a4bebbf92aa567caf88e38e969ada9f0ac12643b2847391635",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "originPackage",
|
||||||
|
Value: "zlib",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "size",
|
||||||
|
Value: "51213",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "installedSize",
|
||||||
|
Value: "110592",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "pullDependencies",
|
||||||
|
Value: "so:libc.musl-x86_64.so.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "pullChecksum",
|
||||||
|
Value: "Q1uss4DfpvL16Nw2YUTwmzGBABz3Y=",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "gitCommitOfApkPort",
|
||||||
|
Value: "d2bfb22c8e8f67ad7d8d02704f35ec4d2a19f9b9",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: cyclonedx.ComponentTypeOS,
|
||||||
|
Name: "debian",
|
||||||
|
Version: "1.2.3",
|
||||||
|
Hashes: nil,
|
||||||
|
Licenses: &cyclonedx.Licenses{
|
||||||
|
{
|
||||||
|
License: &cyclonedx.License{
|
||||||
|
ID: "MIT",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Properties: &[]cyclonedx.Property{
|
||||||
|
{
|
||||||
|
Name: "prettyName",
|
||||||
|
Value: "debian",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "id",
|
||||||
|
Value: "debian",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "versionID",
|
||||||
|
Value: "1.2.3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Components: nil,
|
||||||
|
Evidence: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Dependencies: &[]cyclonedx.Dependency{
|
||||||
|
{
|
||||||
|
Ref: "p1",
|
||||||
|
Dependencies: &[]cyclonedx.Dependency{
|
||||||
|
{
|
||||||
|
Ref: "p2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []expected{
|
||||||
|
{
|
||||||
|
os: "debian",
|
||||||
|
ver: "1.2.3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pkg: "package-1",
|
||||||
|
ver: "1.0.1",
|
||||||
|
cpe: "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*",
|
||||||
|
purl: "pkg:some/package-1@1.0.1?arch=arm64&upstream=upstream1&distro=alpine-1",
|
||||||
|
relation: "package-2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pkg: "package-2",
|
||||||
|
ver: "2.0.2",
|
||||||
|
purl: "pkg:alpine/alpine-baselayout@3.2.0-r16?arch=x86_64&upstream=alpine-baselayout&distro=alpine-3.14.2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
sbom, err := toSyftModel(&test.input)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
test:
|
||||||
|
for _, e := range test.expected {
|
||||||
|
if e.os != "" {
|
||||||
|
assert.Equal(t, e.os, sbom.Artifacts.LinuxDistribution.ID)
|
||||||
|
assert.Equal(t, e.ver, sbom.Artifacts.LinuxDistribution.VersionID)
|
||||||
|
}
|
||||||
|
if e.pkg != "" {
|
||||||
|
for p := range sbom.Artifacts.PackageCatalog.Enumerate() {
|
||||||
|
if e.pkg != p.Name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, e.ver, p.Version)
|
||||||
|
|
||||||
|
if e.cpe != "" {
|
||||||
|
foundCPE := false
|
||||||
|
for _, c := range p.CPEs {
|
||||||
|
cstr := c.BindToFmtString()
|
||||||
|
if e.cpe == cstr {
|
||||||
|
foundCPE = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !foundCPE {
|
||||||
|
assert.Fail(t, fmt.Sprintf("CPE not found in package: %s", e.cpe))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.purl != "" {
|
||||||
|
assert.Equal(t, e.purl, p.PURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.relation != "" {
|
||||||
|
foundRelation := false
|
||||||
|
for _, r := range sbom.Relationships {
|
||||||
|
p := sbom.Artifacts.PackageCatalog.Package(r.To.ID())
|
||||||
|
if e.relation == p.Name {
|
||||||
|
foundRelation = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !foundRelation {
|
||||||
|
assert.Fail(t, fmt.Sprintf("relation not found: %s", e.relation))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue test
|
||||||
|
}
|
||||||
|
assert.Fail(t, fmt.Sprintf("package should be present: %s", e.pkg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,7 +2,7 @@ package cyclonedxhelpers
|
|||||||
|
|
||||||
import "github.com/anchore/syft/syft/pkg"
|
import "github.com/anchore/syft/syft/pkg"
|
||||||
|
|
||||||
func Description(p pkg.Package) string {
|
func encodeDescription(p pkg.Package) string {
|
||||||
if hasMetadata(p) {
|
if hasMetadata(p) {
|
||||||
switch metadata := p.Metadata.(type) {
|
switch metadata := p.Metadata.(type) {
|
||||||
case pkg.ApkMetadata:
|
case pkg.ApkMetadata:
|
||||||
@ -13,3 +13,12 @@ func Description(p pkg.Package) string {
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func decodeDescription(description string, metadata interface{}) {
|
||||||
|
switch meta := metadata.(type) {
|
||||||
|
case *pkg.ApkMetadata:
|
||||||
|
meta.Description = description
|
||||||
|
case *pkg.NpmPackageJSONMetadata:
|
||||||
|
meta.Description = description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_Description(t *testing.T) {
|
func Test_encodeDescription(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
input pkg.Package
|
input pkg.Package
|
||||||
@ -50,7 +50,7 @@ func Test_Description(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
assert.Equal(t, test.expected, Description(test.input))
|
assert.Equal(t, test.expected, encodeDescription(test.input))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,12 +2,13 @@ package cyclonedxhelpers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/CycloneDX/cyclonedx-go"
|
"github.com/CycloneDX/cyclonedx-go"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExternalReferences(p pkg.Package) *[]cyclonedx.ExternalReference {
|
func encodeExternalReferences(p pkg.Package) *[]cyclonedx.ExternalReference {
|
||||||
refs := []cyclonedx.ExternalReference{}
|
refs := []cyclonedx.ExternalReference{}
|
||||||
if hasMetadata(p) {
|
if hasMetadata(p) {
|
||||||
switch metadata := p.Metadata.(type) {
|
switch metadata := p.Metadata.(type) {
|
||||||
@ -63,3 +64,51 @@ func ExternalReferences(p pkg.Package) *[]cyclonedx.ExternalReference {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func decodeExternalReferences(c *cyclonedx.Component, metadata interface{}) {
|
||||||
|
if c.ExternalReferences == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch meta := metadata.(type) {
|
||||||
|
case *pkg.ApkMetadata:
|
||||||
|
meta.URL = refURL(c, cyclonedx.ERTypeDistribution)
|
||||||
|
case *pkg.CargoPackageMetadata:
|
||||||
|
meta.Source = refURL(c, cyclonedx.ERTypeDistribution)
|
||||||
|
case *pkg.NpmPackageJSONMetadata:
|
||||||
|
meta.URL = refURL(c, cyclonedx.ERTypeDistribution)
|
||||||
|
meta.Homepage = refURL(c, cyclonedx.ERTypeWebsite)
|
||||||
|
case *pkg.GemMetadata:
|
||||||
|
meta.Homepage = refURL(c, cyclonedx.ERTypeWebsite)
|
||||||
|
case *pkg.PythonPackageMetadata:
|
||||||
|
if meta.DirectURLOrigin == nil {
|
||||||
|
meta.DirectURLOrigin = &pkg.PythonDirectURLOriginInfo{}
|
||||||
|
}
|
||||||
|
meta.DirectURLOrigin.URL = refURL(c, cyclonedx.ERTypeVCS)
|
||||||
|
meta.DirectURLOrigin.CommitID = strings.TrimPrefix(refComment(c, cyclonedx.ERTypeVCS), "commit: ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func findExternalRef(c *cyclonedx.Component, typ cyclonedx.ExternalReferenceType) *cyclonedx.ExternalReference {
|
||||||
|
if c.ExternalReferences != nil {
|
||||||
|
for _, r := range *c.ExternalReferences {
|
||||||
|
if r.Type == typ {
|
||||||
|
return &r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func refURL(c *cyclonedx.Component, typ cyclonedx.ExternalReferenceType) string {
|
||||||
|
if r := findExternalRef(c, typ); r != nil {
|
||||||
|
return r.URL
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func refComment(c *cyclonedx.Component, typ cyclonedx.ExternalReferenceType) string {
|
||||||
|
if r := findExternalRef(c, typ); r != nil {
|
||||||
|
return r.Comment
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_ExternalReferences(t *testing.T) {
|
func Test_encodeExternalReferences(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
input pkg.Package
|
input pkg.Package
|
||||||
@ -127,7 +127,7 @@ func Test_ExternalReferences(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
assert.Equal(t, test.expected, ExternalReferences(test.input))
|
assert.Equal(t, test.expected, encodeExternalReferences(test.input))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,7 +27,7 @@ func ToFormatModel(s sbom.SBOM) *cyclonedx.BOM {
|
|||||||
packages := s.Artifacts.PackageCatalog.Sorted()
|
packages := s.Artifacts.PackageCatalog.Sorted()
|
||||||
components := make([]cyclonedx.Component, len(packages))
|
components := make([]cyclonedx.Component, len(packages))
|
||||||
for i, p := range packages {
|
for i, p := range packages {
|
||||||
components[i] = Component(p)
|
components[i] = encodeComponent(p)
|
||||||
}
|
}
|
||||||
components = append(components, toOSComponent(s.Artifacts.LinuxDistribution)...)
|
components = append(components, toOSComponent(s.Artifacts.LinuxDistribution)...)
|
||||||
cdxBOM.Components = &components
|
cdxBOM.Components = &components
|
||||||
@ -80,9 +80,17 @@ func toOSComponent(distro *linux.Release) []cyclonedx.Component {
|
|||||||
}
|
}
|
||||||
return []cyclonedx.Component{
|
return []cyclonedx.Component{
|
||||||
{
|
{
|
||||||
Type: cyclonedx.ComponentTypeOS,
|
Type: cyclonedx.ComponentTypeOS,
|
||||||
Name: distro.Name,
|
// FIXME is it idiomatic to be using SWID here for specific name and version information?
|
||||||
Version: distro.Version,
|
SWID: &cyclonedx.SWID{
|
||||||
|
TagID: distro.ID,
|
||||||
|
Name: distro.ID,
|
||||||
|
Version: distro.VersionID,
|
||||||
|
},
|
||||||
|
Description: distro.PrettyName,
|
||||||
|
Name: distro.ID,
|
||||||
|
Version: distro.VersionID,
|
||||||
|
// TODO should we add a PURL?
|
||||||
CPE: distro.CPEName,
|
CPE: distro.CPEName,
|
||||||
ExternalReferences: eRefs,
|
ExternalReferences: eRefs,
|
||||||
Properties: props,
|
Properties: props,
|
||||||
|
|||||||
@ -2,7 +2,7 @@ package cyclonedxhelpers
|
|||||||
|
|
||||||
import "github.com/anchore/syft/syft/pkg"
|
import "github.com/anchore/syft/syft/pkg"
|
||||||
|
|
||||||
func Group(p pkg.Package) string {
|
func encodeGroup(p pkg.Package) string {
|
||||||
if hasMetadata(p) {
|
if hasMetadata(p) {
|
||||||
if metadata, ok := p.Metadata.(pkg.JavaMetadata); ok && metadata.PomProperties != nil {
|
if metadata, ok := p.Metadata.(pkg.JavaMetadata); ok && metadata.PomProperties != nil {
|
||||||
return metadata.PomProperties.GroupID
|
return metadata.PomProperties.GroupID
|
||||||
@ -10,3 +10,12 @@ func Group(p pkg.Package) string {
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func decodeGroup(group string, metadata interface{}) {
|
||||||
|
if meta, ok := metadata.(*pkg.JavaMetadata); ok {
|
||||||
|
if meta.PomProperties == nil {
|
||||||
|
meta.PomProperties = &pkg.PomProperties{}
|
||||||
|
}
|
||||||
|
meta.PomProperties.GroupID = group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGroup(t *testing.T) {
|
func Test_encodeGroup(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
input pkg.Package
|
input pkg.Package
|
||||||
@ -46,7 +46,7 @@ func TestGroup(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
assert.Equal(t, test.expected, Group(test.input))
|
assert.Equal(t, test.expected, encodeGroup(test.input))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import (
|
|||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Licenses(p pkg.Package) *cyclonedx.Licenses {
|
func encodeLicenses(p pkg.Package) *cyclonedx.Licenses {
|
||||||
lc := cyclonedx.Licenses{}
|
lc := cyclonedx.Licenses{}
|
||||||
for _, licenseName := range p.Licenses {
|
for _, licenseName := range p.Licenses {
|
||||||
if value, exists := spdxlicense.ID(licenseName); exists {
|
if value, exists := spdxlicense.ID(licenseName); exists {
|
||||||
@ -22,3 +22,12 @@ func Licenses(p pkg.Package) *cyclonedx.Licenses {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func decodeLicenses(c *cyclonedx.Component) (out []string) {
|
||||||
|
if c.Licenses != nil {
|
||||||
|
for _, l := range *c.Licenses {
|
||||||
|
out = append(out, l.License.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_License(t *testing.T) {
|
func Test_encodeLicense(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
input pkg.Package
|
input pkg.Package
|
||||||
@ -77,7 +77,7 @@ func Test_License(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
assert.Equal(t, test.expected, Licenses(test.input))
|
assert.Equal(t, test.expected, encodeLicenses(test.input))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import (
|
|||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Properties(p pkg.Package) *[]cyclonedx.Property {
|
func encodeProperties(p pkg.Package) *[]cyclonedx.Property {
|
||||||
props := []cyclonedx.Property{}
|
props := []cyclonedx.Property{}
|
||||||
props = append(props, *getCycloneDXProperties(p)...)
|
props = append(props, *getCycloneDXProperties(p)...)
|
||||||
if len(p.Locations) > 0 {
|
if len(p.Locations) > 0 {
|
||||||
@ -76,3 +76,12 @@ func getCycloneDXPropertyValue(field reflect.Value) interface{} {
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findPropertyValue(c *cyclonedx.Component, name string) string {
|
||||||
|
for _, p := range *c.Properties {
|
||||||
|
if p.Name == name {
|
||||||
|
return p.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_Properties(t *testing.T) {
|
func Test_encodeProperties(t *testing.T) {
|
||||||
epoch := 2
|
epoch := 2
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -132,7 +132,7 @@ func Test_Properties(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
assert.Equal(t, test.expected, Properties(test.input))
|
assert.Equal(t, test.expected, encodeProperties(test.input))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import (
|
|||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Publisher(p pkg.Package) string {
|
func encodePublisher(p pkg.Package) string {
|
||||||
if hasMetadata(p) {
|
if hasMetadata(p) {
|
||||||
switch metadata := p.Metadata.(type) {
|
switch metadata := p.Metadata.(type) {
|
||||||
case pkg.ApkMetadata:
|
case pkg.ApkMetadata:
|
||||||
@ -17,3 +17,14 @@ func Publisher(p pkg.Package) string {
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func decodePublisher(publisher string, metadata interface{}) {
|
||||||
|
switch meta := metadata.(type) {
|
||||||
|
case *pkg.ApkMetadata:
|
||||||
|
meta.Maintainer = publisher
|
||||||
|
case *pkg.RpmdbMetadata:
|
||||||
|
meta.Vendor = publisher
|
||||||
|
case *pkg.DpkgMetadata:
|
||||||
|
meta.Maintainer = publisher
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_Publisher(t *testing.T) {
|
func Test_encodePublisher(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
input pkg.Package
|
input pkg.Package
|
||||||
@ -59,7 +59,7 @@ func Test_Publisher(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
assert.Equal(t, test.expected, Publisher(test.input))
|
assert.Equal(t, test.expected, encodePublisher(test.input))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
70
internal/formats/cyclonedx13json/decoder_test.go
Normal file
70
internal/formats/cyclonedx13json/decoder_test.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package cyclonedx13json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_decodeJSON(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
file string
|
||||||
|
err bool
|
||||||
|
distro string
|
||||||
|
packages []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
file: "snapshot/TestCycloneDxDirectoryEncoder.golden",
|
||||||
|
distro: "debian:1.2.3",
|
||||||
|
packages: []string{"package-1:1.0.1", "package-2:2.0.1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: "snapshot/TestCycloneDxImageEncoder.golden",
|
||||||
|
distro: "debian:1.2.3",
|
||||||
|
packages: []string{"package-1:1.0.1", "package-2:2.0.1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: "image-simple/Dockerfile",
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.file, func(t *testing.T) {
|
||||||
|
reader, err := os.Open("test-fixtures/" + test.file)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
if test.err {
|
||||||
|
err = Format().Validate(reader)
|
||||||
|
assert.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bom, err := Format().Decode(reader)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
split := strings.SplitN(test.distro, ":", 2)
|
||||||
|
name := split[0]
|
||||||
|
version := split[1]
|
||||||
|
assert.Equal(t, bom.Artifacts.LinuxDistribution.ID, name)
|
||||||
|
assert.Equal(t, bom.Artifacts.LinuxDistribution.Version, version)
|
||||||
|
|
||||||
|
pkgs:
|
||||||
|
for _, pkg := range test.packages {
|
||||||
|
split = strings.SplitN(pkg, ":", 2)
|
||||||
|
name = split[0]
|
||||||
|
version = split[1]
|
||||||
|
for p := range bom.Artifacts.PackageCatalog.Enumerate() {
|
||||||
|
if p.Name == name {
|
||||||
|
assert.Equal(t, version, p.Version)
|
||||||
|
continue pkgs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.Fail(t, fmt.Sprintf("package should be present: %s", pkg))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,12 +1,16 @@
|
|||||||
package cyclonedx13json
|
package cyclonedx13json
|
||||||
|
|
||||||
import "github.com/anchore/syft/syft/format"
|
import (
|
||||||
|
"github.com/CycloneDX/cyclonedx-go"
|
||||||
|
"github.com/anchore/syft/internal/formats/common/cyclonedxhelpers"
|
||||||
|
"github.com/anchore/syft/syft/format"
|
||||||
|
)
|
||||||
|
|
||||||
func Format() format.Format {
|
func Format() format.Format {
|
||||||
return format.NewFormat(
|
return format.NewFormat(
|
||||||
format.CycloneDxJSONOption,
|
format.CycloneDxJSONOption,
|
||||||
encoder,
|
encoder,
|
||||||
nil,
|
cyclonedxhelpers.GetDecoder(cyclonedx.BOMFileFormatJSON),
|
||||||
nil,
|
cyclonedxhelpers.GetValidator(cyclonedx.BOMFileFormatJSON),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"bomFormat": "CycloneDX",
|
"bomFormat": "CycloneDX",
|
||||||
"specVersion": "1.3",
|
"specVersion": "1.3",
|
||||||
"serialNumber": "urn:uuid:258d2616-5b1f-48cd-82a3-d6c95e262950",
|
"serialNumber": "urn:uuid:326afa86-5620-4a80-8f2b-7f283b954b9b",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"timestamp": "2022-01-14T22:47:00Z",
|
"timestamp": "2022-02-10T17:19:38-05:00",
|
||||||
"tools": [
|
"tools": [
|
||||||
{
|
{
|
||||||
"vendor": "anchore",
|
"vendor": "anchore",
|
||||||
@ -13,6 +13,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"component": {
|
"component": {
|
||||||
|
"bom-ref": "163686ac6e30c752",
|
||||||
"type": "file",
|
"type": "file",
|
||||||
"name": "/some/path",
|
"name": "/some/path",
|
||||||
"version": ""
|
"version": ""
|
||||||
@ -84,6 +85,12 @@
|
|||||||
"type": "operating-system",
|
"type": "operating-system",
|
||||||
"name": "debian",
|
"name": "debian",
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
|
"description": "debian",
|
||||||
|
"swid": {
|
||||||
|
"tagId": "debian",
|
||||||
|
"name": "debian",
|
||||||
|
"version": "1.2.3"
|
||||||
|
},
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "prettyName",
|
"name": "prettyName",
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"bomFormat": "CycloneDX",
|
"bomFormat": "CycloneDX",
|
||||||
"specVersion": "1.3",
|
"specVersion": "1.3",
|
||||||
"serialNumber": "urn:uuid:8a84b1cf-e918-4842-a6a8-c7fdafc55bc0",
|
"serialNumber": "urn:uuid:761a2036-0f25-4787-bf28-f5e9a7d9a0bf",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"timestamp": "2022-01-14T22:47:00Z",
|
"timestamp": "2022-02-10T17:19:38-05:00",
|
||||||
"tools": [
|
"tools": [
|
||||||
{
|
{
|
||||||
"vendor": "anchore",
|
"vendor": "anchore",
|
||||||
@ -13,6 +13,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"component": {
|
"component": {
|
||||||
|
"bom-ref": "4f9453fd20e0cf80",
|
||||||
"type": "container",
|
"type": "container",
|
||||||
"name": "user-image-input",
|
"name": "user-image-input",
|
||||||
"version": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368"
|
"version": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368"
|
||||||
@ -55,7 +56,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "layerID",
|
"name": "layerID",
|
||||||
"value": "sha256:16e64541f2ddf59a90391ce7bb8af90313f7d373f2105d88f3d3267b72e0ebab"
|
"value": "sha256:41e7295da66c405eb3a4df29188dcf80f622f9304d487033a86d4a22e3f01abe"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -84,7 +85,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "layerID",
|
"name": "layerID",
|
||||||
"value": "sha256:de6c235f76ea24c8503ec08891445b5d6a8bdf8249117ed8d8b0b6fb3ebe4f67"
|
"value": "sha256:68a2c166dcb3acf6b7303e995ca1fe7d794bd3b5852a0b4048f9c96b796086aa"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -92,6 +93,12 @@
|
|||||||
"type": "operating-system",
|
"type": "operating-system",
|
||||||
"name": "debian",
|
"name": "debian",
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
|
"description": "debian",
|
||||||
|
"swid": {
|
||||||
|
"tagId": "debian",
|
||||||
|
"name": "debian",
|
||||||
|
"version": "1.2.3"
|
||||||
|
},
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "prettyName",
|
"name": "prettyName",
|
||||||
|
|||||||
Binary file not shown.
70
internal/formats/cyclonedx13xml/decoder_test.go
Normal file
70
internal/formats/cyclonedx13xml/decoder_test.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package cyclonedx13xml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_decodeXML(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
file string
|
||||||
|
err bool
|
||||||
|
distro string
|
||||||
|
packages []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
file: "snapshot/TestCycloneDxDirectoryEncoder.golden",
|
||||||
|
distro: "debian:1.2.3",
|
||||||
|
packages: []string{"package-1:1.0.1", "package-2:2.0.1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: "snapshot/TestCycloneDxImageEncoder.golden",
|
||||||
|
distro: "debian:1.2.3",
|
||||||
|
packages: []string{"package-1:1.0.1", "package-2:2.0.1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: "image-simple/Dockerfile",
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.file, func(t *testing.T) {
|
||||||
|
reader, err := os.Open("test-fixtures/" + test.file)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
if test.err {
|
||||||
|
err = Format().Validate(reader)
|
||||||
|
assert.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bom, err := Format().Decode(reader)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
split := strings.SplitN(test.distro, ":", 2)
|
||||||
|
name := split[0]
|
||||||
|
version := split[1]
|
||||||
|
assert.Equal(t, bom.Artifacts.LinuxDistribution.ID, name)
|
||||||
|
assert.Equal(t, bom.Artifacts.LinuxDistribution.Version, version)
|
||||||
|
|
||||||
|
pkgs:
|
||||||
|
for _, pkg := range test.packages {
|
||||||
|
split = strings.SplitN(pkg, ":", 2)
|
||||||
|
name = split[0]
|
||||||
|
version = split[1]
|
||||||
|
for p := range bom.Artifacts.PackageCatalog.Enumerate() {
|
||||||
|
if p.Name == name {
|
||||||
|
assert.Equal(t, version, p.Version)
|
||||||
|
continue pkgs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.Fail(t, fmt.Sprintf("package should be present: %s", pkg))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,12 +1,16 @@
|
|||||||
package cyclonedx13xml
|
package cyclonedx13xml
|
||||||
|
|
||||||
import "github.com/anchore/syft/syft/format"
|
import (
|
||||||
|
"github.com/CycloneDX/cyclonedx-go"
|
||||||
|
"github.com/anchore/syft/internal/formats/common/cyclonedxhelpers"
|
||||||
|
"github.com/anchore/syft/syft/format"
|
||||||
|
)
|
||||||
|
|
||||||
func Format() format.Format {
|
func Format() format.Format {
|
||||||
return format.NewFormat(
|
return format.NewFormat(
|
||||||
format.CycloneDxXMLOption,
|
format.CycloneDxXMLOption,
|
||||||
encoder,
|
encoder,
|
||||||
nil,
|
cyclonedxhelpers.GetDecoder(cyclonedx.BOMFileFormatXML),
|
||||||
nil,
|
cyclonedxhelpers.GetValidator(cyclonedx.BOMFileFormatXML),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.3" serialNumber="urn:uuid:16a426e7-fcc7-4b94-abd2-66c67569cc44" version="1">
|
<bom xmlns="http://cyclonedx.org/schema/bom/1.3" serialNumber="urn:uuid:25133adc-01af-46ad-a198-002034e54097" version="1">
|
||||||
<metadata>
|
<metadata>
|
||||||
<timestamp>2022-01-14T22:46:49Z</timestamp>
|
<timestamp>2022-02-10T17:18:31-05:00</timestamp>
|
||||||
<tools>
|
<tools>
|
||||||
<tool>
|
<tool>
|
||||||
<vendor>anchore</vendor>
|
<vendor>anchore</vendor>
|
||||||
@ -9,7 +9,7 @@
|
|||||||
<version>[not provided]</version>
|
<version>[not provided]</version>
|
||||||
</tool>
|
</tool>
|
||||||
</tools>
|
</tools>
|
||||||
<component type="file">
|
<component bom-ref="163686ac6e30c752" type="file">
|
||||||
<name>/some/path</name>
|
<name>/some/path</name>
|
||||||
<version></version>
|
<version></version>
|
||||||
</component>
|
</component>
|
||||||
@ -48,6 +48,8 @@
|
|||||||
<component type="operating-system">
|
<component type="operating-system">
|
||||||
<name>debian</name>
|
<name>debian</name>
|
||||||
<version>1.2.3</version>
|
<version>1.2.3</version>
|
||||||
|
<description>debian</description>
|
||||||
|
<swid tagId="debian" name="debian" version="1.2.3"></swid>
|
||||||
<properties>
|
<properties>
|
||||||
<property name="prettyName">debian</property>
|
<property name="prettyName">debian</property>
|
||||||
<property name="id">debian</property>
|
<property name="id">debian</property>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.3" serialNumber="urn:uuid:a3d776c6-a2ca-4116-8b99-b3538dd1a460" version="1">
|
<bom xmlns="http://cyclonedx.org/schema/bom/1.3" serialNumber="urn:uuid:e702d78f-9372-4458-ac38-be8e80fc3005" version="1">
|
||||||
<metadata>
|
<metadata>
|
||||||
<timestamp>2022-01-14T22:46:49Z</timestamp>
|
<timestamp>2022-02-10T17:18:31-05:00</timestamp>
|
||||||
<tools>
|
<tools>
|
||||||
<tool>
|
<tool>
|
||||||
<vendor>anchore</vendor>
|
<vendor>anchore</vendor>
|
||||||
@ -9,7 +9,7 @@
|
|||||||
<version>[not provided]</version>
|
<version>[not provided]</version>
|
||||||
</tool>
|
</tool>
|
||||||
</tools>
|
</tools>
|
||||||
<component type="container">
|
<component bom-ref="4f9453fd20e0cf80" type="container">
|
||||||
<name>user-image-input</name>
|
<name>user-image-input</name>
|
||||||
<version>sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368</version>
|
<version>sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368</version>
|
||||||
</component>
|
</component>
|
||||||
@ -31,7 +31,7 @@
|
|||||||
<property name="type">python</property>
|
<property name="type">python</property>
|
||||||
<property name="metadataType">PythonPackageMetadata</property>
|
<property name="metadataType">PythonPackageMetadata</property>
|
||||||
<property name="path">/somefile-1.txt</property>
|
<property name="path">/somefile-1.txt</property>
|
||||||
<property name="layerID">sha256:16e64541f2ddf59a90391ce7bb8af90313f7d373f2105d88f3d3267b72e0ebab</property>
|
<property name="layerID">sha256:41e7295da66c405eb3a4df29188dcf80f622f9304d487033a86d4a22e3f01abe</property>
|
||||||
</properties>
|
</properties>
|
||||||
</component>
|
</component>
|
||||||
<component type="library">
|
<component type="library">
|
||||||
@ -44,12 +44,14 @@
|
|||||||
<property name="type">deb</property>
|
<property name="type">deb</property>
|
||||||
<property name="metadataType">DpkgMetadata</property>
|
<property name="metadataType">DpkgMetadata</property>
|
||||||
<property name="path">/somefile-2.txt</property>
|
<property name="path">/somefile-2.txt</property>
|
||||||
<property name="layerID">sha256:de6c235f76ea24c8503ec08891445b5d6a8bdf8249117ed8d8b0b6fb3ebe4f67</property>
|
<property name="layerID">sha256:68a2c166dcb3acf6b7303e995ca1fe7d794bd3b5852a0b4048f9c96b796086aa</property>
|
||||||
</properties>
|
</properties>
|
||||||
</component>
|
</component>
|
||||||
<component type="operating-system">
|
<component type="operating-system">
|
||||||
<name>debian</name>
|
<name>debian</name>
|
||||||
<version>1.2.3</version>
|
<version>1.2.3</version>
|
||||||
|
<description>debian</description>
|
||||||
|
<swid tagId="debian" name="debian" version="1.2.3"></swid>
|
||||||
<properties>
|
<properties>
|
||||||
<property name="prettyName">debian</property>
|
<property name="prettyName">debian</property>
|
||||||
<property name="id">debian</property>
|
<property name="id">debian</property>
|
||||||
|
|||||||
Binary file not shown.
@ -1,14 +1,12 @@
|
|||||||
package rust
|
package pkg
|
||||||
|
|
||||||
import "github.com/anchore/syft/syft/pkg"
|
|
||||||
|
|
||||||
type CargoMetadata struct {
|
type CargoMetadata struct {
|
||||||
Packages []pkg.CargoPackageMetadata `toml:"package"`
|
Packages []CargoPackageMetadata `toml:"package"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pkgs returns all of the packages referenced within the Cargo.lock metadata.
|
// Pkgs returns all of the packages referenced within the Cargo.lock metadata.
|
||||||
func (m CargoMetadata) Pkgs() []*pkg.Package {
|
func (m CargoMetadata) Pkgs() []*Package {
|
||||||
pkgs := make([]*pkg.Package, 0)
|
pkgs := make([]*Package, 0)
|
||||||
|
|
||||||
for _, p := range m.Packages {
|
for _, p := range m.Packages {
|
||||||
if p.Dependencies == nil {
|
if p.Dependencies == nil {
|
||||||
@ -20,7 +20,7 @@ func parseCargoLock(_ string, reader io.Reader) ([]*pkg.Package, []artifact.Rela
|
|||||||
return nil, nil, fmt.Errorf("unable to load Cargo.lock for parsing: %v", err)
|
return nil, nil, fmt.Errorf("unable to load Cargo.lock for parsing: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata := CargoMetadata{}
|
metadata := pkg.CargoMetadata{}
|
||||||
err = tree.Unmarshal(&metadata)
|
err = tree.Unmarshal(&metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("unable to parse Cargo.lock: %v", err)
|
return nil, nil, fmt.Errorf("unable to parse Cargo.lock: %v", err)
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
package pkg
|
package pkg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
// MetadataType represents the data shape stored within pkg.Package.Metadata.
|
// MetadataType represents the data shape stored within pkg.Package.Metadata.
|
||||||
type MetadataType string
|
type MetadataType string
|
||||||
|
|
||||||
@ -33,3 +37,17 @@ var AllMetadataTypes = []MetadataType{
|
|||||||
GolangBinMetadataType,
|
GolangBinMetadataType,
|
||||||
PhpComposerJSONMetadataType,
|
PhpComposerJSONMetadataType,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var MetadataTypeByName = map[MetadataType]reflect.Type{
|
||||||
|
ApkMetadataType: reflect.TypeOf(ApkMetadata{}),
|
||||||
|
DpkgMetadataType: reflect.TypeOf(DpkgMetadata{}),
|
||||||
|
GemMetadataType: reflect.TypeOf(GemMetadata{}),
|
||||||
|
JavaMetadataType: reflect.TypeOf(JavaMetadata{}),
|
||||||
|
NpmPackageJSONMetadataType: reflect.TypeOf(NpmPackageJSONMetadata{}),
|
||||||
|
RpmdbMetadataType: reflect.TypeOf(RpmdbMetadata{}),
|
||||||
|
PythonPackageMetadataType: reflect.TypeOf(PythonPackageMetadata{}),
|
||||||
|
RustCargoPackageMetadataType: reflect.TypeOf(CargoMetadata{}),
|
||||||
|
KbPackageMetadataType: reflect.TypeOf(KbPackageMetadata{}),
|
||||||
|
GolangBinMetadataType: reflect.TypeOf(GolangBinMetadata{}),
|
||||||
|
PhpComposerJSONMetadataType: reflect.TypeOf(PhpComposerJSONMetadata{}),
|
||||||
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package integration
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/anchore/syft/syft"
|
"github.com/anchore/syft/syft"
|
||||||
@ -20,11 +21,27 @@ import (
|
|||||||
// encode-decode-encode loop which will detect lossy behavior in both directions.
|
// encode-decode-encode loop which will detect lossy behavior in both directions.
|
||||||
func TestEncodeDecodeEncodeCycleComparison(t *testing.T) {
|
func TestEncodeDecodeEncodeCycleComparison(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
format format.Option
|
format format.Option
|
||||||
|
redactor func(in []byte) []byte
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
format: format.JSONOption,
|
format: format.JSONOption,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
format: format.CycloneDxJSONOption,
|
||||||
|
redactor: func(in []byte) []byte {
|
||||||
|
in = regexp.MustCompile("\"(timestamp|serialNumber|bom-ref)\": \"[^\"]+").ReplaceAll(in, []byte{})
|
||||||
|
return in
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format: format.CycloneDxXMLOption,
|
||||||
|
redactor: func(in []byte) []byte {
|
||||||
|
in = regexp.MustCompile("(serialNumber|bom-ref)=\"[^\"]+").ReplaceAll(in, []byte{})
|
||||||
|
in = regexp.MustCompile("<timestamp>[^<]+</timestamp>").ReplaceAll(in, []byte{})
|
||||||
|
return in
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(string(test.format), func(t *testing.T) {
|
t.Run(string(test.format), func(t *testing.T) {
|
||||||
@ -33,6 +50,7 @@ func TestEncodeDecodeEncodeCycleComparison(t *testing.T) {
|
|||||||
|
|
||||||
by1, err := syft.Encode(originalSBOM, test.format)
|
by1, err := syft.Encode(originalSBOM, test.format)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
newSBOM, newFormat, err := syft.Decode(bytes.NewReader(by1))
|
newSBOM, newFormat, err := syft.Decode(bytes.NewReader(by1))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, test.format, newFormat)
|
assert.Equal(t, test.format, newFormat)
|
||||||
@ -40,6 +58,11 @@ func TestEncodeDecodeEncodeCycleComparison(t *testing.T) {
|
|||||||
by2, err := syft.Encode(*newSBOM, test.format)
|
by2, err := syft.Encode(*newSBOM, test.format)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
if test.redactor != nil {
|
||||||
|
by1 = test.redactor(by1)
|
||||||
|
by2 = test.redactor(by2)
|
||||||
|
}
|
||||||
|
|
||||||
if !assert.True(t, bytes.Equal(by1, by2)) {
|
if !assert.True(t, bytes.Equal(by1, by2)) {
|
||||||
dmp := diffmatchpatch.New()
|
dmp := diffmatchpatch.New()
|
||||||
diffs := dmp.DiffMain(string(by1), string(by2), true)
|
diffs := dmp.DiffMain(string(by1), string(by2), true)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user