mirror of
https://github.com/anchore/syft.git
synced 2026-11-01 23:16:30 +01:00
Add SPDX support (#445)
* add initial spdx support Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * expose FileOwner and use in SPDX presenter Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * add initial json support for SPDX Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * add remaining package fields Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * add spdx license list generation + tests Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * keep fileOwner unexported from pkg Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * restore cli test util Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * add external refs to spdx tag-value format Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * add golang support to CPE generation Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * use tag-value format as default "spdx" format flavor Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * add tests around spdx presenters + refactor presenter tests Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * add bouncer exception for spdx tools-golang repo Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * remove spdx model questions Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
@@ -1,169 +1,34 @@
|
||||
package packages
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/filetree"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
|
||||
"github.com/anchore/go-testutils"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
)
|
||||
|
||||
var update = flag.Bool("update", false, "update the *.golden files for json presenters")
|
||||
|
||||
func TestCycloneDxDirsPresenter(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
catalog := pkg.NewCatalog()
|
||||
|
||||
// populate catalog with test data
|
||||
catalog.Add(pkg.Package{
|
||||
Name: "package1",
|
||||
Version: "1.0.1",
|
||||
Type: pkg.DebPkg,
|
||||
FoundBy: "the-cataloger-1",
|
||||
Locations: []source.Location{
|
||||
{RealPath: "/some/path/pkg1"},
|
||||
},
|
||||
Metadata: pkg.DpkgMetadata{
|
||||
Package: "package1",
|
||||
Version: "1.0.1",
|
||||
Architecture: "amd64",
|
||||
},
|
||||
})
|
||||
catalog.Add(pkg.Package{
|
||||
Name: "package2",
|
||||
Version: "2.0.1",
|
||||
Type: pkg.DebPkg,
|
||||
FoundBy: "the-cataloger-2",
|
||||
Locations: []source.Location{
|
||||
{RealPath: "/some/path/pkg1"},
|
||||
},
|
||||
Licenses: []string{
|
||||
"MIT",
|
||||
"Apache-v2",
|
||||
},
|
||||
Metadata: pkg.DpkgMetadata{
|
||||
Package: "package2",
|
||||
Version: "1.0.2",
|
||||
Architecture: "amd64",
|
||||
},
|
||||
})
|
||||
|
||||
s, err := source.NewFromDirectory("/some/path")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pres := NewCycloneDxPresenter(catalog, s.Metadata)
|
||||
|
||||
// run presenter
|
||||
err = pres.Present(&buffer)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual := buffer.Bytes()
|
||||
|
||||
if *update {
|
||||
testutils.UpdateGoldenFileContents(t, actual)
|
||||
}
|
||||
|
||||
var expected = testutils.GetGoldenFileContents(t)
|
||||
|
||||
// remove dynamic values, which are tested independently
|
||||
actual = redact(actual)
|
||||
expected = redact(expected)
|
||||
|
||||
if !bytes.Equal(expected, actual) {
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(string(actual), string(expected), true)
|
||||
t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs))
|
||||
}
|
||||
var updateCycloneDx = flag.Bool("update-cyclonedx", false, "update the *.golden files for cyclone-dx presenters")
|
||||
|
||||
func TestCycloneDxDirectoryPresenter(t *testing.T) {
|
||||
catalog, metadata, _ := presenterDirectoryInput(t)
|
||||
assertPresenterAgainstGoldenSnapshot(t,
|
||||
NewCycloneDxPresenter(catalog, metadata),
|
||||
*updateCycloneDx,
|
||||
cycloneDxRedactor,
|
||||
)
|
||||
}
|
||||
|
||||
func TestCycloneDxImgsPresenter(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
catalog := pkg.NewCatalog()
|
||||
img := imagetest.GetFixtureImage(t, "docker-archive", "image-simple")
|
||||
|
||||
_, ref1, _ := img.SquashedTree().File("/somefile-1.txt", filetree.FollowBasenameLinks)
|
||||
_, ref2, _ := img.SquashedTree().File("/somefile-2.txt", filetree.FollowBasenameLinks)
|
||||
|
||||
// populate catalog with test data
|
||||
catalog.Add(pkg.Package{
|
||||
Name: "package1",
|
||||
Version: "1.0.1",
|
||||
Locations: []source.Location{
|
||||
source.NewLocationFromImage(string(ref1.RealPath), *ref1, img),
|
||||
},
|
||||
Type: pkg.RpmPkg,
|
||||
FoundBy: "the-cataloger-1",
|
||||
PURL: "the-purl-1",
|
||||
})
|
||||
catalog.Add(pkg.Package{
|
||||
Name: "package2",
|
||||
Version: "2.0.1",
|
||||
Locations: []source.Location{
|
||||
source.NewLocationFromImage(string(ref2.RealPath), *ref2, img),
|
||||
},
|
||||
Type: pkg.RpmPkg,
|
||||
FoundBy: "the-cataloger-2",
|
||||
Licenses: []string{
|
||||
"MIT",
|
||||
"Apache-v2",
|
||||
},
|
||||
PURL: "the-purl-2",
|
||||
})
|
||||
|
||||
s, err := source.NewFromImage(img, "user-image-input")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// This accounts for the non-deterministic digest value that we end up with when
|
||||
// we build a container image dynamically during testing. Ultimately, we should
|
||||
// use a golden image as a test fixture in place of building this image during
|
||||
// testing. At that time, this line will no longer be necessary.
|
||||
//
|
||||
// This value is sourced from the "version" node in "./test-fixtures/snapshot/TestCycloneDxImgsPresenter.golden"
|
||||
s.Metadata.ImageMetadata.ManifestDigest = "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368"
|
||||
|
||||
pres := NewCycloneDxPresenter(catalog, s.Metadata)
|
||||
|
||||
// run presenter
|
||||
err = pres.Present(&buffer)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual := buffer.Bytes()
|
||||
|
||||
if *update {
|
||||
testutils.UpdateGoldenFileContents(t, actual)
|
||||
}
|
||||
|
||||
var expected = testutils.GetGoldenFileContents(t)
|
||||
|
||||
// remove dynamic values, which are tested independently
|
||||
actual = redact(actual)
|
||||
expected = redact(expected)
|
||||
|
||||
if !bytes.Equal(expected, actual) {
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(string(expected), string(actual), true)
|
||||
t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs))
|
||||
}
|
||||
func TestCycloneDxImagePresenter(t *testing.T) {
|
||||
testImage := "image-simple"
|
||||
catalog, metadata, _ := presenterImageInput(t, testImage)
|
||||
assertPresenterAgainstGoldenImageSnapshot(t,
|
||||
NewCycloneDxPresenter(catalog, metadata),
|
||||
testImage,
|
||||
*updateCycloneDx,
|
||||
cycloneDxRedactor,
|
||||
)
|
||||
}
|
||||
|
||||
func redact(s []byte) []byte {
|
||||
func cycloneDxRedactor(s []byte) []byte {
|
||||
serialPattern := regexp.MustCompile(`serialNumber="[a-zA-Z0-9\-:]+"`)
|
||||
rfc3339Pattern := regexp.MustCompile(`([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([Zz])|([\+|\-]([01][0-9]|2[0-3]):[0-5][0-9]))`)
|
||||
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
package packages
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/filetree"
|
||||
|
||||
"github.com/anchore/go-testutils"
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
)
|
||||
|
||||
var updateJSONGoldenFiles = flag.Bool("update-json", false, "update the *.golden files for json presenters")
|
||||
@@ -24,160 +17,21 @@ func must(c pkg.CPE, e error) pkg.CPE {
|
||||
return c
|
||||
}
|
||||
|
||||
func TestJSONDirsPresenter(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
catalog := pkg.NewCatalog()
|
||||
|
||||
// populate catalog with test data
|
||||
catalog.Add(pkg.Package{
|
||||
ID: "package-1-id",
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
Type: pkg.PythonPkg,
|
||||
FoundBy: "the-cataloger-1",
|
||||
Locations: []source.Location{
|
||||
{RealPath: "/some/path/pkg1"},
|
||||
},
|
||||
Language: pkg.Python,
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
Licenses: []string{"MIT"},
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
},
|
||||
PURL: "a-purl-2",
|
||||
CPEs: []pkg.CPE{
|
||||
must(pkg.NewCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*")),
|
||||
},
|
||||
})
|
||||
catalog.Add(pkg.Package{
|
||||
ID: "package-2-id",
|
||||
Name: "package-2",
|
||||
Version: "2.0.1",
|
||||
Type: pkg.DebPkg,
|
||||
FoundBy: "the-cataloger-2",
|
||||
Locations: []source.Location{
|
||||
{RealPath: "/some/path/pkg1"},
|
||||
},
|
||||
MetadataType: pkg.DpkgMetadataType,
|
||||
Metadata: pkg.DpkgMetadata{
|
||||
Package: "package-2",
|
||||
Version: "2.0.1",
|
||||
},
|
||||
PURL: "a-purl-2",
|
||||
CPEs: []pkg.CPE{
|
||||
must(pkg.NewCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*")),
|
||||
},
|
||||
})
|
||||
var d *distro.Distro
|
||||
s, err := source.NewFromDirectory("/some/path")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pres := NewJSONPresenter(catalog, s.Metadata, d, source.SquashedScope)
|
||||
|
||||
// run presenter
|
||||
err = pres.Present(&buffer)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual := buffer.Bytes()
|
||||
|
||||
if *updateJSONGoldenFiles {
|
||||
testutils.UpdateGoldenFileContents(t, actual)
|
||||
}
|
||||
|
||||
var expected = testutils.GetGoldenFileContents(t)
|
||||
|
||||
if !bytes.Equal(expected, actual) {
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(string(expected), string(actual), true)
|
||||
t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs))
|
||||
}
|
||||
func TestJSONDirectoryPresenter(t *testing.T) {
|
||||
catalog, metadata, dist := presenterDirectoryInput(t)
|
||||
assertPresenterAgainstGoldenSnapshot(t,
|
||||
NewJSONPresenter(catalog, metadata, dist, source.SquashedScope),
|
||||
*updateJSONGoldenFiles,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
func TestJSONImgsPresenter(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
func TestJSONImagePresenter(t *testing.T) {
|
||||
testImage := "image-simple"
|
||||
|
||||
if *updateJSONGoldenFiles {
|
||||
imagetest.UpdateGoldenFixtureImage(t, testImage)
|
||||
}
|
||||
|
||||
catalog := pkg.NewCatalog()
|
||||
img := imagetest.GetGoldenFixtureImage(t, testImage)
|
||||
|
||||
_, ref1, _ := img.SquashedTree().File("/somefile-1.txt", filetree.FollowBasenameLinks)
|
||||
_, ref2, _ := img.SquashedTree().File("/somefile-2.txt", filetree.FollowBasenameLinks)
|
||||
|
||||
// populate catalog with test data
|
||||
catalog.Add(pkg.Package{
|
||||
ID: "package-1-id",
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
Locations: []source.Location{
|
||||
source.NewLocationFromImage(string(ref1.RealPath), *ref1, img),
|
||||
},
|
||||
Type: pkg.PythonPkg,
|
||||
FoundBy: "the-cataloger-1",
|
||||
Language: pkg.Python,
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
Licenses: []string{"MIT"},
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
},
|
||||
PURL: "a-purl-1",
|
||||
CPEs: []pkg.CPE{
|
||||
must(pkg.NewCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*")),
|
||||
},
|
||||
})
|
||||
catalog.Add(pkg.Package{
|
||||
ID: "package-2-id",
|
||||
Name: "package-2",
|
||||
Version: "2.0.1",
|
||||
Locations: []source.Location{
|
||||
source.NewLocationFromImage(string(ref2.RealPath), *ref2, img),
|
||||
},
|
||||
Type: pkg.DebPkg,
|
||||
FoundBy: "the-cataloger-2",
|
||||
MetadataType: pkg.DpkgMetadataType,
|
||||
Metadata: pkg.DpkgMetadata{
|
||||
Package: "package-2",
|
||||
Version: "2.0.1",
|
||||
},
|
||||
PURL: "a-purl-2",
|
||||
CPEs: []pkg.CPE{
|
||||
must(pkg.NewCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*")),
|
||||
},
|
||||
})
|
||||
|
||||
// this is a hard coded value that is not given by the fixture helper and must be provided manually
|
||||
img.Metadata.ManifestDigest = "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368"
|
||||
|
||||
s, err := source.NewFromImage(img, "user-image-input")
|
||||
var d *distro.Distro
|
||||
pres := NewJSONPresenter(catalog, s.Metadata, d, source.SquashedScope)
|
||||
|
||||
// run presenter
|
||||
err = pres.Present(&buffer)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual := buffer.Bytes()
|
||||
|
||||
if *updateJSONGoldenFiles {
|
||||
testutils.UpdateGoldenFileContents(t, actual)
|
||||
}
|
||||
|
||||
var expected = testutils.GetGoldenFileContents(t)
|
||||
|
||||
if !bytes.Equal(expected, actual) {
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(string(expected), string(actual), true)
|
||||
t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs))
|
||||
}
|
||||
catalog, metadata, dist := presenterImageInput(t, testImage)
|
||||
assertPresenterAgainstGoldenImageSnapshot(t,
|
||||
NewJSONPresenter(catalog, metadata, dist, source.SquashedScope),
|
||||
testImage,
|
||||
*updateJSONGoldenFiles,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package spdx22
|
||||
|
||||
import "time"
|
||||
|
||||
type AnnotationType string
|
||||
|
||||
const (
|
||||
ReviewerAnnotationType AnnotationType = "REVIEWER"
|
||||
OtherAnnotationType AnnotationType = "OTHER"
|
||||
)
|
||||
|
||||
type Annotation struct {
|
||||
// Identify when the comment was made. This is to be specified according to the combined date and time in the
|
||||
// UTC format, as specified in the ISO 8601 standard.
|
||||
AnnotationDate time.Time `json:"annotationDate"`
|
||||
// Type of the annotation
|
||||
AnnotationType AnnotationType `json:"annotationType"`
|
||||
// This field identifies the person, organization or tool that has commented on a file, package, or the entire document.
|
||||
Annotator string `json:"annotator"`
|
||||
Comment string `json:"comment"`
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package spdx22
|
||||
|
||||
type Checksum struct {
|
||||
// Identifies the algorithm used to produce the subject Checksum. One of: "SHA256", "SHA1", "SHA384", "MD2", "MD4", "SHA512", "MD6", "MD5", "SHA224"
|
||||
Algorithm string `json:"algorithm"`
|
||||
ChecksumValue string `json:"checksumValue"`
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package spdx22
|
||||
|
||||
import "time"
|
||||
|
||||
type CreationInfo struct {
|
||||
Comment string `json:"comment,omitempty"`
|
||||
// Identify when the SPDX file was originally created. The date is to be specified according to combined date and
|
||||
// time in UTC format as specified in ISO 8601 standard. This field is distinct from the fields in section 8,
|
||||
// which involves the addition of information during a subsequent review.
|
||||
Created time.Time `json:"created"`
|
||||
// Identify who (or what, in the case of a tool) created the SPDX file. If the SPDX file was created by an
|
||||
// individual, indicate the person's name. If the SPDX file was created on behalf of a company or organization,
|
||||
//indicate the entity name. If the SPDX file was created using a software tool, indicate the name and version
|
||||
// for that tool. If multiple participants or tools were involved, use multiple instances of this field. Person
|
||||
// name or organization name may be designated as “anonymous” if appropriate.
|
||||
Creators []string `json:"creators"`
|
||||
// An optional field for creators of the SPDX file to provide the version of the SPDX License List used when the SPDX file was created.
|
||||
LicenseListVersion string `json:"licenseListVersion"`
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package spdx22
|
||||
|
||||
// derived from:
|
||||
// - https://spdx.github.io/spdx-spec/appendix-III-RDF-data-model-implementation-and-identifier-syntax/
|
||||
// - https://github.com/spdx/spdx-spec/blob/v2.2/schemas/spdx-schema.json
|
||||
// - https://github.com/spdx/spdx-spec/tree/v2.2/ontology
|
||||
|
||||
type Document struct {
|
||||
Element
|
||||
SPDXVersion string `json:"spdxVersion"`
|
||||
// One instance is required for each SPDX file produced. It provides the necessary information for forward
|
||||
// and backward compatibility for processing tools.
|
||||
CreationInfo CreationInfo `json:"creationInfo"`
|
||||
// 2.2: Data License; should be "CC0-1.0"
|
||||
// Cardinality: mandatory, one
|
||||
// License expression for dataLicense. Compliance with the SPDX specification includes populating the SPDX
|
||||
// fields therein with data related to such fields (\"SPDX-Metadata\"). The SPDX specification contains numerous
|
||||
// fields where an SPDX document creator may provide relevant explanatory text in SPDX-Metadata. Without
|
||||
// opining on the lawfulness of \"database rights\" (in jurisdictions where applicable), such explanatory text
|
||||
// is copyrightable subject matter in most Berne Convention countries. By using the SPDX specification, or any
|
||||
// portion hereof, you hereby agree that any copyright rights (as determined by your jurisdiction) in any
|
||||
// SPDX-Metadata, including without limitation explanatory text, shall be subject to the terms of the Creative
|
||||
// Commons CC0 1.0 Universal license. For SPDX-Metadata not containing any copyright rights, you hereby agree
|
||||
// and acknowledge that the SPDX-Metadata is provided to you \"as-is\" and without any representations or
|
||||
// warranties of any kind concerning the SPDX-Metadata, express, implied, statutory or otherwise, including
|
||||
// without limitation warranties of title, merchantability, fitness for a particular purpose, non-infringement,
|
||||
// or the absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not
|
||||
// discoverable, all to the greatest extent permissible under applicable law.
|
||||
DataLicense string `json:"dataLicense"`
|
||||
// Information about an external SPDX document reference including the checksum. This allows for verification of the external references.
|
||||
ExternalDocumentRefs []ExternalDocumentRef `json:"externalDocumentRefs,omitempty"`
|
||||
// Indicates that a particular ExtractedLicensingInfo was defined in the subject SpdxDocument.
|
||||
HasExtractedLicensingInfos []HasExtractedLicensingInfo `json:"hasExtractedLicensingInfos,omitempty"`
|
||||
// note: found in example documents from SPDX, but not in the JSON schema. See https://spdx.github.io/spdx-spec/2-document-creation-information/#25-spdx-document-namespace
|
||||
DocumentNamespace string `json:"documentNamespace"`
|
||||
// note: found in example documents from SPDX, but not in the JSON schema
|
||||
// DocumentDescribes []string `json:"documentDescribes"`
|
||||
Packages []Package `json:"packages"`
|
||||
// Files referenced in the SPDX document
|
||||
Files []File `json:"files,omitempty"`
|
||||
// Snippets referenced in the SPDX document
|
||||
Snippets []Snippet `json:"snippets,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package spdx22
|
||||
|
||||
type Element struct {
|
||||
SPDXID string `json:"SPDXID"`
|
||||
// Identify name of this SpdxElement.
|
||||
Name string `json:"name"`
|
||||
// Relationships referenced in the SPDX document
|
||||
Relationships []Relationship `json:"relationships,omitempty"`
|
||||
// Provide additional information about an SpdxElement.
|
||||
Annotations []Annotation `json:"annotations,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package spdx22
|
||||
|
||||
// ElementID represents the identifier string portion of an SPDX element
|
||||
// identifier. DocElementID should be used for any attributes which can
|
||||
// contain identifiers defined in a different SPDX document.
|
||||
// ElementIDs should NOT contain the mandatory 'SPDXRef-' portion.
|
||||
type ElementID string
|
||||
|
||||
func (e ElementID) String() string {
|
||||
return "SPDXRef-" + string(e)
|
||||
}
|
||||
|
||||
// DocElementID represents an SPDX element identifier that could be defined
|
||||
// in a different SPDX document, and therefore could have a "DocumentRef-"
|
||||
// portion, such as Relationship and Annotations.
|
||||
// ElementID is used for attributes in which a "DocumentRef-" portion cannot
|
||||
// appear, such as a Package or File definition (since it is necessarily
|
||||
// being defined in the present document).
|
||||
// DocumentRefID will be the empty string for elements defined in the
|
||||
// present document.
|
||||
// DocElementIDs should NOT contain the mandatory 'DocumentRef-' or
|
||||
// 'SPDXRef-' portions.
|
||||
type DocElementID struct {
|
||||
DocumentRefID string
|
||||
ElementRefID ElementID
|
||||
}
|
||||
|
||||
// RenderDocElementID takes a DocElementID and returns the string equivalent,
|
||||
// with the SPDXRef- prefix (and, if applicable, the DocumentRef- prefix)
|
||||
// reinserted.
|
||||
func (d DocElementID) String() string {
|
||||
prefix := ""
|
||||
if d.DocumentRefID != "" {
|
||||
prefix = "DocumentRef-" + d.DocumentRefID + ":"
|
||||
}
|
||||
return prefix + d.ElementRefID.String()
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package spdx22
|
||||
|
||||
type ExternalDocumentRef struct {
|
||||
// externalDocumentId is a string containing letters, numbers, ., - and/or + which uniquely identifies an external document within this document.
|
||||
ExternalDocumentID string `json:"externalDocumentId"`
|
||||
Checksum Checksum `json:"checksum"`
|
||||
// SPDX ID for SpdxDocument. A propoerty containing an SPDX document.
|
||||
SpdxDocument string `json:"spdxDocument"`
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package spdx22
|
||||
|
||||
type ReferenceCategory string
|
||||
|
||||
const (
|
||||
SecurityReferenceCategory ReferenceCategory = "SECURITY"
|
||||
PackageManagerReferenceCategory ReferenceCategory = "PACKAGE_MANAGER"
|
||||
OtherReferenceCategory ReferenceCategory = "OTHER"
|
||||
)
|
||||
|
||||
// source: https://spdx.github.io/spdx-spec/appendix-VI-external-repository-identifiers/
|
||||
|
||||
type ExternalRefType string
|
||||
|
||||
const (
|
||||
// see https://nvd.nist.gov/cpe
|
||||
Cpe22ExternalRefType ExternalRefType = "cpe22Type"
|
||||
// see https://nvd.nist.gov/cpe
|
||||
Cpe23ExternalRefType ExternalRefType = "cpe23Type"
|
||||
// see http://repo1.maven.org/maven2/
|
||||
MavenCentralExternalRefType ExternalRefType = "maven-central"
|
||||
// see https://www.npmjs.com/
|
||||
NpmExternalRefType ExternalRefType = "npm"
|
||||
// see https://www.nuget.org/
|
||||
NugetExternalRefType ExternalRefType = "nuget"
|
||||
// see http://bower.io/
|
||||
BowerExternalRefType ExternalRefType = "bower"
|
||||
// see https://github.com/package-url/purl-spec
|
||||
PurlExternalRefType ExternalRefType = "purl"
|
||||
// These point to objects present in the Software Heritage archive by the means of SoftWare Heritage persistent Identifiers (SWHID)
|
||||
SwhExternalRefType ExternalRefType = "swh"
|
||||
)
|
||||
|
||||
type ExternalRef struct {
|
||||
Comment string `json:"comment,omitempty"`
|
||||
// Category for the external reference.
|
||||
ReferenceCategory ReferenceCategory `json:"referenceCategory"`
|
||||
// The unique string with no spaces necessary to access the package-specific information, metadata, or content
|
||||
// within the target location. The format of the locator is subject to constraints defined by the <type>.
|
||||
ReferenceLocator string `json:"referenceLocator"`
|
||||
// Type of the external reference. These are defined in an appendix in the SPDX specification.
|
||||
ReferenceType ExternalRefType `json:"referenceType"`
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package spdx22
|
||||
|
||||
type FileType string
|
||||
|
||||
const (
|
||||
DocumentationFileType FileType = "DOCUMENTATION"
|
||||
ImageFileType FileType = "IMAGE"
|
||||
VideoFileType FileType = "VIDEO"
|
||||
ArchiveFileType FileType = "ARCHIVE"
|
||||
SpdxFileType FileType = "SPDX"
|
||||
ApplicationFileType FileType = "APPLICATION"
|
||||
SourceFileType FileType = "SOURCE"
|
||||
BinaryFileType FileType = "BINARY"
|
||||
TextFileType FileType = "TEXT"
|
||||
AudioFileType FileType = "AUDIO"
|
||||
OtherFileType FileType = "OTHER"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
Item
|
||||
// (At least one is required.) The checksum property provides a mechanism that can be used to verify that the
|
||||
// contents of a File or Package have not changed.
|
||||
Checksums []Checksum `json:"checksums"`
|
||||
// This field provides a place for the SPDX file creator to record file contributors. Contributors could include
|
||||
// names of copyright holders and/or authors who may not be copyright holders yet contributed to the file content.
|
||||
FileContributors []string `json:"fileContributors"`
|
||||
// Each element is a SPDX ID for a File.
|
||||
FileDependencies []string `json:"fileDependencies"`
|
||||
// The name of the file relative to the root of the package.
|
||||
FileName string `json:"fileName"`
|
||||
// The type of the file
|
||||
FileTypes []string `json:"fileTypes"`
|
||||
// This field provides a place for the SPDX file creator to record potential legal notices found in the file.
|
||||
// This may or may not include copyright statements.
|
||||
NoticeText string `json:"noticeText,omitempty"`
|
||||
// Indicates the project in which the SpdxElement originated. Tools must preserve doap:homepage and doap:name
|
||||
// properties and the URI (if one is known) of doap:Project resources that are values of this property. All other
|
||||
// properties of doap:Projects are not directly supported by SPDX and may be dropped when translating to or
|
||||
// from some SPDX formats.
|
||||
ArtifactOf []string `json:"artifactOf"`
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package spdx22
|
||||
|
||||
type HasExtractedLicensingInfo struct {
|
||||
// Verbatim license or licensing notice text that was discovered.
|
||||
ExtractedText string `json:"extractedText"`
|
||||
// A human readable short form license identifier for a license. The license ID is iether on the standard license
|
||||
// oist or the form \"LicenseRef-\"[idString] where [idString] is a unique string containing letters,
|
||||
// numbers, \".\", \"-\" or \"+\".
|
||||
LicenseID string `json:"licenseId"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
// Identify name of this SpdxElement.
|
||||
Name string `json:"name,omitempty"`
|
||||
SeeAlsos []string `json:"seeAlsos,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package spdx22
|
||||
|
||||
type Item struct {
|
||||
Element
|
||||
// The licenseComments property allows the preparer of the SPDX document to describe why the licensing in
|
||||
// spdx:licenseConcluded was chosen.
|
||||
LicenseComments string `json:"licenseComments,omitempty"`
|
||||
LicenseConcluded string `json:"licenseConcluded"`
|
||||
// The licensing information that was discovered directly within the package. There will be an instance of this
|
||||
// property for each distinct value of alllicenseInfoInFile properties of all files contained in the package.
|
||||
LicenseInfoFromFiles []string `json:"licenseInfoFromFiles,omitempty"`
|
||||
// Licensing information that was discovered directly in the subject file. This is also considered a declared license for the file.
|
||||
LicenseInfoInFiles []string `json:"licenseInfoInFiles,omitempty"`
|
||||
// The text of copyright declarations recited in the Package or File.
|
||||
CopyrightText string `json:"copyrightText,omitempty"`
|
||||
// This field provides a place for the SPDX data creator to record acknowledgements that may be required to be
|
||||
// communicated in some contexts. This is not meant to include the actual complete license text (see
|
||||
// licenseConculded and licenseDeclared), and may or may not include copyright notices (see also copyrightText).
|
||||
// The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from
|
||||
// license texts, which may be necessary or desirable to reproduce.
|
||||
AttributionTexts []string `json:"attributionTexts,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package spdx22
|
||||
|
||||
type Package struct {
|
||||
Item
|
||||
// The checksum property provides a mechanism that can be used to verify that the contents of a File or
|
||||
// Package have not changed.
|
||||
Checksums []Checksum `json:"checksums,omitempty"`
|
||||
// Provides a detailed description of the package.
|
||||
Description string `json:"description,omitempty"`
|
||||
// The URI at which this package is available for download. Private (i.e., not publicly reachable) URIs are
|
||||
// acceptable as values of this property. The values http://spdx.org/rdf/terms#none and http://spdx.org/rdf/terms#noassertion
|
||||
// may be used to specify that the package is not downloadable or that no attempt was made to determine its
|
||||
// download location, respectively.
|
||||
DownloadLocation string `json:"downloadLocation,omitempty"`
|
||||
// An External Reference allows a Package to reference an external source of additional information, metadata,
|
||||
// enumerations, asset identifiers, or downloadable content believed to be relevant to the Package.
|
||||
ExternalRefs []ExternalRef `json:"externalRefs,omitempty"`
|
||||
// Indicates whether the file content of this package has been available for or subjected to analysis when
|
||||
// creating the SPDX document. If false indicates packages that represent metadata or URI references to a
|
||||
// project, product, artifact, distribution or a component. If set to false, the package must not contain any files
|
||||
FilesAnalyzed bool `json:"filesAnalyzed"`
|
||||
// Indicates that a particular file belongs to a package (elements are SPDX ID for a File).
|
||||
HasFiles []string `json:"hasFiles,omitempty"`
|
||||
// Provide a place for the SPDX file creator to record a web site that serves as the package's home page.
|
||||
// This link can also be used to reference further information about the package referenced by the SPDX file creator.
|
||||
Homepage string `json:"homepage,omitempty"`
|
||||
// List the licenses that have been declared by the authors of the package. Any license information that does not
|
||||
// originate from the package authors, e.g. license information from a third party repository, should not be included in this field.
|
||||
LicenseDeclared string `json:"licenseDeclared"`
|
||||
// The name and, optionally, contact information of the person or organization that originally created the package.
|
||||
// Values of this property must conform to the agent and tool syntax.
|
||||
Originator string `json:"originator,omitempty"`
|
||||
// The base name of the package file name. For example, zlib-1.2.5.tar.gz.
|
||||
PackageFileName string `json:"packageFileName,omitempty"`
|
||||
// A manifest based verification code (the algorithm is defined in section 4.7 of the full specification) of the
|
||||
// SPDX Item. This allows consumers of this data and/or database to determine if an SPDX item they have in hand
|
||||
// is identical to the SPDX item from which the data was produced. This algorithm works even if the SPDX document
|
||||
// is included in the SPDX item.
|
||||
PackageVerificationCode *PackageVerificationCode `json:"packageVerificationCode,omitempty"`
|
||||
// Allows the producer(s) of the SPDX document to describe how the package was acquired and/or changed from the original source.
|
||||
SourceInfo string `json:"sourceInfo,omitempty"`
|
||||
// Provides a short description of the package.
|
||||
Summary string `json:"summary,omitempty"`
|
||||
// The name and, optionally, contact information of the person or organization who was the immediate supplier
|
||||
// of this package to the recipient. The supplier may be different than originator when the software has been
|
||||
// repackaged. Values of this property must conform to the agent and tool syntax.
|
||||
Supplier string `json:"supplier,omitempty"`
|
||||
// Provides an indication of the version of the package that is described by this SpdxDocument.
|
||||
VersionInfo string `json:"versionInfo,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package spdx22
|
||||
|
||||
// Why are there two package identifier fields Package Checksum and Package Verification?
|
||||
// Although the values of the two fields Package Checksum and Package Verification are similar, they each serve a
|
||||
// different purpose. The Package Checksum provides a unique identifier of a software package which is computed by
|
||||
// taking the SHA1 of the entire software package file. This enables one to quickly determine if two different copies
|
||||
// of a package are the same. One disadvantage of this approach is that one cannot add an SPDX data file into the
|
||||
// original package without changing the Package Checksum value. Alternatively, the Package Verification field enables
|
||||
// the inclusion of an SPDX file. It enables one to quickly verify if one or more of the original package files has
|
||||
// changed. The Package Verification field is a unique identifier that is based on SHAing only the original package
|
||||
// files (e.g., excluding the SPDX file). This allows one to add an SPDX file to the original package without changing
|
||||
// this unique identifier.
|
||||
// source: https://wiki.spdx.org/view/SPDX_FAQ
|
||||
type PackageVerificationCode struct {
|
||||
// "A file that was excluded when calculating the package verification code. This is usually a file containing
|
||||
// SPDX data regarding the package. If a package contains more than one SPDX file all SPDX files must be excluded
|
||||
// from the package verification code. If this is not done it would be impossible to correctly calculate the
|
||||
// verification codes in both files.
|
||||
PackageVerificationCodeExcludedFiles []string `json:"packageVerificationCodeExcludedFiles"`
|
||||
|
||||
// The actual package verification code as a hex encoded value.
|
||||
PackageVerificationCodeValue string `json:"packageVerificationCodeValue"`
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package spdx22
|
||||
|
||||
type Relationship struct {
|
||||
// SPDX ID for SpdxElement. A related SpdxElement.
|
||||
RelatedSpdxElement string `json:"relatedSpdxElement"`
|
||||
// Describes the type of relationship between two SPDX elements.
|
||||
RelationshipType RelationshipType `json:"relationshipType"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
}
|
||||
|
||||
// source: https://spdx.github.io/spdx-spec/7-relationships-between-SPDX-elements/
|
||||
type RelationshipType string
|
||||
|
||||
const (
|
||||
// DescribedByRelationship is to be used when SPDXRef-A is described by SPDXREF-Document.
|
||||
// Example: The package 'WildFly' is described by SPDX document WildFly.spdx.
|
||||
DescribedByRelationship RelationshipType = "DESCRIBED_BY"
|
||||
|
||||
// ContainsRelationship is to be used when SPDXRef-A contains SPDXRef-B.
|
||||
// Example: An ARCHIVE file bar.tgz contains a SOURCE file foo.c.
|
||||
ContainsRelationship RelationshipType = "CONTAINS"
|
||||
|
||||
// ContainedByRelationship is to be used when SPDXRef-A is contained by SPDXRef-B.
|
||||
// Example: A SOURCE file foo.c is contained by ARCHIVE file bar.tgz
|
||||
ContainedByRelationship RelationshipType = "CONTAINED_BY"
|
||||
|
||||
// DependsOnRelationship is to be used when SPDXRef-A depends on SPDXRef-B.
|
||||
// Example: Package A depends on the presence of package B in order to build and run
|
||||
DependsOnRelationship RelationshipType = "DEPENDS_ON"
|
||||
|
||||
// DependencyOfRelationship is to be used when SPDXRef-A is dependency of SPDXRef-B.
|
||||
// Example: A is explicitly stated as a dependency of B in a machine-readable file. Use when a package manager does not define scopes.
|
||||
DependencyOfRelationship RelationshipType = "DEPENDENCY_OF"
|
||||
|
||||
// DependencyManifestOfRelationship is to be used when SPDXRef-A is a manifest file that lists a set of dependencies for SPDXRef-B.
|
||||
// Example: A file package.json is the dependency manifest of a package foo. Note that only one manifest should be used to define the same dependency graph.
|
||||
DependencyManifestOfRelationship RelationshipType = "DEPENDENCY_MANIFEST_OF"
|
||||
|
||||
// BuildDependencyOfRelationship is to be used when SPDXRef-A is a build dependency of SPDXRef-B.
|
||||
// Example: A is in the compile scope of B in a Maven project.
|
||||
BuildDependencyOfRelationship RelationshipType = "BUILD_DEPENDENCY_OF"
|
||||
|
||||
// DevDependencyOfRelationship is to be used when SPDXRef-A is a development dependency of SPDXRef-B.
|
||||
// Example: A is in the devDependencies scope of B in a Maven project.
|
||||
DevDependencyOfRelationship RelationshipType = "DEV_DEPENDENCY_OF"
|
||||
|
||||
// OptionalDependencyOfRelationship is to be used when SPDXRef-A is an optional dependency of SPDXRef-B.
|
||||
// Example: Use when building the code will proceed even if a dependency cannot be found, fails to install, or is only installed on a specific platform. For example, A is in the optionalDependencies scope of npm project B.
|
||||
OptionalDependencyOfRelationship RelationshipType = "OPTIONAL_DEPENDENCY_OF"
|
||||
|
||||
// ProvidedDependencyOfRelationship is to be used when SPDXRef-A is a to be provided dependency of SPDXRef-B.
|
||||
// Example: A is in the provided scope of B in a Maven project, indicating that the project expects it to be provided, for instance, by the container or JDK.
|
||||
ProvidedDependencyOfRelationship RelationshipType = "PROVIDED_DEPENDENCY_OF"
|
||||
|
||||
// TestDependencyOfRelationship is to be used when SPDXRef-A is a test dependency of SPDXRef-B.
|
||||
// Example: A is in the test scope of B in a Maven project.
|
||||
TestDependencyOfRelationship RelationshipType = "TEST_DEPENDENCY_OF"
|
||||
|
||||
// RuntimeDependencyOfRelationship is to be used when SPDXRef-A is a dependency required for the execution of SPDXRef-B.
|
||||
// Example: A is in the runtime scope of B in a Maven project.
|
||||
RuntimeDependencyOfRelationship RelationshipType = "RUNTIME_DEPENDENCY_OF"
|
||||
|
||||
// ExampleOfRelationship is to be used when SPDXRef-A is an example of SPDXRef-B.
|
||||
// Example: The file or snippet that illustrates how to use an application or library.
|
||||
ExampleOfRelationship RelationshipType = "EXAMPLE_OF"
|
||||
|
||||
// GeneratesRelationship is to be used when SPDXRef-A generates SPDXRef-B.
|
||||
// Example: A SOURCE file makefile.mk generates a BINARY file a.out
|
||||
GeneratesRelationship RelationshipType = "GENERATES"
|
||||
|
||||
// GeneratedFromRelationship is to be used when SPDXRef-A was generated from SPDXRef-B.
|
||||
// Example: A BINARY file a.out has been generated from a SOURCE file makefile.mk. A BINARY file foolib.a is generated from a SOURCE file bar.c.
|
||||
GeneratedFromRelationship RelationshipType = "GENERATED_FROM"
|
||||
|
||||
// AncestorOfRelationship is to be used when SPDXRef-A is an ancestor (same lineage but pre-dates) SPDXRef-B.
|
||||
// Example: A SOURCE file makefile.mk is a version of the original ancestor SOURCE file 'makefile2.mk'
|
||||
AncestorOfRelationship RelationshipType = "ANCESTOR_OF"
|
||||
|
||||
// DescendantOfRelationship is to be used when SPDXRef-A is a descendant of (same lineage but postdates) SPDXRef-B.
|
||||
// Example: A SOURCE file makefile2.mk is a descendant of the original SOURCE file 'makefile.mk'
|
||||
DescendantOfRelationship RelationshipType = "DESCENDANT_OF"
|
||||
|
||||
// VariantOfRelationship is to be used when SPDXRef-A is a variant of (same lineage but not clear which came first) SPDXRef-B.
|
||||
// Example: A SOURCE file makefile2.mk is a variant of SOURCE file makefile.mk if they differ by some edit, but there is no way to tell which came first (no reliable date information).
|
||||
VariantOfRelationship RelationshipType = "VARIANT_OF"
|
||||
|
||||
// DistributionArtifactRelationship is to be used when distributing SPDXRef-A requires that SPDXRef-B also be distributed.
|
||||
// Example: A BINARY file foo.o requires that the ARCHIVE file bar-sources.tgz be made available on distribution.
|
||||
DistributionArtifactRelationship RelationshipType = "DISTRIBUTION_ARTIFACT"
|
||||
|
||||
// PatchForRelationship is to be used when SPDXRef-A is a patch file for (to be applied to) SPDXRef-B.
|
||||
// Example: A SOURCE file foo.diff is a patch file for SOURCE file foo.c.
|
||||
PatchForRelationship RelationshipType = "PATCH_FOR"
|
||||
|
||||
// PatchAppliedRelationship is to be used when SPDXRef-A is a patch file that has been applied to SPDXRef-B.
|
||||
// Example: A SOURCE file foo.diff is a patch file that has been applied to SOURCE file 'foo-patched.c'.
|
||||
PatchAppliedRelationship RelationshipType = "PATCH_APPLIED"
|
||||
|
||||
// CopyOfRelationship is to be used when SPDXRef-A is an exact copy of SPDXRef-B.
|
||||
// Example: A BINARY file alib.a is an exact copy of BINARY file a2lib.a.
|
||||
CopyOfRelationship RelationshipType = "COPY_OF"
|
||||
|
||||
// FileAddedRelationship is to be used when SPDXRef-A is a file that was added to SPDXRef-B.
|
||||
// Example: A SOURCE file foo.c has been added to package ARCHIVE bar.tgz.
|
||||
FileAddedRelationship RelationshipType = "FILE_ADDED"
|
||||
|
||||
// FileDeletedRelationship is to be used when SPDXRef-A is a file that was deleted from SPDXRef-B.
|
||||
// Example: A SOURCE file foo.diff has been deleted from package ARCHIVE bar.tgz.
|
||||
FileDeletedRelationship RelationshipType = "FILE_DELETED"
|
||||
|
||||
// FileModifiedRelationship is to be used when SPDXRef-A is a file that was modified from SPDXRef-B.
|
||||
// Example: A SOURCE file foo.c has been modified from SOURCE file foo.orig.c.
|
||||
FileModifiedRelationship RelationshipType = "FILE_MODIFIED"
|
||||
|
||||
// ExpandedFromArchiveRelationship is to be used when SPDXRef-A is expanded from the archive SPDXRef-B.
|
||||
// Example: A SOURCE file foo.c, has been expanded from the archive ARCHIVE file xyz.tgz.
|
||||
ExpandedFromArchiveRelationship RelationshipType = "EXPANDED_FROM_ARCHIVE"
|
||||
|
||||
// DynamicLinkRelationship is to be used when SPDXRef-A dynamically links to SPDXRef-B.
|
||||
// Example: An APPLICATION file 'myapp' dynamically links to BINARY file zlib.so.
|
||||
DynamicLinkRelationship RelationshipType = "DYNAMIC_LINK"
|
||||
|
||||
// StaticLinkRelationship is to be used when SPDXRef-A statically links to SPDXRef-B.
|
||||
// Example: An APPLICATION file 'myapp' statically links to BINARY zlib.a.
|
||||
StaticLinkRelationship RelationshipType = "STATIC_LINK"
|
||||
|
||||
// DataFileOfRelationship is to be used when SPDXRef-A is a data file used in SPDXRef-B.
|
||||
// Example: An IMAGE file 'kitty.jpg' is a data file of an APPLICATION 'hellokitty'.
|
||||
DataFileOfRelationship RelationshipType = "DATA_FILE_OF"
|
||||
|
||||
// TestCaseOfRelationship is to be used when SPDXRef-A is a test case used in testing SPDXRef-B.
|
||||
// Example: A SOURCE file testMyCode.java is a unit test file used to test an APPLICATION MyPackage.
|
||||
TestCaseOfRelationship RelationshipType = "TEST_CASE_OF"
|
||||
|
||||
// BuildToolOfRelationship is to be used when SPDXRef-A is used to build SPDXRef-B.
|
||||
// Example: A SOURCE file makefile.mk is used to build an APPLICATION 'zlib'.
|
||||
BuildToolOfRelationship RelationshipType = "BUILD_TOOL_OF"
|
||||
|
||||
// DevToolOfRelationship is to be used when SPDXRef-A is used as a development tool for SPDXRef-B.
|
||||
// Example: Any tool used for development such as a code debugger.
|
||||
DevToolOfRelationship RelationshipType = "DEV_TOOL_OF"
|
||||
|
||||
// TestOfRelationship is to be used when SPDXRef-A is used for testing SPDXRef-B.
|
||||
// Example: Generic relationship for cases where it's clear that something is used for testing but unclear whether it's TEST_CASE_OF or TEST_TOOL_OF.
|
||||
TestOfRelationship RelationshipType = "TEST_OF"
|
||||
|
||||
// TestToolOfRelationship is to be used when SPDXRef-A is used as a test tool for SPDXRef-B.
|
||||
// Example: Any tool used to test the code such as ESlint.
|
||||
TestToolOfRelationship RelationshipType = "TEST_TOOL_OF"
|
||||
|
||||
// DocumentationOfRelationship is to be used when SPDXRef-A provides documentation of SPDXRef-B.
|
||||
// Example: A DOCUMENTATION file readme.txt documents the APPLICATION 'zlib'.
|
||||
DocumentationOfRelationship RelationshipType = "DOCUMENTATION_OF"
|
||||
|
||||
// OptionalComponentOfRelationship is to be used when SPDXRef-A is an optional component of SPDXRef-B.
|
||||
// Example: A SOURCE file fool.c (which is in the contributors directory) may or may not be included in the build of APPLICATION 'atthebar'.
|
||||
OptionalComponentOfRelationship RelationshipType = "OPTIONAL_COMPONENT_OF"
|
||||
|
||||
// MetafileOfRelationship is to be used when SPDXRef-A is a metafile of SPDXRef-B.
|
||||
// Example: A SOURCE file pom.xml is a metafile of the APPLICATION 'Apache Xerces'.
|
||||
MetafileOfRelationship RelationshipType = "METAFILE_OF"
|
||||
|
||||
// PackageOfRelationship is to be used when SPDXRef-A is used as a package as part of SPDXRef-B.
|
||||
// Example: A Linux distribution contains an APPLICATION package gawk as part of the distribution MyLinuxDistro.
|
||||
PackageOfRelationship RelationshipType = "PACKAGE_OF"
|
||||
|
||||
// AmendsRelationship is to be used when (current) SPDXRef-DOCUMENT amends the SPDX information in SPDXRef-B.
|
||||
// Example: (Current) SPDX document A version 2 contains a correction to a previous version of the SPDX document A version 1. Note the reserved identifier SPDXRef-DOCUMENT for the current document is required.
|
||||
AmendsRelationship RelationshipType = "AMENDS"
|
||||
|
||||
// PrerequisiteForRelationship is to be used when SPDXRef-A is a prerequisite for SPDXRef-B.
|
||||
// Example: A library bar.dll is a prerequisite or dependency for APPLICATION foo.exe
|
||||
PrerequisiteForRelationship RelationshipType = "PREREQUISITE_FOR"
|
||||
|
||||
// HasPrerequisiteRelationship is to be used when SPDXRef-A has as a prerequisite SPDXRef-B.
|
||||
// Example: An APPLICATION foo.exe has prerequisite or dependency on bar.dll
|
||||
HasPrerequisiteRelationship RelationshipType = "HAS_PREREQUISITE"
|
||||
|
||||
// OtherRelationship is to be used for a relationship which has not been defined in the formal SPDX specification. A description of the relationship should be included in the Relationship comments field.
|
||||
OtherRelationship RelationshipType = "OTHER"
|
||||
)
|
||||
@@ -0,0 +1,32 @@
|
||||
package spdx22
|
||||
|
||||
type StartPointer struct {
|
||||
Offset int `json:"offset,omitempty"`
|
||||
LineNumber int `json:"lineNumber,omitempty"`
|
||||
// SPDX ID for File
|
||||
Reference string `json:"reference"`
|
||||
}
|
||||
|
||||
type EndPointer struct {
|
||||
Offset int `json:"offset,omitempty"`
|
||||
LineNumber int `json:"lineNumber,omitempty"`
|
||||
// SPDX ID for File
|
||||
Reference string `json:"reference"`
|
||||
}
|
||||
|
||||
type Range struct {
|
||||
StartPointer StartPointer `json:"startPointer"`
|
||||
EndPointer EndPointer `json:"endPointer"`
|
||||
}
|
||||
|
||||
type Snippet struct {
|
||||
Item
|
||||
// Licensing information that was discovered directly in the subject snippet. This is also considered a declared
|
||||
// license for the snippet. (elements are license expressions)
|
||||
LicenseInfoInSnippets []string `json:"licenseInfoInSnippets"`
|
||||
// SPDX ID for File. File containing the SPDX element (e.g. the file contaning a snippet).
|
||||
SnippetFromFile string `json:"snippetFromFile"`
|
||||
// (At least 1 range is required). This field defines the byte range in the original host file (in X.2) that the
|
||||
// snippet information applies to.
|
||||
Ranges []Range `json:"ranges"`
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package spdx22
|
||||
|
||||
const Version = "SPDX-2.2"
|
||||
@@ -0,0 +1,168 @@
|
||||
package packages
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal/presenter/packages/model/spdx22"
|
||||
"github.com/anchore/syft/internal/spdxlicense"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func getSPDXExternalRefs(p *pkg.Package) (externalRefs []spdx22.ExternalRef) {
|
||||
externalRefs = make([]spdx22.ExternalRef, 0)
|
||||
for _, c := range p.CPEs {
|
||||
externalRefs = append(externalRefs, spdx22.ExternalRef{
|
||||
ReferenceCategory: spdx22.SecurityReferenceCategory,
|
||||
ReferenceLocator: c.BindToFmtString(),
|
||||
ReferenceType: spdx22.Cpe23ExternalRefType,
|
||||
})
|
||||
}
|
||||
|
||||
if p.PURL != "" {
|
||||
externalRefs = append(externalRefs, spdx22.ExternalRef{
|
||||
ReferenceCategory: spdx22.PackageManagerReferenceCategory,
|
||||
ReferenceLocator: p.PURL,
|
||||
ReferenceType: spdx22.PurlExternalRefType,
|
||||
})
|
||||
}
|
||||
return externalRefs
|
||||
}
|
||||
|
||||
func getSPDXLicense(p *pkg.Package) string {
|
||||
// source: https://spdx.github.io/spdx-spec/3-package-information/#313-concluded-license
|
||||
// The options to populate this field are limited to:
|
||||
// A valid SPDX License Expression as defined in Appendix IV;
|
||||
// NONE, if the SPDX file creator concludes there is no license available for this package; or
|
||||
// NOASSERTION if:
|
||||
// (i) the SPDX file creator has attempted to but cannot reach a reasonable objective determination;
|
||||
// (ii) the SPDX file creator has made no attempt to determine this field; or
|
||||
// (iii) the SPDX file creator has intentionally provided no information (no meaning should be implied by doing so).
|
||||
|
||||
if len(p.Licenses) == 0 {
|
||||
return "NONE"
|
||||
}
|
||||
|
||||
// take all licenses and assume an AND expression; for information about license expressions see https://spdx.github.io/spdx-spec/appendix-IV-SPDX-license-expressions/
|
||||
var parsedLicenses []string
|
||||
for _, l := range p.Licenses {
|
||||
if value, exists := spdxlicense.ID(l); exists {
|
||||
parsedLicenses = append(parsedLicenses, value)
|
||||
}
|
||||
}
|
||||
|
||||
if len(parsedLicenses) == 0 {
|
||||
return "NOASSERTION"
|
||||
}
|
||||
|
||||
return strings.Join(parsedLicenses, " AND ")
|
||||
}
|
||||
|
||||
func noneIfEmpty(value string) string {
|
||||
if strings.TrimSpace(value) == "" {
|
||||
return "NONE"
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func getSPDXDownloadLocation(p *pkg.Package) string {
|
||||
// 3.7: Package Download Location
|
||||
// Cardinality: mandatory, one
|
||||
// NONE if there is no download location whatsoever.
|
||||
// NOASSERTION if:
|
||||
// (i) the SPDX file creator has attempted to but cannot reach a reasonable objective determination;
|
||||
// (ii) the SPDX file creator has made no attempt to determine this field; or
|
||||
// (iii) the SPDX file creator has intentionally provided no information (no meaning should be implied by doing so).
|
||||
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.ApkMetadata:
|
||||
return noneIfEmpty(metadata.URL)
|
||||
case pkg.NpmPackageJSONMetadata:
|
||||
return noneIfEmpty(metadata.URL)
|
||||
default:
|
||||
return "NOASSERTION"
|
||||
}
|
||||
}
|
||||
|
||||
func getSPDXHomepage(p *pkg.Package) string {
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.GemMetadata:
|
||||
return metadata.Homepage
|
||||
case pkg.NpmPackageJSONMetadata:
|
||||
return metadata.Homepage
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func getSPDXSourceInfo(p *pkg.Package) string {
|
||||
answer := ""
|
||||
switch p.Type {
|
||||
case pkg.RpmPkg:
|
||||
answer = "acquired package info from RPM DB"
|
||||
case pkg.ApkPkg:
|
||||
answer = "acquired package info from APK DB"
|
||||
case pkg.DebPkg:
|
||||
answer = "acquired package info from DPKG DB"
|
||||
case pkg.NpmPkg:
|
||||
answer = "acquired package info from installed node module manifest file"
|
||||
case pkg.PythonPkg:
|
||||
answer = "acquired package info from installed python package manifest file"
|
||||
case pkg.JavaPkg, pkg.JenkinsPluginPkg:
|
||||
answer = "acquired package info from installed java archive"
|
||||
case pkg.GemPkg:
|
||||
answer = "acquired package info from installed gem metadata file"
|
||||
case pkg.GoModulePkg:
|
||||
answer = "acquired package info from go module information"
|
||||
case pkg.RustPkg:
|
||||
answer = "acquired package info from rust cargo manifest"
|
||||
default:
|
||||
answer = "acquired package info from the following paths"
|
||||
}
|
||||
var paths []string
|
||||
for _, l := range p.Locations {
|
||||
paths = append(paths, l.RealPath)
|
||||
}
|
||||
|
||||
return answer + ": " + strings.Join(paths, ", ")
|
||||
}
|
||||
|
||||
func getSPDXOriginator(p *pkg.Package) string {
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.ApkMetadata:
|
||||
return metadata.Maintainer
|
||||
case pkg.NpmPackageJSONMetadata:
|
||||
return metadata.Author
|
||||
case pkg.PythonPackageMetadata:
|
||||
author := metadata.Author
|
||||
if author == "" {
|
||||
return metadata.AuthorEmail
|
||||
}
|
||||
if metadata.AuthorEmail != "" {
|
||||
author += fmt.Sprintf(" <%s>", metadata.AuthorEmail)
|
||||
}
|
||||
return author
|
||||
case pkg.GemMetadata:
|
||||
if len(metadata.Authors) > 0 {
|
||||
return metadata.Authors[0]
|
||||
}
|
||||
return ""
|
||||
case pkg.RpmdbMetadata:
|
||||
return metadata.Vendor
|
||||
case pkg.DpkgMetadata:
|
||||
return metadata.Maintainer
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func getSPDXDescription(p *pkg.Package) string {
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.ApkMetadata:
|
||||
return metadata.Description
|
||||
case pkg.NpmPackageJSONMetadata:
|
||||
return metadata.Description
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,518 @@
|
||||
package packages
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/source"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/internal/presenter/packages/model/spdx22"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func Test_getSPDXExternalRefs(t *testing.T) {
|
||||
testCPE := must(pkg.NewCPE("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*"))
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
expected []spdx22.ExternalRef
|
||||
}{
|
||||
{
|
||||
name: "cpe + purl",
|
||||
input: pkg.Package{
|
||||
CPEs: []pkg.CPE{
|
||||
testCPE,
|
||||
},
|
||||
PURL: "a-purl",
|
||||
},
|
||||
expected: []spdx22.ExternalRef{
|
||||
{
|
||||
ReferenceCategory: spdx22.SecurityReferenceCategory,
|
||||
ReferenceLocator: testCPE.BindToFmtString(),
|
||||
ReferenceType: spdx22.Cpe23ExternalRefType,
|
||||
},
|
||||
{
|
||||
ReferenceCategory: spdx22.PackageManagerReferenceCategory,
|
||||
ReferenceLocator: "a-purl",
|
||||
ReferenceType: spdx22.PurlExternalRefType,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.ElementsMatch(t, test.expected, getSPDXExternalRefs(&test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getSPDXLicense(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "no licenses",
|
||||
input: pkg.Package{},
|
||||
expected: "NONE",
|
||||
},
|
||||
{
|
||||
name: "no SPDX licenses",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"made-up",
|
||||
},
|
||||
},
|
||||
expected: "NOASSERTION",
|
||||
},
|
||||
{
|
||||
name: "with SPDX license",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"MIT",
|
||||
},
|
||||
},
|
||||
expected: "MIT",
|
||||
},
|
||||
{
|
||||
name: "with SPDX license expression",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"MIT",
|
||||
"GPL-3.0",
|
||||
},
|
||||
},
|
||||
expected: "MIT AND GPL-3.0",
|
||||
},
|
||||
{
|
||||
name: "cap insensitive",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"gpl-3.0",
|
||||
},
|
||||
},
|
||||
expected: "GPL-3.0",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, getSPDXLicense(&test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_noneIfEmpty(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
value string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "non-zero value",
|
||||
value: "something",
|
||||
expected: "something",
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
value: "",
|
||||
expected: "NONE",
|
||||
},
|
||||
{
|
||||
name: "space",
|
||||
value: " ",
|
||||
expected: "NONE",
|
||||
},
|
||||
{
|
||||
name: "tab",
|
||||
value: "\t",
|
||||
expected: "NONE",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, noneIfEmpty(test.value))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getSPDXDownloadLocation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "no metadata",
|
||||
input: pkg.Package{},
|
||||
expected: "NOASSERTION",
|
||||
},
|
||||
{
|
||||
name: "from apk",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
URL: "http://a-place.gov",
|
||||
},
|
||||
},
|
||||
expected: "http://a-place.gov",
|
||||
},
|
||||
{
|
||||
name: "from npm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
URL: "http://a-place.gov",
|
||||
},
|
||||
},
|
||||
expected: "http://a-place.gov",
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
URL: "",
|
||||
},
|
||||
},
|
||||
expected: "NONE",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, getSPDXDownloadLocation(&test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getSPDXHomepage(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
|
||||
name: "no metadata",
|
||||
input: pkg.Package{},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "from gem",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.GemMetadata{
|
||||
Homepage: "http://a-place.gov",
|
||||
},
|
||||
},
|
||||
expected: "http://a-place.gov",
|
||||
},
|
||||
{
|
||||
name: "from npm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Homepage: "http://a-place.gov",
|
||||
},
|
||||
},
|
||||
expected: "http://a-place.gov",
|
||||
},
|
||||
{
|
||||
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
|
||||
name: "empty",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Homepage: "",
|
||||
},
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, getSPDXHomepage(&test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getSPDXSourceInfo(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "locations are captured",
|
||||
input: pkg.Package{
|
||||
// note: no type given
|
||||
Locations: []source.Location{
|
||||
{
|
||||
RealPath: "/a-place",
|
||||
VirtualPath: "/b-place",
|
||||
},
|
||||
{
|
||||
RealPath: "/c-place",
|
||||
VirtualPath: "/d-place",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{
|
||||
"from the following paths",
|
||||
"/a-place",
|
||||
"/c-place",
|
||||
},
|
||||
},
|
||||
{
|
||||
// note: no specific support for this
|
||||
input: pkg.Package{
|
||||
Type: pkg.KbPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from the following paths",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.RpmPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from RPM DB",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.ApkPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from APK DB",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.DebPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from DPKG DB",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.NpmPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from installed node module manifest file",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.PythonPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from installed python package manifest file",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.JavaPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from installed java archive",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from installed java archive",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.GemPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from installed gem metadata file",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.GoModulePkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from go module information",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.RustPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from rust cargo manifest",
|
||||
},
|
||||
},
|
||||
}
|
||||
var pkgTypes []pkg.Type
|
||||
for _, test := range tests {
|
||||
t.Run(test.name+" "+string(test.input.Type), func(t *testing.T) {
|
||||
if test.input.Type != "" {
|
||||
pkgTypes = append(pkgTypes, test.input.Type)
|
||||
}
|
||||
actual := getSPDXSourceInfo(&test.input)
|
||||
for _, expected := range test.expected {
|
||||
assert.Contains(t, actual, expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
assert.ElementsMatch(t, pkg.AllPkgs, pkgTypes, "missing one or more package types to test against (maybe a package type was added?)")
|
||||
}
|
||||
|
||||
func Test_getSPDXOriginator(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
|
||||
name: "no metadata",
|
||||
input: pkg.Package{},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "from gem",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.GemMetadata{
|
||||
Authors: []string{
|
||||
"auth1",
|
||||
"auth2",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "auth1",
|
||||
},
|
||||
{
|
||||
name: "from npm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Author: "auth",
|
||||
},
|
||||
},
|
||||
expected: "auth",
|
||||
},
|
||||
{
|
||||
name: "from apk",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Maintainer: "auth",
|
||||
},
|
||||
},
|
||||
expected: "auth",
|
||||
},
|
||||
{
|
||||
name: "from python - just name",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Author: "auth",
|
||||
},
|
||||
},
|
||||
expected: "auth",
|
||||
},
|
||||
{
|
||||
name: "from python - just email",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
AuthorEmail: "auth@auth.gov",
|
||||
},
|
||||
},
|
||||
expected: "auth@auth.gov",
|
||||
},
|
||||
{
|
||||
name: "from python - both name and email",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Author: "auth",
|
||||
AuthorEmail: "auth@auth.gov",
|
||||
},
|
||||
},
|
||||
expected: "auth <auth@auth.gov>",
|
||||
},
|
||||
{
|
||||
name: "from rpm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.RpmdbMetadata{
|
||||
Vendor: "auth",
|
||||
},
|
||||
},
|
||||
expected: "auth",
|
||||
},
|
||||
{
|
||||
name: "from dpkg",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.DpkgMetadata{
|
||||
Maintainer: "auth",
|
||||
},
|
||||
},
|
||||
expected: "auth",
|
||||
},
|
||||
{
|
||||
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
|
||||
name: "empty",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Author: "",
|
||||
},
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, getSPDXOriginator(&test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getSPDXDescription(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
|
||||
name: "no metadata",
|
||||
input: pkg.Package{},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "from apk",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Description: "a description!",
|
||||
},
|
||||
},
|
||||
expected: "a description!",
|
||||
},
|
||||
{
|
||||
name: "from npm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Description: "a description!",
|
||||
},
|
||||
},
|
||||
expected: "a description!",
|
||||
},
|
||||
{
|
||||
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
|
||||
name: "empty",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Homepage: "",
|
||||
},
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, getSPDXDescription(&test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package packages
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/presenter/packages/model/spdx22"
|
||||
"github.com/anchore/syft/internal/spdxlicense"
|
||||
"github.com/anchore/syft/internal/version"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// SPDXJsonPresenter is a SPDX presentation object for the syft results (see https://github.com/spdx/spdx-spec)
|
||||
type SPDXJsonPresenter struct {
|
||||
catalog *pkg.Catalog
|
||||
srcMetadata source.Metadata
|
||||
}
|
||||
|
||||
// NewSPDXJSONPresenter creates a new JSON presenter object for the given cataloging results.
|
||||
func NewSPDXJSONPresenter(catalog *pkg.Catalog, srcMetadata source.Metadata) *SPDXJsonPresenter {
|
||||
return &SPDXJsonPresenter{
|
||||
catalog: catalog,
|
||||
srcMetadata: srcMetadata,
|
||||
}
|
||||
}
|
||||
|
||||
// Present the catalog results to the given writer.
|
||||
func (pres *SPDXJsonPresenter) Present(output io.Writer) error {
|
||||
doc := newSPDXJsonDocument(pres.catalog, pres.srcMetadata)
|
||||
|
||||
enc := json.NewEncoder(output)
|
||||
// prevent > and < from being escaped in the payload
|
||||
enc.SetEscapeHTML(false)
|
||||
enc.SetIndent("", " ")
|
||||
return enc.Encode(&doc)
|
||||
}
|
||||
|
||||
func newSPDXJsonDocument(catalog *pkg.Catalog, srcMetadata source.Metadata) spdx22.Document {
|
||||
var name string
|
||||
switch srcMetadata.Scheme {
|
||||
case source.ImageScheme:
|
||||
name = srcMetadata.ImageMetadata.UserInput
|
||||
case source.DirectoryScheme:
|
||||
name = srcMetadata.Path
|
||||
}
|
||||
|
||||
return spdx22.Document{
|
||||
Element: spdx22.Element{
|
||||
SPDXID: spdx22.ElementID("DOCUMENT").String(),
|
||||
Name: name,
|
||||
},
|
||||
SPDXVersion: spdx22.Version,
|
||||
CreationInfo: spdx22.CreationInfo{
|
||||
Created: time.Now().UTC(),
|
||||
Creators: []string{
|
||||
// note: key-value format derived from the JSON example document examples: https://github.com/spdx/spdx-spec/blob/v2.2/examples/SPDXJSONExample-v2.2.spdx.json
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: " + internal.ApplicationName + "-" + version.FromBuild().Version,
|
||||
},
|
||||
LicenseListVersion: spdxlicense.Version,
|
||||
},
|
||||
DataLicense: "CC0-1.0",
|
||||
DocumentNamespace: fmt.Sprintf("https://anchore.com/syft/image/%s", srcMetadata.ImageMetadata.UserInput),
|
||||
Packages: newSPDXJsonPackages(catalog),
|
||||
}
|
||||
}
|
||||
|
||||
func newSPDXJsonPackages(catalog *pkg.Catalog) []spdx22.Package {
|
||||
results := make([]spdx22.Package, 0)
|
||||
for _, p := range catalog.Sorted() {
|
||||
license := getSPDXLicense(p)
|
||||
|
||||
// note: the license concluded and declared should be the same since we are collecting license information
|
||||
// from the project data itself (the installed package files).
|
||||
results = append(results, spdx22.Package{
|
||||
Description: getSPDXDescription(p),
|
||||
DownloadLocation: getSPDXDownloadLocation(p),
|
||||
ExternalRefs: getSPDXExternalRefs(p),
|
||||
FilesAnalyzed: false,
|
||||
Homepage: getSPDXHomepage(p),
|
||||
LicenseDeclared: license, // The Declared License is what the authors of a project believe govern the package
|
||||
Originator: getSPDXOriginator(p),
|
||||
SourceInfo: getSPDXSourceInfo(p),
|
||||
VersionInfo: p.Version,
|
||||
Item: spdx22.Item{
|
||||
LicenseConcluded: license, // The Concluded License field is the license the SPDX file creator believes governs the package
|
||||
Element: spdx22.Element{
|
||||
SPDXID: spdx22.ElementID(fmt.Sprintf("Package-%+v-%s-%s", p.Type, p.Name, p.Version)).String(),
|
||||
Name: p.Name,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
return results
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package packages
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var updateSpdxJson = flag.Bool("update-spdx-json", false, "update the *.golden files for spdx-json presenters")
|
||||
|
||||
func TestSPDXJSONDirectoryPresenter(t *testing.T) {
|
||||
catalog, metadata, _ := presenterDirectoryInput(t)
|
||||
assertPresenterAgainstGoldenSnapshot(t,
|
||||
NewSPDXJSONPresenter(catalog, metadata),
|
||||
*updateSpdxJson,
|
||||
spdxJsonRedactor,
|
||||
)
|
||||
}
|
||||
|
||||
func TestSPDXJSONImagePresenter(t *testing.T) {
|
||||
testImage := "image-simple"
|
||||
catalog, metadata, _ := presenterImageInput(t, testImage)
|
||||
assertPresenterAgainstGoldenImageSnapshot(t,
|
||||
NewSPDXJSONPresenter(catalog, metadata),
|
||||
testImage,
|
||||
*updateSpdxJson,
|
||||
spdxJsonRedactor,
|
||||
)
|
||||
}
|
||||
|
||||
func spdxJsonRedactor(s []byte) []byte {
|
||||
return regexp.MustCompile(`"created": .*`).ReplaceAll(s, []byte("redacted"))
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
package packages
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/anchore/syft/internal/spdxlicense"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/version"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/spdx/tools-golang/spdx"
|
||||
"github.com/spdx/tools-golang/tvsaver"
|
||||
)
|
||||
|
||||
// SPDXTagValuePresenter is a SPDX presentation object for the syft results (see https://github.com/spdx/spdx-spec)
|
||||
type SPDXTagValuePresenter struct {
|
||||
catalog *pkg.Catalog
|
||||
srcMetadata source.Metadata
|
||||
}
|
||||
|
||||
// NewJSONPresenter creates a new JSON presenter object for the given cataloging results.
|
||||
func NewSPDXTagValuePresenter(catalog *pkg.Catalog, srcMetadata source.Metadata) *SPDXTagValuePresenter {
|
||||
return &SPDXTagValuePresenter{
|
||||
catalog: catalog,
|
||||
srcMetadata: srcMetadata,
|
||||
}
|
||||
}
|
||||
|
||||
// Present the catalog results to the given writer.
|
||||
// nolint: funlen
|
||||
func (pres *SPDXTagValuePresenter) Present(output io.Writer) error {
|
||||
doc := spdx.Document2_2{
|
||||
CreationInfo: &spdx.CreationInfo2_2{
|
||||
// 2.1: SPDX Version; should be in the format "SPDX-2.2"
|
||||
// Cardinality: mandatory, one
|
||||
SPDXVersion: "SPDX-2.2",
|
||||
|
||||
// 2.2: Data License; should be "CC0-1.0"
|
||||
// Cardinality: mandatory, one
|
||||
DataLicense: "CC0-1.0",
|
||||
|
||||
// 2.3: SPDX Identifier; should be "DOCUMENT" to represent mandatory identifier of SPDXRef-DOCUMENT
|
||||
// Cardinality: mandatory, one
|
||||
SPDXIdentifier: spdx.ElementID("DOCUMENT"),
|
||||
|
||||
// 2.4: Document Name
|
||||
// Cardinality: mandatory, one
|
||||
DocumentName: pres.srcMetadata.ImageMetadata.UserInput,
|
||||
|
||||
// 2.5: Document Namespace
|
||||
// Cardinality: mandatory, one
|
||||
// Purpose: Provide an SPDX document specific namespace as a unique absolute Uniform Resource
|
||||
// Identifier (URI) as specified in RFC-3986, with the exception of the ‘#’ delimiter. The SPDX
|
||||
// Document URI cannot contain a URI "part" (e.g. the "#" character), since the ‘#’ is used in SPDX
|
||||
// element URIs (packages, files, snippets, etc) to separate the document namespace from the
|
||||
// element’s SPDX identifier. Additionally, a scheme (e.g. “https:”) is required.
|
||||
|
||||
// The URI must be unique for the SPDX document including the specific version of the SPDX document.
|
||||
// If the SPDX document is updated, thereby creating a new version, a new URI for the updated
|
||||
// document must be used. There can only be one URI for an SPDX document and only one SPDX document
|
||||
// for a given URI.
|
||||
|
||||
// Note that the URI does not have to be accessible. It is only intended to provide a unique ID.
|
||||
// In many cases, the URI will point to a web accessible document, but this should not be assumed
|
||||
// to be the case.
|
||||
|
||||
DocumentNamespace: fmt.Sprintf("https://anchore.com/syft/image/%s", pres.srcMetadata.ImageMetadata.UserInput),
|
||||
|
||||
// 2.6: External Document References
|
||||
// Cardinality: optional, one or many
|
||||
ExternalDocumentReferences: nil,
|
||||
|
||||
// 2.7: License List Version
|
||||
// Cardinality: optional, one
|
||||
LicenseListVersion: spdxlicense.Version,
|
||||
|
||||
// 2.8: Creators: may have multiple keys for Person, Organization
|
||||
// and/or Tool
|
||||
// Cardinality: mandatory, one or many
|
||||
CreatorPersons: nil,
|
||||
CreatorOrganizations: []string{"Anchore, Inc"},
|
||||
CreatorTools: []string{internal.ApplicationName + "-" + version.FromBuild().Version},
|
||||
|
||||
// 2.9: Created: data format YYYY-MM-DDThh:mm:ssZ
|
||||
// Cardinality: mandatory, one
|
||||
Created: time.Now().UTC().Format(time.RFC3339),
|
||||
|
||||
// 2.10: Creator Comment
|
||||
// Cardinality: optional, one
|
||||
CreatorComment: "",
|
||||
|
||||
// 2.11: Document Comment
|
||||
// Cardinality: optional, one
|
||||
DocumentComment: "",
|
||||
},
|
||||
Packages: pres.packages(),
|
||||
}
|
||||
|
||||
return tvsaver.Save2_2(&doc, output)
|
||||
}
|
||||
|
||||
// packages populates all Package Information from the package Catalog (see https://spdx.github.io/spdx-spec/3-package-information/)
|
||||
// nolint: funlen
|
||||
func (pres *SPDXTagValuePresenter) packages() map[spdx.ElementID]*spdx.Package2_2 {
|
||||
results := make(map[spdx.ElementID]*spdx.Package2_2)
|
||||
|
||||
for p := range pres.catalog.Enumerate() {
|
||||
// name should be guaranteed to be unique, but semantically useful and stable
|
||||
id := fmt.Sprintf("Package-%+v-%s", p.Type, p.Name)
|
||||
|
||||
// If the Concluded License is not the same as the Declared License, a written explanation should be provided
|
||||
// in the Comments on License field (section 3.16). With respect to NOASSERTION, a written explanation in
|
||||
// the Comments on License field (section 3.16) is preferred.
|
||||
license := getSPDXLicense(p)
|
||||
|
||||
results[spdx.ElementID(id)] = &spdx.Package2_2{
|
||||
|
||||
// NOT PART OF SPEC
|
||||
// flag: does this "package" contain files that were in fact "unpackaged",
|
||||
// e.g. included directly in the Document without being in a Package?
|
||||
IsUnpackaged: false,
|
||||
|
||||
// 3.1: Package Name
|
||||
// Cardinality: mandatory, one
|
||||
PackageName: p.Name,
|
||||
|
||||
// 3.2: Package SPDX Identifier: "SPDXRef-[idstring]"
|
||||
// Cardinality: mandatory, one
|
||||
PackageSPDXIdentifier: spdx.ElementID(id),
|
||||
|
||||
// 3.3: Package Version
|
||||
// Cardinality: optional, one
|
||||
PackageVersion: p.Version,
|
||||
|
||||
// 3.4: Package File Name
|
||||
// Cardinality: optional, one
|
||||
PackageFileName: "",
|
||||
|
||||
// 3.5: Package Supplier: may have single result for either Person or Organization,
|
||||
// or NOASSERTION
|
||||
// Cardinality: optional, one
|
||||
PackageSupplierPerson: "",
|
||||
PackageSupplierOrganization: "",
|
||||
PackageSupplierNOASSERTION: false,
|
||||
|
||||
// 3.6: Package Originator: may have single result for either Person or Organization,
|
||||
// or NOASSERTION
|
||||
// Cardinality: optional, one
|
||||
PackageOriginatorPerson: "",
|
||||
PackageOriginatorOrganization: "",
|
||||
PackageOriginatorNOASSERTION: false,
|
||||
|
||||
// 3.7: Package Download Location
|
||||
// Cardinality: mandatory, one
|
||||
// NONE if there is no download location whatsoever.
|
||||
// NOASSERTION if:
|
||||
// (i) the SPDX file creator has attempted to but cannot reach a reasonable objective determination;
|
||||
// (ii) the SPDX file creator has made no attempt to determine this field; or
|
||||
// (iii) the SPDX file creator has intentionally provided no information (no meaning should be implied by doing so).
|
||||
PackageDownloadLocation: "NOASSERTION",
|
||||
|
||||
// 3.8: FilesAnalyzed
|
||||
// Cardinality: optional, one; default value is "true" if omitted
|
||||
|
||||
// Purpose: Indicates whether the file content of this package has been available for or subjected to
|
||||
// analysis when creating the SPDX document. If false, indicates packages that represent metadata or
|
||||
// URI references to a project, product, artifact, distribution or a component. If false, the package
|
||||
// must not contain any files.
|
||||
|
||||
// Intent: A package can refer to a project, product, artifact, distribution or a component that is
|
||||
// external to the SPDX document.
|
||||
FilesAnalyzed: false,
|
||||
// NOT PART OF SPEC: did FilesAnalyzed tag appear?
|
||||
IsFilesAnalyzedTagPresent: true,
|
||||
|
||||
// 3.9: Package Verification Code
|
||||
// Cardinality: mandatory, one if filesAnalyzed is true / omitted;
|
||||
// zero (must be omitted) if filesAnalyzed is false
|
||||
PackageVerificationCode: "",
|
||||
// Spec also allows specifying a single file to exclude from the
|
||||
// verification code algorithm; intended to enable exclusion of
|
||||
// the SPDX document file itself.
|
||||
PackageVerificationCodeExcludedFile: "",
|
||||
|
||||
// 3.10: Package Checksum: may have keys for SHA1, SHA256 and/or MD5
|
||||
// Cardinality: optional, one or many
|
||||
|
||||
// 3.10.1 Purpose: Provide an independently reproducible mechanism that permits unique identification of
|
||||
// a specific package that correlates to the data in this SPDX file. This identifier enables a recipient
|
||||
// to determine if any file in the original package has been changed. If the SPDX file is to be included
|
||||
// in a package, this value should not be calculated. The SHA-1 algorithm will be used to provide the
|
||||
// checksum by default.
|
||||
|
||||
// note: based on the purpose above no discovered checksums should be provided, but instead, only
|
||||
// tool-derived checksums.
|
||||
PackageChecksumSHA1: "",
|
||||
PackageChecksumSHA256: "",
|
||||
PackageChecksumMD5: "",
|
||||
|
||||
// 3.11: Package Home Page
|
||||
// Cardinality: optional, one
|
||||
PackageHomePage: "",
|
||||
|
||||
// 3.12: Source Information
|
||||
// Cardinality: optional, one
|
||||
PackageSourceInfo: "",
|
||||
|
||||
// 3.13: Concluded License: SPDX License Expression, "NONE" or "NOASSERTION"
|
||||
// Cardinality: mandatory, one
|
||||
// Purpose: Contain the license the SPDX file creator has concluded as governing the
|
||||
// package or alternative values, if the governing license cannot be determined.
|
||||
PackageLicenseConcluded: license,
|
||||
|
||||
// 3.14: All Licenses Info from Files: SPDX License Expression, "NONE" or "NOASSERTION"
|
||||
// Cardinality: mandatory, one or many if filesAnalyzed is true / omitted;
|
||||
// zero (must be omitted) if filesAnalyzed is false
|
||||
PackageLicenseInfoFromFiles: nil,
|
||||
|
||||
// 3.15: Declared License: SPDX License Expression, "NONE" or "NOASSERTION"
|
||||
// Cardinality: mandatory, one
|
||||
// Purpose: List the licenses that have been declared by the authors of the package.
|
||||
// Any license information that does not originate from the package authors, e.g. license
|
||||
// information from a third party repository, should not be included in this field.
|
||||
PackageLicenseDeclared: license,
|
||||
|
||||
// 3.16: Comments on License
|
||||
// Cardinality: optional, one
|
||||
PackageLicenseComments: "",
|
||||
|
||||
// 3.17: Copyright Text: copyright notice(s) text, "NONE" or "NOASSERTION"
|
||||
// Cardinality: mandatory, one
|
||||
// Purpose: Identify the copyright holders of the package, as well as any dates present. This will be a free form text field extracted from package information files. The options to populate this field are limited to:
|
||||
//
|
||||
// Any text related to a copyright notice, even if not complete;
|
||||
// NONE if the package contains no copyright information whatsoever; or
|
||||
// NOASSERTION, if
|
||||
// (i) the SPDX document creator has made no attempt to determine this field; or
|
||||
// (ii) the SPDX document creator has intentionally provided no information (no meaning should be implied by doing so).
|
||||
//
|
||||
PackageCopyrightText: "NOASSERTION",
|
||||
|
||||
// 3.18: Package Summary Description
|
||||
// Cardinality: optional, one
|
||||
PackageSummary: "",
|
||||
|
||||
// 3.19: Package Detailed Description
|
||||
// Cardinality: optional, one
|
||||
PackageDescription: "",
|
||||
|
||||
// 3.20: Package Comment
|
||||
// Cardinality: optional, one
|
||||
PackageComment: "",
|
||||
|
||||
// 3.21: Package External Reference
|
||||
// Cardinality: optional, one or many
|
||||
PackageExternalReferences: formatSPDXExternalRefs(p),
|
||||
|
||||
// 3.22: Package External Reference Comment
|
||||
// Cardinality: conditional (optional, one) for each External Reference
|
||||
// contained within PackageExternalReference2_1 struct, if present
|
||||
|
||||
// 3.23: Package Attribution Text
|
||||
// Cardinality: optional, one or many
|
||||
PackageAttributionTexts: nil,
|
||||
|
||||
// Files contained in this Package
|
||||
Files: nil,
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func formatSPDXExternalRefs(p *pkg.Package) (refs []*spdx.PackageExternalReference2_2) {
|
||||
for _, ref := range getSPDXExternalRefs(p) {
|
||||
refs = append(refs, &spdx.PackageExternalReference2_2{
|
||||
Category: string(ref.ReferenceCategory),
|
||||
RefType: string(ref.ReferenceType),
|
||||
Locator: ref.ReferenceLocator,
|
||||
ExternalRefComment: ref.Comment,
|
||||
})
|
||||
}
|
||||
return refs
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package packages
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var updateSpdxTagValue = flag.Bool("update-spdx-tv", false, "update the *.golden files for spdx-tv presenters")
|
||||
|
||||
func TestSPDXTagValueDirectoryPresenter(t *testing.T) {
|
||||
catalog, metadata, _ := presenterDirectoryInput(t)
|
||||
assertPresenterAgainstGoldenSnapshot(t,
|
||||
NewSPDXTagValuePresenter(catalog, metadata),
|
||||
*updateSpdxTagValue,
|
||||
spdxTagValueRedactor,
|
||||
)
|
||||
}
|
||||
|
||||
func TestSPDXTagValueImagePresenter(t *testing.T) {
|
||||
testImage := "image-simple"
|
||||
catalog, metadata, _ := presenterImageInput(t, testImage)
|
||||
assertPresenterAgainstGoldenImageSnapshot(t,
|
||||
NewSPDXTagValuePresenter(catalog, metadata),
|
||||
testImage,
|
||||
*updateSpdxTagValue,
|
||||
spdxTagValueRedactor,
|
||||
)
|
||||
}
|
||||
|
||||
func spdxTagValueRedactor(s []byte) []byte {
|
||||
return regexp.MustCompile(`Created: .*`).ReplaceAll(s, []byte("redacted"))
|
||||
}
|
||||
@@ -1,72 +1,22 @@
|
||||
package packages
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/filetree"
|
||||
|
||||
"github.com/go-test/deep"
|
||||
|
||||
"github.com/anchore/go-testutils"
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
)
|
||||
|
||||
var updateTablePresenterGoldenFiles = flag.Bool("update-table", false, "update the *.golden files for table presenters")
|
||||
|
||||
func TestTablePresenter(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
testImage := "image-simple"
|
||||
|
||||
catalog := pkg.NewCatalog()
|
||||
img := imagetest.GetFixtureImage(t, "docker-archive", testImage)
|
||||
|
||||
_, ref1, _ := img.SquashedTree().File("/somefile-1.txt", filetree.FollowBasenameLinks)
|
||||
_, ref2, _ := img.SquashedTree().File("/somefile-2.txt", filetree.FollowBasenameLinks)
|
||||
|
||||
// populate catalog with test data
|
||||
catalog.Add(pkg.Package{
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
Locations: []source.Location{
|
||||
source.NewLocationFromImage(string(ref1.RealPath), *ref1, img),
|
||||
},
|
||||
Type: pkg.DebPkg,
|
||||
})
|
||||
catalog.Add(pkg.Package{
|
||||
Name: "package-2",
|
||||
Version: "2.0.1",
|
||||
Locations: []source.Location{
|
||||
source.NewLocationFromImage(string(ref2.RealPath), *ref2, img),
|
||||
},
|
||||
Type: pkg.DebPkg,
|
||||
})
|
||||
|
||||
pres := NewTablePresenter(catalog)
|
||||
|
||||
// run presenter
|
||||
err := pres.Present(&buffer)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual := buffer.Bytes()
|
||||
|
||||
if *updateTablePresenterGoldenFiles {
|
||||
testutils.UpdateGoldenFileContents(t, actual)
|
||||
}
|
||||
|
||||
var expected = testutils.GetGoldenFileContents(t)
|
||||
|
||||
if !bytes.Equal(expected, actual) {
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(string(actual), string(expected), true)
|
||||
t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs))
|
||||
}
|
||||
catalog, _, _ := presenterImageInput(t, testImage)
|
||||
assertPresenterAgainstGoldenImageSnapshot(t,
|
||||
NewTablePresenter(catalog),
|
||||
testImage,
|
||||
*updateTablePresenterGoldenFiles,
|
||||
)
|
||||
}
|
||||
|
||||
func TestRemoveDuplicateRows(t *testing.T) {
|
||||
|
||||
+9
-10
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.2" version="1" serialNumber="urn:uuid:544b9f08-b39d-4d0c-8723-32609567e8ed">
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.2" version="1" serialNumber="urn:uuid:5404937f-72d6-44a2-8e9b-954305ecb4f6">
|
||||
<metadata>
|
||||
<timestamp>2020-12-28T13:56:29-05:00</timestamp>
|
||||
<timestamp>2021-06-23T13:40:33-04:00</timestamp>
|
||||
<tools>
|
||||
<tool>
|
||||
<vendor>anchore</vendor>
|
||||
@@ -16,20 +16,19 @@
|
||||
</metadata>
|
||||
<components>
|
||||
<component type="library">
|
||||
<name>package1</name>
|
||||
<name>package-1</name>
|
||||
<version>1.0.1</version>
|
||||
</component>
|
||||
<component type="library">
|
||||
<name>package2</name>
|
||||
<version>2.0.1</version>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>MIT</name>
|
||||
</license>
|
||||
<license>
|
||||
<name>Apache-v2</name>
|
||||
</license>
|
||||
</licenses>
|
||||
<purl>a-purl-2</purl>
|
||||
</component>
|
||||
<component type="library">
|
||||
<name>package-2</name>
|
||||
<version>2.0.1</version>
|
||||
<purl>a-purl-2</purl>
|
||||
</component>
|
||||
</components>
|
||||
</bom>
|
||||
+9
-12
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.2" version="1" serialNumber="urn:uuid:e1515688-2fd6-422a-acb2-97405a44cdfe">
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.2" version="1" serialNumber="urn:uuid:e34bad2e-cd27-483c-86dc-f4e26d6103b0">
|
||||
<metadata>
|
||||
<timestamp>2020-12-28T13:56:29-05:00</timestamp>
|
||||
<timestamp>2021-06-23T13:40:33-04:00</timestamp>
|
||||
<tools>
|
||||
<tool>
|
||||
<vendor>anchore</vendor>
|
||||
@@ -16,22 +16,19 @@
|
||||
</metadata>
|
||||
<components>
|
||||
<component type="library">
|
||||
<name>package1</name>
|
||||
<name>package-1</name>
|
||||
<version>1.0.1</version>
|
||||
<purl>the-purl-1</purl>
|
||||
</component>
|
||||
<component type="library">
|
||||
<name>package2</name>
|
||||
<version>2.0.1</version>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>MIT</name>
|
||||
</license>
|
||||
<license>
|
||||
<name>Apache-v2</name>
|
||||
</license>
|
||||
</licenses>
|
||||
<purl>the-purl-2</purl>
|
||||
<purl>a-purl-1</purl>
|
||||
</component>
|
||||
<component type="library">
|
||||
<name>package-2</name>
|
||||
<version>2.0.1</version>
|
||||
<purl>a-purl-2</purl>
|
||||
</component>
|
||||
</components>
|
||||
</bom>
|
||||
+3
-3
@@ -66,9 +66,9 @@
|
||||
"target": "/some/path"
|
||||
},
|
||||
"distro": {
|
||||
"name": "",
|
||||
"version": "",
|
||||
"idLike": ""
|
||||
"name": "debian",
|
||||
"version": "1.2.3",
|
||||
"idLike": "like!"
|
||||
},
|
||||
"descriptor": {
|
||||
"name": "syft",
|
||||
+3
-3
@@ -93,9 +93,9 @@
|
||||
}
|
||||
},
|
||||
"distro": {
|
||||
"name": "",
|
||||
"version": "",
|
||||
"idLike": ""
|
||||
"name": "debian",
|
||||
"version": "1.2.3",
|
||||
"idLike": "like!"
|
||||
},
|
||||
"descriptor": {
|
||||
"name": "syft",
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"name": "/some/path",
|
||||
"spdxVersion": "SPDX-2.2",
|
||||
"creationInfo": {
|
||||
"created": "2021-06-23T17:48:32.734847Z",
|
||||
"creators": [
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: syft-[not provided]"
|
||||
],
|
||||
"licenseListVersion": "3.13"
|
||||
},
|
||||
"dataLicense": "CC0-1.0",
|
||||
"documentNamespace": "https://anchore.com/syft/image/",
|
||||
"packages": [
|
||||
{
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-1.0.1",
|
||||
"name": "package-1",
|
||||
"licenseConcluded": "MIT",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"externalRefs": [
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||
"referenceType": "cpe23Type"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE_MANAGER",
|
||||
"referenceLocator": "a-purl-2",
|
||||
"referenceType": "purl"
|
||||
}
|
||||
],
|
||||
"filesAnalyzed": false,
|
||||
"licenseDeclared": "MIT",
|
||||
"sourceInfo": "acquired package info from installed python package manifest file: /some/path/pkg1",
|
||||
"versionInfo": "1.0.1"
|
||||
},
|
||||
{
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-2.0.1",
|
||||
"name": "package-2",
|
||||
"licenseConcluded": "NONE",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"externalRefs": [
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||
"referenceType": "cpe23Type"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE_MANAGER",
|
||||
"referenceLocator": "a-purl-2",
|
||||
"referenceType": "purl"
|
||||
}
|
||||
],
|
||||
"filesAnalyzed": false,
|
||||
"licenseDeclared": "NONE",
|
||||
"sourceInfo": "acquired package info from DPKG DB: /some/path/pkg1",
|
||||
"versionInfo": "2.0.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"name": "user-image-input",
|
||||
"spdxVersion": "SPDX-2.2",
|
||||
"creationInfo": {
|
||||
"created": "2021-06-23T17:48:32.7379Z",
|
||||
"creators": [
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: syft-[not provided]"
|
||||
],
|
||||
"licenseListVersion": "3.13"
|
||||
},
|
||||
"dataLicense": "CC0-1.0",
|
||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input",
|
||||
"packages": [
|
||||
{
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-1.0.1",
|
||||
"name": "package-1",
|
||||
"licenseConcluded": "MIT",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"externalRefs": [
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceLocator": "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*",
|
||||
"referenceType": "cpe23Type"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE_MANAGER",
|
||||
"referenceLocator": "a-purl-1",
|
||||
"referenceType": "purl"
|
||||
}
|
||||
],
|
||||
"filesAnalyzed": false,
|
||||
"licenseDeclared": "MIT",
|
||||
"sourceInfo": "acquired package info from installed python package manifest file: /somefile-1.txt",
|
||||
"versionInfo": "1.0.1"
|
||||
},
|
||||
{
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-2.0.1",
|
||||
"name": "package-2",
|
||||
"licenseConcluded": "NONE",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"externalRefs": [
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||
"referenceType": "cpe23Type"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE_MANAGER",
|
||||
"referenceLocator": "a-purl-2",
|
||||
"referenceType": "purl"
|
||||
}
|
||||
],
|
||||
"filesAnalyzed": false,
|
||||
"licenseDeclared": "NONE",
|
||||
"sourceInfo": "acquired package info from DPKG DB: /somefile-2.txt",
|
||||
"versionInfo": "2.0.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
SPDXVersion: SPDX-2.2
|
||||
DataLicense: CC0-1.0
|
||||
SPDXID: SPDXRef-DOCUMENT
|
||||
DocumentNamespace: https://anchore.com/syft/image/
|
||||
LicenseListVersion: 3.13
|
||||
Creator: Organization: Anchore, Inc
|
||||
Creator: Tool: syft-[not provided]
|
||||
Created: 2021-06-23T17:49:25Z
|
||||
|
||||
##### Package: package-2
|
||||
|
||||
PackageName: package-2
|
||||
SPDXID: SPDXRef-Package-deb-package-2
|
||||
PackageVersion: 2.0.1
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageLicenseConcluded: NONE
|
||||
PackageLicenseDeclared: NONE
|
||||
PackageCopyrightText: NOASSERTION
|
||||
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:*
|
||||
ExternalRef: PACKAGE_MANAGER purl a-purl-2
|
||||
|
||||
##### Package: package-1
|
||||
|
||||
PackageName: package-1
|
||||
SPDXID: SPDXRef-Package-python-package-1
|
||||
PackageVersion: 1.0.1
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageLicenseConcluded: MIT
|
||||
PackageLicenseDeclared: MIT
|
||||
PackageCopyrightText: NOASSERTION
|
||||
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:*
|
||||
ExternalRef: PACKAGE_MANAGER purl a-purl-2
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
SPDXVersion: SPDX-2.2
|
||||
DataLicense: CC0-1.0
|
||||
SPDXID: SPDXRef-DOCUMENT
|
||||
DocumentName: user-image-input
|
||||
DocumentNamespace: https://anchore.com/syft/image/user-image-input
|
||||
LicenseListVersion: 3.13
|
||||
Creator: Organization: Anchore, Inc
|
||||
Creator: Tool: syft-[not provided]
|
||||
Created: 2021-06-23T17:49:25Z
|
||||
|
||||
##### Package: package-2
|
||||
|
||||
PackageName: package-2
|
||||
SPDXID: SPDXRef-Package-deb-package-2
|
||||
PackageVersion: 2.0.1
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageLicenseConcluded: NONE
|
||||
PackageLicenseDeclared: NONE
|
||||
PackageCopyrightText: NOASSERTION
|
||||
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:*
|
||||
ExternalRef: PACKAGE_MANAGER purl a-purl-2
|
||||
|
||||
##### Package: package-1
|
||||
|
||||
PackageName: package-1
|
||||
SPDXID: SPDXRef-Package-python-package-1
|
||||
PackageVersion: 1.0.1
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageLicenseConcluded: MIT
|
||||
PackageLicenseDeclared: MIT
|
||||
PackageCopyrightText: NOASSERTION
|
||||
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:1:*:*:*:*:*:*:*
|
||||
ExternalRef: PACKAGE_MANAGER purl a-purl-1
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
NAME VERSION TYPE
|
||||
package-1 1.0.1 deb
|
||||
package-2 2.0.1 deb
|
||||
NAME VERSION TYPE
|
||||
package-1 1.0.1 python
|
||||
package-2 2.0.1 deb
|
||||
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
[Path: /some/path]
|
||||
[package-1]
|
||||
Version: 1.0.1
|
||||
Type: deb
|
||||
Found by:
|
||||
Type: python
|
||||
Found by: the-cataloger-1
|
||||
|
||||
[package-2]
|
||||
Version: 2.0.1
|
||||
Type: deb
|
||||
Found by:
|
||||
Found by: the-cataloger-2
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
[Image]
|
||||
Layer: 0
|
||||
Digest: sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59
|
||||
Size: 22
|
||||
MediaType: application/vnd.docker.image.rootfs.diff.tar.gzip
|
||||
|
||||
Layer: 1
|
||||
Digest: sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec
|
||||
Size: 16
|
||||
MediaType: application/vnd.docker.image.rootfs.diff.tar.gzip
|
||||
|
||||
[package-1]
|
||||
Version: 1.0.1
|
||||
Type: python
|
||||
Found by: the-cataloger-1
|
||||
|
||||
[package-2]
|
||||
Version: 2.0.1
|
||||
Type: deb
|
||||
Found by: the-cataloger-2
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
[Image]
|
||||
Layer: 0
|
||||
Digest: sha256:ad8ecdc058976c07e7e347cb89fa9ad86a294b5ceaae6d09713fb035f84115abf3c4a2388a4af3aa60f13b94f4c6846930bdf53
|
||||
Size: 22
|
||||
MediaType: application/vnd.docker.image.rootfs.diff.tar.gzip
|
||||
|
||||
Layer: 1
|
||||
Digest: sha256:ad8ecdc058976c07e7e347cb89fa9ad86a294b5ceaae6d09713fb035f84115abf3c4a2388a4af3aa60f13b94f4c6846930bdf53
|
||||
Size: 16
|
||||
MediaType: application/vnd.docker.image.rootfs.diff.tar.gzip
|
||||
|
||||
[package-1]
|
||||
Version: 1.0.1
|
||||
Type: deb
|
||||
Found by: dpkg
|
||||
|
||||
[package-2]
|
||||
Version: 2.0.1
|
||||
Type: deb
|
||||
Found by: dpkg
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
[Path: /some/path]
|
||||
[package-1]
|
||||
Version: 1.0.1
|
||||
Type: deb
|
||||
Found by:
|
||||
|
||||
[package-2]
|
||||
Version: 2.0.1
|
||||
Type: deb
|
||||
Found by:
|
||||
|
||||
@@ -1,127 +1,26 @@
|
||||
package packages
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/filetree"
|
||||
|
||||
"github.com/anchore/go-testutils"
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
)
|
||||
|
||||
var updateTextPresenterGoldenFiles = flag.Bool("update-text", false, "update the *.golden files for text presenters")
|
||||
|
||||
func TestTextDirPresenter(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
catalog := pkg.NewCatalog()
|
||||
|
||||
// populate catalog with test data
|
||||
catalog.Add(pkg.Package{
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
Type: pkg.DebPkg,
|
||||
})
|
||||
catalog.Add(pkg.Package{
|
||||
Name: "package-2",
|
||||
Version: "2.0.1",
|
||||
Type: pkg.DebPkg,
|
||||
})
|
||||
|
||||
s, err := source.NewFromDirectory("/some/path")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create source: %+v", err)
|
||||
}
|
||||
pres := NewTextPresenter(catalog, s.Metadata)
|
||||
|
||||
// run presenter
|
||||
err = pres.Present(&buffer)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual := buffer.Bytes()
|
||||
|
||||
if *updateTextPresenterGoldenFiles {
|
||||
testutils.UpdateGoldenFileContents(t, actual)
|
||||
}
|
||||
|
||||
var expected = testutils.GetGoldenFileContents(t)
|
||||
|
||||
if !bytes.Equal(expected, actual) {
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(string(actual), string(expected), true)
|
||||
t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs))
|
||||
}
|
||||
|
||||
func TestTextDirectoryPresenter(t *testing.T) {
|
||||
catalog, metadata, _ := presenterDirectoryInput(t)
|
||||
assertPresenterAgainstGoldenSnapshot(t,
|
||||
NewTextPresenter(catalog, metadata),
|
||||
*updateTextPresenterGoldenFiles,
|
||||
)
|
||||
}
|
||||
|
||||
type PackageInfo struct {
|
||||
Name string
|
||||
Version string
|
||||
}
|
||||
|
||||
func TestTextImgPresenter(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
catalog := pkg.NewCatalog()
|
||||
img := imagetest.GetFixtureImage(t, "docker-archive", "image-simple")
|
||||
|
||||
_, ref1, _ := img.SquashedTree().File("/somefile-1.txt", filetree.FollowBasenameLinks)
|
||||
_, ref2, _ := img.SquashedTree().File("/somefile-2.txt", filetree.FollowBasenameLinks)
|
||||
|
||||
// populate catalog with test data
|
||||
catalog.Add(pkg.Package{
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
Locations: []source.Location{
|
||||
source.NewLocationFromImage(string(ref1.RealPath), *ref1, img),
|
||||
},
|
||||
FoundBy: "dpkg",
|
||||
Type: pkg.DebPkg,
|
||||
})
|
||||
catalog.Add(pkg.Package{
|
||||
Name: "package-2",
|
||||
Version: "2.0.1",
|
||||
Locations: []source.Location{
|
||||
source.NewLocationFromImage(string(ref2.RealPath), *ref2, img),
|
||||
},
|
||||
FoundBy: "dpkg",
|
||||
Metadata: PackageInfo{Name: "package-2", Version: "1.0.2"},
|
||||
Type: pkg.DebPkg,
|
||||
})
|
||||
|
||||
// stub out all the digests so that they don't affect tests comparisons
|
||||
// TODO: update with stereoscope test utils feature when this issue is resolved: https://github.com/anchore/stereoscope/issues/43
|
||||
for _, l := range img.Layers {
|
||||
l.Metadata.Digest = "sha256:ad8ecdc058976c07e7e347cb89fa9ad86a294b5ceaae6d09713fb035f84115abf3c4a2388a4af3aa60f13b94f4c6846930bdf53"
|
||||
}
|
||||
|
||||
s, err := source.NewFromImage(img, "user-image-input")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pres := NewTextPresenter(catalog, s.Metadata)
|
||||
// run presenter
|
||||
err = pres.Present(&buffer)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual := buffer.Bytes()
|
||||
if *updateTextPresenterGoldenFiles {
|
||||
testutils.UpdateGoldenFileContents(t, actual)
|
||||
}
|
||||
|
||||
var expected = testutils.GetGoldenFileContents(t)
|
||||
|
||||
if !bytes.Equal(expected, actual) {
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(string(actual), string(expected), true)
|
||||
t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs))
|
||||
}
|
||||
|
||||
func TestTextImagePresenter(t *testing.T) {
|
||||
testImage := "image-simple"
|
||||
catalog, metadata, _ := presenterImageInput(t, testImage)
|
||||
assertPresenterAgainstGoldenImageSnapshot(t,
|
||||
NewTextPresenter(catalog, metadata),
|
||||
testImage,
|
||||
*updateTextPresenterGoldenFiles,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
package packages
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/go-testutils"
|
||||
"github.com/anchore/stereoscope/pkg/filetree"
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/presenter"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type redactor func(s []byte) []byte
|
||||
|
||||
func assertPresenterAgainstGoldenImageSnapshot(t *testing.T, pres presenter.Presenter, testImage string, updateSnapshot bool, redactors ...redactor) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
// grab the latest image contents and persist
|
||||
if updateSnapshot {
|
||||
imagetest.UpdateGoldenFixtureImage(t, testImage)
|
||||
}
|
||||
|
||||
err := pres.Present(&buffer)
|
||||
assert.NoError(t, err)
|
||||
actual := buffer.Bytes()
|
||||
|
||||
// replace the expected snapshot contents with the current presenter contents
|
||||
if updateSnapshot {
|
||||
testutils.UpdateGoldenFileContents(t, actual)
|
||||
}
|
||||
|
||||
var expected = testutils.GetGoldenFileContents(t)
|
||||
|
||||
// remove dynamic values, which should be tested independently
|
||||
for _, r := range redactors {
|
||||
actual = r(actual)
|
||||
expected = r(expected)
|
||||
}
|
||||
|
||||
// assert that the golden file snapshot matches the actual contents
|
||||
if !bytes.Equal(expected, actual) {
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(string(expected), string(actual), true)
|
||||
t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs))
|
||||
}
|
||||
}
|
||||
|
||||
func assertPresenterAgainstGoldenSnapshot(t *testing.T, pres presenter.Presenter, updateSnapshot bool, redactors ...redactor) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
err := pres.Present(&buffer)
|
||||
assert.NoError(t, err)
|
||||
actual := buffer.Bytes()
|
||||
|
||||
// replace the expected snapshot contents with the current presenter contents
|
||||
if updateSnapshot {
|
||||
testutils.UpdateGoldenFileContents(t, actual)
|
||||
}
|
||||
|
||||
var expected = testutils.GetGoldenFileContents(t)
|
||||
|
||||
// remove dynamic values, which should be tested independently
|
||||
for _, r := range redactors {
|
||||
actual = r(actual)
|
||||
expected = r(expected)
|
||||
}
|
||||
|
||||
if !bytes.Equal(expected, actual) {
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(string(expected), string(actual), true)
|
||||
t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs))
|
||||
}
|
||||
}
|
||||
|
||||
func presenterImageInput(t testing.TB, testImage string) (*pkg.Catalog, source.Metadata, *distro.Distro) {
|
||||
t.Helper()
|
||||
catalog := pkg.NewCatalog()
|
||||
img := imagetest.GetGoldenFixtureImage(t, testImage)
|
||||
|
||||
_, ref1, _ := img.SquashedTree().File("/somefile-1.txt", filetree.FollowBasenameLinks)
|
||||
_, ref2, _ := img.SquashedTree().File("/somefile-2.txt", filetree.FollowBasenameLinks)
|
||||
|
||||
// populate catalog with test data
|
||||
catalog.Add(pkg.Package{
|
||||
ID: "package-1-id",
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
Locations: []source.Location{
|
||||
source.NewLocationFromImage(string(ref1.RealPath), *ref1, img),
|
||||
},
|
||||
Type: pkg.PythonPkg,
|
||||
FoundBy: "the-cataloger-1",
|
||||
Language: pkg.Python,
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
Licenses: []string{"MIT"},
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
},
|
||||
PURL: "a-purl-1",
|
||||
CPEs: []pkg.CPE{
|
||||
must(pkg.NewCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*")),
|
||||
},
|
||||
})
|
||||
catalog.Add(pkg.Package{
|
||||
ID: "package-2-id",
|
||||
Name: "package-2",
|
||||
Version: "2.0.1",
|
||||
Locations: []source.Location{
|
||||
source.NewLocationFromImage(string(ref2.RealPath), *ref2, img),
|
||||
},
|
||||
Type: pkg.DebPkg,
|
||||
FoundBy: "the-cataloger-2",
|
||||
MetadataType: pkg.DpkgMetadataType,
|
||||
Metadata: pkg.DpkgMetadata{
|
||||
Package: "package-2",
|
||||
Version: "2.0.1",
|
||||
},
|
||||
PURL: "a-purl-2",
|
||||
CPEs: []pkg.CPE{
|
||||
must(pkg.NewCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*")),
|
||||
},
|
||||
})
|
||||
|
||||
// this is a hard coded value that is not given by the fixture helper and must be provided manually
|
||||
img.Metadata.ManifestDigest = "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368"
|
||||
|
||||
src, err := source.NewFromImage(img, "user-image-input")
|
||||
assert.NoError(t, err)
|
||||
|
||||
dist, err := distro.NewDistro(distro.Debian, "1.2.3", "like!")
|
||||
assert.NoError(t, err)
|
||||
|
||||
return catalog, src.Metadata, &dist
|
||||
}
|
||||
|
||||
func presenterDirectoryInput(t testing.TB) (*pkg.Catalog, source.Metadata, *distro.Distro) {
|
||||
catalog := pkg.NewCatalog()
|
||||
|
||||
// populate catalog with test data
|
||||
catalog.Add(pkg.Package{
|
||||
ID: "package-1-id",
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
Type: pkg.PythonPkg,
|
||||
FoundBy: "the-cataloger-1",
|
||||
Locations: []source.Location{
|
||||
{RealPath: "/some/path/pkg1"},
|
||||
},
|
||||
Language: pkg.Python,
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
Licenses: []string{"MIT"},
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
},
|
||||
PURL: "a-purl-2",
|
||||
CPEs: []pkg.CPE{
|
||||
must(pkg.NewCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*")),
|
||||
},
|
||||
})
|
||||
catalog.Add(pkg.Package{
|
||||
ID: "package-2-id",
|
||||
Name: "package-2",
|
||||
Version: "2.0.1",
|
||||
Type: pkg.DebPkg,
|
||||
FoundBy: "the-cataloger-2",
|
||||
Locations: []source.Location{
|
||||
{RealPath: "/some/path/pkg1"},
|
||||
},
|
||||
MetadataType: pkg.DpkgMetadataType,
|
||||
Metadata: pkg.DpkgMetadata{
|
||||
Package: "package-2",
|
||||
Version: "2.0.1",
|
||||
},
|
||||
PURL: "a-purl-2",
|
||||
CPEs: []pkg.CPE{
|
||||
must(pkg.NewCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*")),
|
||||
},
|
||||
})
|
||||
|
||||
dist, err := distro.NewDistro(distro.Debian, "1.2.3", "like!")
|
||||
assert.NoError(t, err)
|
||||
|
||||
src, err := source.NewFromDirectory("/some/path")
|
||||
assert.NoError(t, err)
|
||||
|
||||
return catalog, src.Metadata, &dist
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
// This program generates license_list.go.
|
||||
const (
|
||||
source = "license_list.go"
|
||||
url = "https://spdx.org/licenses/licenses.json"
|
||||
)
|
||||
|
||||
var tmp = template.Must(template.New("").Parse(`// Code generated by go generate; DO NOT EDIT.
|
||||
// This file was generated by robots at {{ .Timestamp }}
|
||||
// using data from {{ .URL }}
|
||||
package spdxlicense
|
||||
|
||||
const Version = {{ printf "%q" .Version }}
|
||||
|
||||
var licenseIDs = map[string]string{
|
||||
{{- range $k, $v := .LicenseIDs }}
|
||||
{{ printf "%q" $k }}: {{ printf "%q" $v }},
|
||||
{{- end }}
|
||||
}
|
||||
`))
|
||||
|
||||
type LicenseList struct {
|
||||
Version string `json:"licenseListVersion"`
|
||||
Licenses []struct {
|
||||
ID string `json:"licenseId"`
|
||||
Name string `json:"name"`
|
||||
Text string `json:"licenseText"`
|
||||
Deprecated bool `json:"isDeprecatedLicenseId"`
|
||||
OSIApproved bool `json:"isOsiApproved"`
|
||||
SeeAlso []string `json:"seeAlso"`
|
||||
} `json:"licenses"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to get licenses list: %+v", err)
|
||||
}
|
||||
|
||||
var result LicenseList
|
||||
if err = json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
log.Fatalf("unable to decode license list: %+v", err)
|
||||
}
|
||||
|
||||
f, err := os.Create(source)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to create %q: %+v", source, err)
|
||||
}
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
log.Fatalf("unable to close %q: %+v", source, err)
|
||||
}
|
||||
}()
|
||||
|
||||
var licenseIDs = make(map[string]string)
|
||||
for _, l := range result.Licenses {
|
||||
cleanID := strings.ToLower(l.ID)
|
||||
if _, exists := licenseIDs[cleanID]; exists {
|
||||
log.Fatalf("duplicate license ID found: %q", cleanID)
|
||||
}
|
||||
licenseIDs[cleanID] = l.ID
|
||||
}
|
||||
|
||||
err = tmp.Execute(f, struct {
|
||||
Timestamp time.Time
|
||||
URL string
|
||||
Version string
|
||||
LicenseIDs map[string]string
|
||||
}{
|
||||
Timestamp: time.Now(),
|
||||
URL: url,
|
||||
Version: result.Version,
|
||||
LicenseIDs: licenseIDs,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("unable to generate template: %+v", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package spdxlicense
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:generate go run generate_license_list.go
|
||||
|
||||
func ID(id string) (string, bool) {
|
||||
value, exists := licenseIDs[strings.ToLower(id)]
|
||||
return value, exists
|
||||
}
|
||||
@@ -0,0 +1,469 @@
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
// This file was generated by robots at 2021-06-23 11:13:41.969613 -0400 EDT m=+0.325660925
|
||||
// using data from https://spdx.org/licenses/licenses.json
|
||||
package spdxlicense
|
||||
|
||||
const Version = "3.13"
|
||||
|
||||
var licenseIDs = map[string]string{
|
||||
"0bsd": "0BSD",
|
||||
"aal": "AAL",
|
||||
"abstyles": "Abstyles",
|
||||
"adobe-2006": "Adobe-2006",
|
||||
"adobe-glyph": "Adobe-Glyph",
|
||||
"adsl": "ADSL",
|
||||
"afl-1.1": "AFL-1.1",
|
||||
"afl-1.2": "AFL-1.2",
|
||||
"afl-2.0": "AFL-2.0",
|
||||
"afl-2.1": "AFL-2.1",
|
||||
"afl-3.0": "AFL-3.0",
|
||||
"afmparse": "Afmparse",
|
||||
"agpl-1.0": "AGPL-1.0",
|
||||
"agpl-1.0-only": "AGPL-1.0-only",
|
||||
"agpl-1.0-or-later": "AGPL-1.0-or-later",
|
||||
"agpl-3.0": "AGPL-3.0",
|
||||
"agpl-3.0-only": "AGPL-3.0-only",
|
||||
"agpl-3.0-or-later": "AGPL-3.0-or-later",
|
||||
"aladdin": "Aladdin",
|
||||
"amdplpa": "AMDPLPA",
|
||||
"aml": "AML",
|
||||
"ampas": "AMPAS",
|
||||
"antlr-pd": "ANTLR-PD",
|
||||
"antlr-pd-fallback": "ANTLR-PD-fallback",
|
||||
"apache-1.0": "Apache-1.0",
|
||||
"apache-1.1": "Apache-1.1",
|
||||
"apache-2.0": "Apache-2.0",
|
||||
"apafml": "APAFML",
|
||||
"apl-1.0": "APL-1.0",
|
||||
"apsl-1.0": "APSL-1.0",
|
||||
"apsl-1.1": "APSL-1.1",
|
||||
"apsl-1.2": "APSL-1.2",
|
||||
"apsl-2.0": "APSL-2.0",
|
||||
"artistic-1.0": "Artistic-1.0",
|
||||
"artistic-1.0-cl8": "Artistic-1.0-cl8",
|
||||
"artistic-1.0-perl": "Artistic-1.0-Perl",
|
||||
"artistic-2.0": "Artistic-2.0",
|
||||
"bahyph": "Bahyph",
|
||||
"barr": "Barr",
|
||||
"beerware": "Beerware",
|
||||
"bittorrent-1.0": "BitTorrent-1.0",
|
||||
"bittorrent-1.1": "BitTorrent-1.1",
|
||||
"blessing": "blessing",
|
||||
"blueoak-1.0.0": "BlueOak-1.0.0",
|
||||
"borceux": "Borceux",
|
||||
"bsd-1-clause": "BSD-1-Clause",
|
||||
"bsd-2-clause": "BSD-2-Clause",
|
||||
"bsd-2-clause-freebsd": "BSD-2-Clause-FreeBSD",
|
||||
"bsd-2-clause-netbsd": "BSD-2-Clause-NetBSD",
|
||||
"bsd-2-clause-patent": "BSD-2-Clause-Patent",
|
||||
"bsd-2-clause-views": "BSD-2-Clause-Views",
|
||||
"bsd-3-clause": "BSD-3-Clause",
|
||||
"bsd-3-clause-attribution": "BSD-3-Clause-Attribution",
|
||||
"bsd-3-clause-clear": "BSD-3-Clause-Clear",
|
||||
"bsd-3-clause-lbnl": "BSD-3-Clause-LBNL",
|
||||
"bsd-3-clause-modification": "BSD-3-Clause-Modification",
|
||||
"bsd-3-clause-no-military-license": "BSD-3-Clause-No-Military-License",
|
||||
"bsd-3-clause-no-nuclear-license": "BSD-3-Clause-No-Nuclear-License",
|
||||
"bsd-3-clause-no-nuclear-license-2014": "BSD-3-Clause-No-Nuclear-License-2014",
|
||||
"bsd-3-clause-no-nuclear-warranty": "BSD-3-Clause-No-Nuclear-Warranty",
|
||||
"bsd-3-clause-open-mpi": "BSD-3-Clause-Open-MPI",
|
||||
"bsd-4-clause": "BSD-4-Clause",
|
||||
"bsd-4-clause-shortened": "BSD-4-Clause-Shortened",
|
||||
"bsd-4-clause-uc": "BSD-4-Clause-UC",
|
||||
"bsd-protection": "BSD-Protection",
|
||||
"bsd-source-code": "BSD-Source-Code",
|
||||
"bsl-1.0": "BSL-1.0",
|
||||
"busl-1.1": "BUSL-1.1",
|
||||
"bzip2-1.0.5": "bzip2-1.0.5",
|
||||
"bzip2-1.0.6": "bzip2-1.0.6",
|
||||
"c-uda-1.0": "C-UDA-1.0",
|
||||
"cal-1.0": "CAL-1.0",
|
||||
"cal-1.0-combined-work-exception": "CAL-1.0-Combined-Work-Exception",
|
||||
"caldera": "Caldera",
|
||||
"catosl-1.1": "CATOSL-1.1",
|
||||
"cc-by-1.0": "CC-BY-1.0",
|
||||
"cc-by-2.0": "CC-BY-2.0",
|
||||
"cc-by-2.5": "CC-BY-2.5",
|
||||
"cc-by-3.0": "CC-BY-3.0",
|
||||
"cc-by-3.0-at": "CC-BY-3.0-AT",
|
||||
"cc-by-3.0-us": "CC-BY-3.0-US",
|
||||
"cc-by-4.0": "CC-BY-4.0",
|
||||
"cc-by-nc-1.0": "CC-BY-NC-1.0",
|
||||
"cc-by-nc-2.0": "CC-BY-NC-2.0",
|
||||
"cc-by-nc-2.5": "CC-BY-NC-2.5",
|
||||
"cc-by-nc-3.0": "CC-BY-NC-3.0",
|
||||
"cc-by-nc-4.0": "CC-BY-NC-4.0",
|
||||
"cc-by-nc-nd-1.0": "CC-BY-NC-ND-1.0",
|
||||
"cc-by-nc-nd-2.0": "CC-BY-NC-ND-2.0",
|
||||
"cc-by-nc-nd-2.5": "CC-BY-NC-ND-2.5",
|
||||
"cc-by-nc-nd-3.0": "CC-BY-NC-ND-3.0",
|
||||
"cc-by-nc-nd-3.0-igo": "CC-BY-NC-ND-3.0-IGO",
|
||||
"cc-by-nc-nd-4.0": "CC-BY-NC-ND-4.0",
|
||||
"cc-by-nc-sa-1.0": "CC-BY-NC-SA-1.0",
|
||||
"cc-by-nc-sa-2.0": "CC-BY-NC-SA-2.0",
|
||||
"cc-by-nc-sa-2.5": "CC-BY-NC-SA-2.5",
|
||||
"cc-by-nc-sa-3.0": "CC-BY-NC-SA-3.0",
|
||||
"cc-by-nc-sa-4.0": "CC-BY-NC-SA-4.0",
|
||||
"cc-by-nd-1.0": "CC-BY-ND-1.0",
|
||||
"cc-by-nd-2.0": "CC-BY-ND-2.0",
|
||||
"cc-by-nd-2.5": "CC-BY-ND-2.5",
|
||||
"cc-by-nd-3.0": "CC-BY-ND-3.0",
|
||||
"cc-by-nd-4.0": "CC-BY-ND-4.0",
|
||||
"cc-by-sa-1.0": "CC-BY-SA-1.0",
|
||||
"cc-by-sa-2.0": "CC-BY-SA-2.0",
|
||||
"cc-by-sa-2.0-uk": "CC-BY-SA-2.0-UK",
|
||||
"cc-by-sa-2.1-jp": "CC-BY-SA-2.1-JP",
|
||||
"cc-by-sa-2.5": "CC-BY-SA-2.5",
|
||||
"cc-by-sa-3.0": "CC-BY-SA-3.0",
|
||||
"cc-by-sa-3.0-at": "CC-BY-SA-3.0-AT",
|
||||
"cc-by-sa-4.0": "CC-BY-SA-4.0",
|
||||
"cc-pddc": "CC-PDDC",
|
||||
"cc0-1.0": "CC0-1.0",
|
||||
"cddl-1.0": "CDDL-1.0",
|
||||
"cddl-1.1": "CDDL-1.1",
|
||||
"cdl-1.0": "CDL-1.0",
|
||||
"cdla-permissive-1.0": "CDLA-Permissive-1.0",
|
||||
"cdla-sharing-1.0": "CDLA-Sharing-1.0",
|
||||
"cecill-1.0": "CECILL-1.0",
|
||||
"cecill-1.1": "CECILL-1.1",
|
||||
"cecill-2.0": "CECILL-2.0",
|
||||
"cecill-2.1": "CECILL-2.1",
|
||||
"cecill-b": "CECILL-B",
|
||||
"cecill-c": "CECILL-C",
|
||||
"cern-ohl-1.1": "CERN-OHL-1.1",
|
||||
"cern-ohl-1.2": "CERN-OHL-1.2",
|
||||
"cern-ohl-p-2.0": "CERN-OHL-P-2.0",
|
||||
"cern-ohl-s-2.0": "CERN-OHL-S-2.0",
|
||||
"cern-ohl-w-2.0": "CERN-OHL-W-2.0",
|
||||
"clartistic": "ClArtistic",
|
||||
"cnri-jython": "CNRI-Jython",
|
||||
"cnri-python": "CNRI-Python",
|
||||
"cnri-python-gpl-compatible": "CNRI-Python-GPL-Compatible",
|
||||
"condor-1.1": "Condor-1.1",
|
||||
"copyleft-next-0.3.0": "copyleft-next-0.3.0",
|
||||
"copyleft-next-0.3.1": "copyleft-next-0.3.1",
|
||||
"cpal-1.0": "CPAL-1.0",
|
||||
"cpl-1.0": "CPL-1.0",
|
||||
"cpol-1.02": "CPOL-1.02",
|
||||
"crossword": "Crossword",
|
||||
"crystalstacker": "CrystalStacker",
|
||||
"cua-opl-1.0": "CUA-OPL-1.0",
|
||||
"cube": "Cube",
|
||||
"curl": "curl",
|
||||
"d-fsl-1.0": "D-FSL-1.0",
|
||||
"diffmark": "diffmark",
|
||||
"doc": "DOC",
|
||||
"dotseqn": "Dotseqn",
|
||||
"drl-1.0": "DRL-1.0",
|
||||
"dsdp": "DSDP",
|
||||
"dvipdfm": "dvipdfm",
|
||||
"ecl-1.0": "ECL-1.0",
|
||||
"ecl-2.0": "ECL-2.0",
|
||||
"ecos-2.0": "eCos-2.0",
|
||||
"efl-1.0": "EFL-1.0",
|
||||
"efl-2.0": "EFL-2.0",
|
||||
"egenix": "eGenix",
|
||||
"entessa": "Entessa",
|
||||
"epics": "EPICS",
|
||||
"epl-1.0": "EPL-1.0",
|
||||
"epl-2.0": "EPL-2.0",
|
||||
"erlpl-1.1": "ErlPL-1.1",
|
||||
"etalab-2.0": "etalab-2.0",
|
||||
"eudatagrid": "EUDatagrid",
|
||||
"eupl-1.0": "EUPL-1.0",
|
||||
"eupl-1.1": "EUPL-1.1",
|
||||
"eupl-1.2": "EUPL-1.2",
|
||||
"eurosym": "Eurosym",
|
||||
"fair": "Fair",
|
||||
"frameworx-1.0": "Frameworx-1.0",
|
||||
"freebsd-doc": "FreeBSD-DOC",
|
||||
"freeimage": "FreeImage",
|
||||
"fsfap": "FSFAP",
|
||||
"fsful": "FSFUL",
|
||||
"fsfullr": "FSFULLR",
|
||||
"ftl": "FTL",
|
||||
"gd": "GD",
|
||||
"gfdl-1.1": "GFDL-1.1",
|
||||
"gfdl-1.1-invariants-only": "GFDL-1.1-invariants-only",
|
||||
"gfdl-1.1-invariants-or-later": "GFDL-1.1-invariants-or-later",
|
||||
"gfdl-1.1-no-invariants-only": "GFDL-1.1-no-invariants-only",
|
||||
"gfdl-1.1-no-invariants-or-later": "GFDL-1.1-no-invariants-or-later",
|
||||
"gfdl-1.1-only": "GFDL-1.1-only",
|
||||
"gfdl-1.1-or-later": "GFDL-1.1-or-later",
|
||||
"gfdl-1.2": "GFDL-1.2",
|
||||
"gfdl-1.2-invariants-only": "GFDL-1.2-invariants-only",
|
||||
"gfdl-1.2-invariants-or-later": "GFDL-1.2-invariants-or-later",
|
||||
"gfdl-1.2-no-invariants-only": "GFDL-1.2-no-invariants-only",
|
||||
"gfdl-1.2-no-invariants-or-later": "GFDL-1.2-no-invariants-or-later",
|
||||
"gfdl-1.2-only": "GFDL-1.2-only",
|
||||
"gfdl-1.2-or-later": "GFDL-1.2-or-later",
|
||||
"gfdl-1.3": "GFDL-1.3",
|
||||
"gfdl-1.3-invariants-only": "GFDL-1.3-invariants-only",
|
||||
"gfdl-1.3-invariants-or-later": "GFDL-1.3-invariants-or-later",
|
||||
"gfdl-1.3-no-invariants-only": "GFDL-1.3-no-invariants-only",
|
||||
"gfdl-1.3-no-invariants-or-later": "GFDL-1.3-no-invariants-or-later",
|
||||
"gfdl-1.3-only": "GFDL-1.3-only",
|
||||
"gfdl-1.3-or-later": "GFDL-1.3-or-later",
|
||||
"giftware": "Giftware",
|
||||
"gl2ps": "GL2PS",
|
||||
"glide": "Glide",
|
||||
"glulxe": "Glulxe",
|
||||
"glwtpl": "GLWTPL",
|
||||
"gnuplot": "gnuplot",
|
||||
"gpl-1.0": "GPL-1.0",
|
||||
"gpl-1.0+": "GPL-1.0+",
|
||||
"gpl-1.0-only": "GPL-1.0-only",
|
||||
"gpl-1.0-or-later": "GPL-1.0-or-later",
|
||||
"gpl-2.0": "GPL-2.0",
|
||||
"gpl-2.0+": "GPL-2.0+",
|
||||
"gpl-2.0-only": "GPL-2.0-only",
|
||||
"gpl-2.0-or-later": "GPL-2.0-or-later",
|
||||
"gpl-2.0-with-autoconf-exception": "GPL-2.0-with-autoconf-exception",
|
||||
"gpl-2.0-with-bison-exception": "GPL-2.0-with-bison-exception",
|
||||
"gpl-2.0-with-classpath-exception": "GPL-2.0-with-classpath-exception",
|
||||
"gpl-2.0-with-font-exception": "GPL-2.0-with-font-exception",
|
||||
"gpl-2.0-with-gcc-exception": "GPL-2.0-with-GCC-exception",
|
||||
"gpl-3.0": "GPL-3.0",
|
||||
"gpl-3.0+": "GPL-3.0+",
|
||||
"gpl-3.0-only": "GPL-3.0-only",
|
||||
"gpl-3.0-or-later": "GPL-3.0-or-later",
|
||||
"gpl-3.0-with-autoconf-exception": "GPL-3.0-with-autoconf-exception",
|
||||
"gpl-3.0-with-gcc-exception": "GPL-3.0-with-GCC-exception",
|
||||
"gsoap-1.3b": "gSOAP-1.3b",
|
||||
"haskellreport": "HaskellReport",
|
||||
"hippocratic-2.1": "Hippocratic-2.1",
|
||||
"hpnd": "HPND",
|
||||
"hpnd-sell-variant": "HPND-sell-variant",
|
||||
"htmltidy": "HTMLTIDY",
|
||||
"ibm-pibs": "IBM-pibs",
|
||||
"icu": "ICU",
|
||||
"ijg": "IJG",
|
||||
"imagemagick": "ImageMagick",
|
||||
"imatix": "iMatix",
|
||||
"imlib2": "Imlib2",
|
||||
"info-zip": "Info-ZIP",
|
||||
"intel": "Intel",
|
||||
"intel-acpi": "Intel-ACPI",
|
||||
"interbase-1.0": "Interbase-1.0",
|
||||
"ipa": "IPA",
|
||||
"ipl-1.0": "IPL-1.0",
|
||||
"isc": "ISC",
|
||||
"jasper-2.0": "JasPer-2.0",
|
||||
"jpnic": "JPNIC",
|
||||
"json": "JSON",
|
||||
"lal-1.2": "LAL-1.2",
|
||||
"lal-1.3": "LAL-1.3",
|
||||
"latex2e": "Latex2e",
|
||||
"leptonica": "Leptonica",
|
||||
"lgpl-2.0": "LGPL-2.0",
|
||||
"lgpl-2.0+": "LGPL-2.0+",
|
||||
"lgpl-2.0-only": "LGPL-2.0-only",
|
||||
"lgpl-2.0-or-later": "LGPL-2.0-or-later",
|
||||
"lgpl-2.1": "LGPL-2.1",
|
||||
"lgpl-2.1+": "LGPL-2.1+",
|
||||
"lgpl-2.1-only": "LGPL-2.1-only",
|
||||
"lgpl-2.1-or-later": "LGPL-2.1-or-later",
|
||||
"lgpl-3.0": "LGPL-3.0",
|
||||
"lgpl-3.0+": "LGPL-3.0+",
|
||||
"lgpl-3.0-only": "LGPL-3.0-only",
|
||||
"lgpl-3.0-or-later": "LGPL-3.0-or-later",
|
||||
"lgpllr": "LGPLLR",
|
||||
"libpng": "Libpng",
|
||||
"libpng-2.0": "libpng-2.0",
|
||||
"libselinux-1.0": "libselinux-1.0",
|
||||
"libtiff": "libtiff",
|
||||
"liliq-p-1.1": "LiLiQ-P-1.1",
|
||||
"liliq-r-1.1": "LiLiQ-R-1.1",
|
||||
"liliq-rplus-1.1": "LiLiQ-Rplus-1.1",
|
||||
"linux-openib": "Linux-OpenIB",
|
||||
"lpl-1.0": "LPL-1.0",
|
||||
"lpl-1.02": "LPL-1.02",
|
||||
"lppl-1.0": "LPPL-1.0",
|
||||
"lppl-1.1": "LPPL-1.1",
|
||||
"lppl-1.2": "LPPL-1.2",
|
||||
"lppl-1.3a": "LPPL-1.3a",
|
||||
"lppl-1.3c": "LPPL-1.3c",
|
||||
"makeindex": "MakeIndex",
|
||||
"miros": "MirOS",
|
||||
"mit": "MIT",
|
||||
"mit-0": "MIT-0",
|
||||
"mit-advertising": "MIT-advertising",
|
||||
"mit-cmu": "MIT-CMU",
|
||||
"mit-enna": "MIT-enna",
|
||||
"mit-feh": "MIT-feh",
|
||||
"mit-modern-variant": "MIT-Modern-Variant",
|
||||
"mit-open-group": "MIT-open-group",
|
||||
"mitnfa": "MITNFA",
|
||||
"motosoto": "Motosoto",
|
||||
"mpich2": "mpich2",
|
||||
"mpl-1.0": "MPL-1.0",
|
||||
"mpl-1.1": "MPL-1.1",
|
||||
"mpl-2.0": "MPL-2.0",
|
||||
"mpl-2.0-no-copyleft-exception": "MPL-2.0-no-copyleft-exception",
|
||||
"ms-pl": "MS-PL",
|
||||
"ms-rl": "MS-RL",
|
||||
"mtll": "MTLL",
|
||||
"mulanpsl-1.0": "MulanPSL-1.0",
|
||||
"mulanpsl-2.0": "MulanPSL-2.0",
|
||||
"multics": "Multics",
|
||||
"mup": "Mup",
|
||||
"naist-2003": "NAIST-2003",
|
||||
"nasa-1.3": "NASA-1.3",
|
||||
"naumen": "Naumen",
|
||||
"nbpl-1.0": "NBPL-1.0",
|
||||
"ncgl-uk-2.0": "NCGL-UK-2.0",
|
||||
"ncsa": "NCSA",
|
||||
"net-snmp": "Net-SNMP",
|
||||
"netcdf": "NetCDF",
|
||||
"newsletr": "Newsletr",
|
||||
"ngpl": "NGPL",
|
||||
"nist-pd": "NIST-PD",
|
||||
"nist-pd-fallback": "NIST-PD-fallback",
|
||||
"nlod-1.0": "NLOD-1.0",
|
||||
"nlpl": "NLPL",
|
||||
"nokia": "Nokia",
|
||||
"nosl": "NOSL",
|
||||
"noweb": "Noweb",
|
||||
"npl-1.0": "NPL-1.0",
|
||||
"npl-1.1": "NPL-1.1",
|
||||
"nposl-3.0": "NPOSL-3.0",
|
||||
"nrl": "NRL",
|
||||
"ntp": "NTP",
|
||||
"ntp-0": "NTP-0",
|
||||
"nunit": "Nunit",
|
||||
"o-uda-1.0": "O-UDA-1.0",
|
||||
"occt-pl": "OCCT-PL",
|
||||
"oclc-2.0": "OCLC-2.0",
|
||||
"odbl-1.0": "ODbL-1.0",
|
||||
"odc-by-1.0": "ODC-By-1.0",
|
||||
"ofl-1.0": "OFL-1.0",
|
||||
"ofl-1.0-no-rfn": "OFL-1.0-no-RFN",
|
||||
"ofl-1.0-rfn": "OFL-1.0-RFN",
|
||||
"ofl-1.1": "OFL-1.1",
|
||||
"ofl-1.1-no-rfn": "OFL-1.1-no-RFN",
|
||||
"ofl-1.1-rfn": "OFL-1.1-RFN",
|
||||
"ogc-1.0": "OGC-1.0",
|
||||
"ogdl-taiwan-1.0": "OGDL-Taiwan-1.0",
|
||||
"ogl-canada-2.0": "OGL-Canada-2.0",
|
||||
"ogl-uk-1.0": "OGL-UK-1.0",
|
||||
"ogl-uk-2.0": "OGL-UK-2.0",
|
||||
"ogl-uk-3.0": "OGL-UK-3.0",
|
||||
"ogtsl": "OGTSL",
|
||||
"oldap-1.1": "OLDAP-1.1",
|
||||
"oldap-1.2": "OLDAP-1.2",
|
||||
"oldap-1.3": "OLDAP-1.3",
|
||||
"oldap-1.4": "OLDAP-1.4",
|
||||
"oldap-2.0": "OLDAP-2.0",
|
||||
"oldap-2.0.1": "OLDAP-2.0.1",
|
||||
"oldap-2.1": "OLDAP-2.1",
|
||||
"oldap-2.2": "OLDAP-2.2",
|
||||
"oldap-2.2.1": "OLDAP-2.2.1",
|
||||
"oldap-2.2.2": "OLDAP-2.2.2",
|
||||
"oldap-2.3": "OLDAP-2.3",
|
||||
"oldap-2.4": "OLDAP-2.4",
|
||||
"oldap-2.5": "OLDAP-2.5",
|
||||
"oldap-2.6": "OLDAP-2.6",
|
||||
"oldap-2.7": "OLDAP-2.7",
|
||||
"oldap-2.8": "OLDAP-2.8",
|
||||
"oml": "OML",
|
||||
"openssl": "OpenSSL",
|
||||
"opl-1.0": "OPL-1.0",
|
||||
"oset-pl-2.1": "OSET-PL-2.1",
|
||||
"osl-1.0": "OSL-1.0",
|
||||
"osl-1.1": "OSL-1.1",
|
||||
"osl-2.0": "OSL-2.0",
|
||||
"osl-2.1": "OSL-2.1",
|
||||
"osl-3.0": "OSL-3.0",
|
||||
"parity-6.0.0": "Parity-6.0.0",
|
||||
"parity-7.0.0": "Parity-7.0.0",
|
||||
"pddl-1.0": "PDDL-1.0",
|
||||
"php-3.0": "PHP-3.0",
|
||||
"php-3.01": "PHP-3.01",
|
||||
"plexus": "Plexus",
|
||||
"polyform-noncommercial-1.0.0": "PolyForm-Noncommercial-1.0.0",
|
||||
"polyform-small-business-1.0.0": "PolyForm-Small-Business-1.0.0",
|
||||
"postgresql": "PostgreSQL",
|
||||
"psf-2.0": "PSF-2.0",
|
||||
"psfrag": "psfrag",
|
||||
"psutils": "psutils",
|
||||
"python-2.0": "Python-2.0",
|
||||
"qhull": "Qhull",
|
||||
"qpl-1.0": "QPL-1.0",
|
||||
"rdisc": "Rdisc",
|
||||
"rhecos-1.1": "RHeCos-1.1",
|
||||
"rpl-1.1": "RPL-1.1",
|
||||
"rpl-1.5": "RPL-1.5",
|
||||
"rpsl-1.0": "RPSL-1.0",
|
||||
"rsa-md": "RSA-MD",
|
||||
"rscpl": "RSCPL",
|
||||
"ruby": "Ruby",
|
||||
"sax-pd": "SAX-PD",
|
||||
"saxpath": "Saxpath",
|
||||
"scea": "SCEA",
|
||||
"sendmail": "Sendmail",
|
||||
"sendmail-8.23": "Sendmail-8.23",
|
||||
"sgi-b-1.0": "SGI-B-1.0",
|
||||
"sgi-b-1.1": "SGI-B-1.1",
|
||||
"sgi-b-2.0": "SGI-B-2.0",
|
||||
"shl-0.5": "SHL-0.5",
|
||||
"shl-0.51": "SHL-0.51",
|
||||
"simpl-2.0": "SimPL-2.0",
|
||||
"sissl": "SISSL",
|
||||
"sissl-1.2": "SISSL-1.2",
|
||||
"sleepycat": "Sleepycat",
|
||||
"smlnj": "SMLNJ",
|
||||
"smppl": "SMPPL",
|
||||
"snia": "SNIA",
|
||||
"spencer-86": "Spencer-86",
|
||||
"spencer-94": "Spencer-94",
|
||||
"spencer-99": "Spencer-99",
|
||||
"spl-1.0": "SPL-1.0",
|
||||
"ssh-openssh": "SSH-OpenSSH",
|
||||
"ssh-short": "SSH-short",
|
||||
"sspl-1.0": "SSPL-1.0",
|
||||
"standardml-nj": "StandardML-NJ",
|
||||
"sugarcrm-1.1.3": "SugarCRM-1.1.3",
|
||||
"swl": "SWL",
|
||||
"tapr-ohl-1.0": "TAPR-OHL-1.0",
|
||||
"tcl": "TCL",
|
||||
"tcp-wrappers": "TCP-wrappers",
|
||||
"tmate": "TMate",
|
||||
"torque-1.1": "TORQUE-1.1",
|
||||
"tosl": "TOSL",
|
||||
"tu-berlin-1.0": "TU-Berlin-1.0",
|
||||
"tu-berlin-2.0": "TU-Berlin-2.0",
|
||||
"ucl-1.0": "UCL-1.0",
|
||||
"unicode-dfs-2015": "Unicode-DFS-2015",
|
||||
"unicode-dfs-2016": "Unicode-DFS-2016",
|
||||
"unicode-tou": "Unicode-TOU",
|
||||
"unlicense": "Unlicense",
|
||||
"upl-1.0": "UPL-1.0",
|
||||
"vim": "Vim",
|
||||
"vostrom": "VOSTROM",
|
||||
"vsl-1.0": "VSL-1.0",
|
||||
"w3c": "W3C",
|
||||
"w3c-19980720": "W3C-19980720",
|
||||
"w3c-20150513": "W3C-20150513",
|
||||
"watcom-1.0": "Watcom-1.0",
|
||||
"wsuipa": "Wsuipa",
|
||||
"wtfpl": "WTFPL",
|
||||
"wxwindows": "wxWindows",
|
||||
"x11": "X11",
|
||||
"xerox": "Xerox",
|
||||
"xfree86-1.1": "XFree86-1.1",
|
||||
"xinetd": "xinetd",
|
||||
"xnet": "Xnet",
|
||||
"xpp": "xpp",
|
||||
"xskat": "XSkat",
|
||||
"ypl-1.0": "YPL-1.0",
|
||||
"ypl-1.1": "YPL-1.1",
|
||||
"zed": "Zed",
|
||||
"zend-2.0": "Zend-2.0",
|
||||
"zimbra-1.3": "Zimbra-1.3",
|
||||
"zimbra-1.4": "Zimbra-1.4",
|
||||
"zlib": "Zlib",
|
||||
"zlib-acknowledgement": "zlib-acknowledgement",
|
||||
"zpl-1.1": "ZPL-1.1",
|
||||
"zpl-2.0": "ZPL-2.0",
|
||||
"zpl-2.1": "ZPL-2.1",
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package spdxlicense
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLicenceListIDs(t *testing.T) {
|
||||
// do a sanity check on the generated data
|
||||
assert.Equal(t, "0BSD", licenseIDs["0bsd"])
|
||||
assert.Equal(t, "ZPL-2.1", licenseIDs["zpl-2.1"])
|
||||
assert.NotEmpty(t, Version)
|
||||
}
|
||||
Reference in New Issue
Block a user