diff --git a/config/example_config.json b/config/example_config.json index 908fb07..b26e48d 100644 --- a/config/example_config.json +++ b/config/example_config.json @@ -1,4 +1,5 @@ { + "spdxVersion": "SPDX-2.2", "documentName": "composed-1.0", "packageName": "top-level-artifact", "spdxID": "SPDXRef-composed-sbom-product", @@ -12,5 +13,8 @@ "packageLicenseDeclared": "license, licenseRef or NOASSERTION", "packageCopyrightText": "text or NOASSERTION", "packageSupplier": "Organization or recognized author of product. Optional", - "packageComment": "Any relevant comment. Optional" + "packageComment": "Any relevant comment. Optional", + "namespacePrefix": "https://spdx.org/spdxdocs/", + "creator": "sbom-composer-v0.1", + "creatorType": "Tool" } \ No newline at end of file diff --git a/config/example_config.yaml b/config/example_config.yaml index fc3bf17..a41dbda 100644 --- a/config/example_config.yaml +++ b/config/example_config.yaml @@ -1,6 +1,8 @@ # Copyright (c) 2022 VMware, Inc. All Rights Reserved. # SPDX-License-Identifier: BSD-2-Clause +# SPDX version the composed doc is output to +spdxVersion: "SPDX-2.2" # SBOM-Composer report for documentName: "composed-1.0" # Top Level Composed SBOM product @@ -24,3 +26,11 @@ packageCopyrightText: "" packageSupplier: "Example supplier" # Any relevant comment. Optional packageComment: "Example comment" + +# The following fields belong to SPDX config reference +# Prefix used for DocumentNamespace +NamespacePrefix: "https://spdx.org/spdxdocs/" +# Composed SPDX SBOM creator +Creator: "sbom-composer-v0.1" +# Should be always set to "Tool" +CreatorType: "Tool" diff --git a/parser/build.go b/parser/build.go index fb8d313..d2bcfc4 100644 --- a/parser/build.go +++ b/parser/build.go @@ -9,20 +9,26 @@ import ( "github.com/spdx/tools-golang/builder" ) -func Build(spdxVersion string, dirRoot string, conf *Config) (*Document, error) { - spdxDocRef := BuildVersion(spdxVersion, dirRoot, conf) +func Build(dirRoot string, conf *Config) (*Document, error) { + spdxDocRef := BuildVersion(dirRoot, conf) - UpdatePackages(SPDX_VERSION, &spdxDocRef, conf) + UpdatePackages(&spdxDocRef, conf) doc := CreateDocument(&spdxDocRef, conf) return doc, nil } -func BuildVersion(spdxVersion string, dirRoot string, conf *Config) SPDXDocRef { +func BuildVersion(dirRoot string, conf *Config) SPDXDocRef { res := SPDXDocRef{} - switch spdxVersion { - case "2.2": + switch conf.SPDXVersion { + case "SPDX-2.2": var err error - res.Doc2_2, err = builder.Build2_2(conf.PackageName, dirRoot, conf.SPDXConfigRef) + res.Doc2_2, err = builder.Build2_2(conf.PackageName, dirRoot, conf.SPDXConfigRef.Conf2_2) + if err != nil { + fmt.Printf("error while building spdx document reference for path %v with config %v, %v: %v\n", dirRoot, conf.PackageName, conf.SPDXConfigRef, err) + } + default: + var err error + res.Doc2_2, err = builder.Build2_2(conf.PackageName, dirRoot, conf.SPDXConfigRef.Conf2_2) if err != nil { fmt.Printf("error while building spdx document reference for path %v with config %v, %v: %v\n", dirRoot, conf.PackageName, conf.SPDXConfigRef, err) } @@ -33,7 +39,7 @@ func BuildVersion(spdxVersion string, dirRoot string, conf *Config) SPDXDocRef { func GenerateComposedDoc(dirRoot string, output string, outFormat string, confFile string) error { conf := LoadConfig(confFile) - doc, err := Build(SPDX_VERSION, dirRoot, conf) + doc, err := Build(dirRoot, conf) if err != nil { return err } diff --git a/parser/build_test.go b/parser/build_test.go index 799c103..732573c 100644 --- a/parser/build_test.go +++ b/parser/build_test.go @@ -7,6 +7,7 @@ import ( "fmt" "testing" + "github.com/spdx/tools-golang/builder" "github.com/spdx/tools-golang/spdx" "github.com/stretchr/testify/assert" ) @@ -14,7 +15,8 @@ import ( func TestGenerateComposedDoc(t *testing.T) { fmt.Println("Testing Unmarshal JSON Config") t.Run("Unmarshal Config:", func(t *testing.T) { - input := []byte(`documentName: "composed-1.0" + input := []byte(`spdxVersion: "SPDX-2.2" +documentName: "composed-1.0" packageName: "top-level-artifact" spdxID: "SPDXRef-DOCUMENT" packageVersion: "1.0" @@ -26,19 +28,28 @@ packageLicenseConcluded: "BSD-3-Clause" packageLicenseDeclared: "BSD-3-Clause" packageCopyrightText: "" packageSupplier: "somesupplier" -packageComment: "somecomment"`) +packageComment: "somecomment" +namespacePrefix: "https://spdx.org/spdxdocs/" +creator: "sbom-composer-v0.1" +creatorType: "Tool"`) want := Document{ SPDXDocRef: &SPDXDocRef{ Doc2_2: &spdx.Document2_2{ - SPDXVersion: "SPDX-2.2", DataLicense: "CC0-1.0", SPDXIdentifier: "DOCUMENT", DocumentName: "top-level-artifact", DocumentNamespace: "https://spdx.org/spdxdocs/top-level-artifact-", }}, ConfigDataRef: &Config{ - SPDXConfigRef: SPDXConfigReference, + SPDXVersion: "SPDX-2.2", + SPDXConfigRef: SPDXConfigRef{ + Conf2_2: &builder.Config2_2{ + NamespacePrefix: "https://spdx.org/spdxdocs/", + CreatorType: "Tool", + Creator: "sbom-composer-1.0", + }, + }, PackageName: "top-level-artifact", DocumentName: "composed-1.0", SPDXID: "SPDXRef-DOCUMENT", @@ -57,10 +68,10 @@ packageComment: "somecomment"`) } loadedConfig := createConfig(input) - doc, err := Build(SPDX_VERSION, "../example_data/micro_sboms/tag_value", loadedConfig) + doc, err := Build("../example_data/micro_sboms/tag_value", loadedConfig) assert.Equal(t, nil, err) - assert.Equal(t, want.SPDXDocRef.Doc2_2.SPDXVersion, doc.SPDXDocRef.Doc2_2.SPDXVersion) + assert.Equal(t, want.ConfigDataRef.SPDXVersion, doc.SPDXDocRef.Doc2_2.SPDXVersion) assert.Equal(t, want.SPDXDocRef.Doc2_2.DataLicense, doc.SPDXDocRef.Doc2_2.DataLicense) assert.Equal(t, want.SPDXDocRef.Doc2_2.SPDXIdentifier, doc.SPDXDocRef.Doc2_2.SPDXIdentifier) assert.Equal(t, want.SPDXDocRef.Doc2_2.DocumentName, doc.SPDXDocRef.Doc2_2.DocumentName) diff --git a/parser/config.go b/parser/config.go index 893b01a..a8f41d5 100644 --- a/parser/config.go +++ b/parser/config.go @@ -15,23 +15,26 @@ import ( ) // PackageChecksum is a unique identifier used to verify if all files -// in the orginal package are unchanged, exluding SPDX documents. +// in the original package are unchanged, excluding SPDX documents. // Should be generated with SHA256. type PackageChecksum struct { SHA256 string } var NOASSERTION string = "NOASSERTION" -var SPDXConfigReference *builder.Config2_2 = &builder.Config2_2{ - NamespacePrefix: "https://spdx.org/spdxdocs/", // TODO: move this to config - CreatorType: "Tool", - Creator: "sbom-composer-1.0", // TODO: automate taking the version + +type SPDXConfigRef struct { + Conf2_2 *builder.Config2_2 } // Config is a collection of configuration settings for builder // to create a composed document with. type Config struct { - SPDXConfigRef *builder.Config2_2 + SPDXConfigRef SPDXConfigRef + + // SPDXVersion is a configuration for the + // version that should be used as an output + SPDXVersion string `json:"spdxVersion"` // DocumentName is an SBOM-Composer report // for @@ -73,6 +76,15 @@ type Config struct { // PackageComment is any relevant comment in a section PackageComment string `json:"packageComment,omitempty"` + + // NamespacePrefix is a prefix used for DocumentNamespace + NamespacePrefix string `json:"namespacePrefix"` + + // Creator is the composed SPDX SBOM creator + Creator string `json:"creator"` + + // CreatorType refers to SPDX CreatorType field + CreatorType string `json:"creatorType"` } func UnmarshalJSONConfig(jsonData []byte) (*Config, error) { @@ -111,9 +123,7 @@ func readConfFile(file string) []byte { } func createConfig(loadedData []byte) *Config { - conf := Config{} - - conf.SPDXConfigRef = SPDXConfigReference + conf := &Config{} mapData := make(map[string]interface{}) @@ -125,6 +135,8 @@ func createConfig(loadedData []byte) *Config { dataByte, _ := json.Marshal(mapData) _ = json.Unmarshal(dataByte, &conf) + conf = setSPDXConfigRef(conf) + if len(conf.PackageDownloadLocation) == 0 { conf.PackageDownloadLocation = NOASSERTION } @@ -139,5 +151,27 @@ func createConfig(loadedData []byte) *Config { } conf.PackageChecksum.SHA256 = strings.Trim(conf.PackageChecksum.SHA256, "\"{}") - return &conf + return conf +} + +func setSPDXConfigRef(conf *Config) *Config { + switch conf.SPDXVersion { + case "SPDX-2.2": + conf.SPDXConfigRef = SPDXConfigRef{ + Conf2_2: &builder.Config2_2{ + NamespacePrefix: conf.NamespacePrefix, + CreatorType: conf.CreatorType, + Creator: conf.Creator, + }, + } + default: + conf.SPDXConfigRef = SPDXConfigRef{ + Conf2_2: &builder.Config2_2{ + NamespacePrefix: conf.NamespacePrefix, + CreatorType: conf.CreatorType, + Creator: conf.Creator, + }, + } + } + return conf } diff --git a/parser/config_test.go b/parser/config_test.go index 2fecedd..f48f807 100644 --- a/parser/config_test.go +++ b/parser/config_test.go @@ -12,11 +12,11 @@ import ( "github.com/stretchr/testify/assert" ) -func generateJSONTConfigExample(documentName string, packageName string, spdxID string, packageVersion string, +func generateJSONTConfigExample(spdxVersion string, documentName string, packageName string, spdxID string, packageVersion string, packageDownloadLocation string, packageChecksum PackageChecksum, filesAnalyzed bool, packageLicenseConcluded string, packageLicenseDeclared string, packageCopyrightText string, packageSupplier string, packageComment string) []byte { - jsonStr := fmt.Sprintf("{\"documentName\":\"%s\",\"packageName\":\"%s\",\"spdxID\":\"%s\",\"packageVersion\":\"%s\",\"packageDownloadLocation\":\"%s\",\"filesAnalyzed\":\"%t\",\"packageChecksum\":\"%s\", \"packageLicenseConcluded\":\"%s\", \"packageLicenseDeclared\":\"%s\", \"packageCopyrightText\":\"%s\", \"packageSupplier\":\"%s\", \"packageComment\":\"%s\"}", - documentName, packageName, spdxID, packageVersion, packageDownloadLocation, filesAnalyzed, packageChecksum, + jsonStr := fmt.Sprintf("{\"spdxVersion\":\"%s\",\"documentName\":\"%s\",\"packageName\":\"%s\",\"spdxID\":\"%s\",\"packageVersion\":\"%s\",\"packageDownloadLocation\":\"%s\",\"filesAnalyzed\":\"%t\",\"packageChecksum\":\"%s\", \"packageLicenseConcluded\":\"%s\", \"packageLicenseDeclared\":\"%s\", \"packageCopyrightText\":\"%s\", \"packageSupplier\":\"%s\", \"packageComment\":\"%s\"}", + spdxVersion, documentName, packageName, spdxID, packageVersion, packageDownloadLocation, filesAnalyzed, packageChecksum, packageLicenseConcluded, packageLicenseDeclared, packageCopyrightText, packageSupplier, packageComment) return []byte(jsonStr) @@ -37,11 +37,12 @@ func TestUnmarshalJSONConfig(t *testing.T) { pch := PackageChecksum{SHA256: checksum} - jsonData := generateJSONTConfigExample("composed-1.0", "top-level-artifact", "SPDXRef-DOCUMENT", "1.0", NOASSERTION, pch, + jsonData := generateJSONTConfigExample("SPDX-2.2", "composed-1.0", "top-level-artifact", "SPDXRef-DOCUMENT", "1.0", NOASSERTION, pch, false, "BSD-3-Clause", "BSD-3-Clause", NOASSERTION, "somesupplier", "somecomment") conf, _ := UnmarshalJSONConfig(jsonData) + assert.Equal(t, "SPDX-2.2", conf.SPDXVersion) assert.Equal(t, "composed-1.0", conf.DocumentName) assert.Equal(t, "top-level-artifact", conf.PackageName) assert.Equal(t, "SPDXRef-DOCUMENT", conf.SPDXID) diff --git a/parser/document.go b/parser/document.go index 7155136..1af3b4c 100644 --- a/parser/document.go +++ b/parser/document.go @@ -29,9 +29,16 @@ func CreateDocumentWithSPDXRef() *Document { return doc } -func UpdatePackages(spdxVersion string, spdxDocRef *SPDXDocRef, conf *Config) { - switch spdxVersion { - case "2.2": +func UpdatePackages(spdxDocRef *SPDXDocRef, conf *Config) { + switch conf.SPDXVersion { + case "SPDX-2.2": + for i := range spdxDocRef.Doc2_2.Packages { + if spdxDocRef.Doc2_2.Packages[i].PackageName == conf.PackageName && + len(spdxDocRef.Doc2_2.Packages[i].PackageVersion) == 0 { + spdxDocRef.Doc2_2.Packages[i].PackageVersion = conf.PackageVersion + } + } + default: for i := range spdxDocRef.Doc2_2.Packages { if spdxDocRef.Doc2_2.Packages[i].PackageName == conf.PackageName && len(spdxDocRef.Doc2_2.Packages[i].PackageVersion) == 0 { diff --git a/parser/read_config_test.go b/parser/read_config_test.go index 4addff9..e816307 100644 --- a/parser/read_config_test.go +++ b/parser/read_config_test.go @@ -15,7 +15,8 @@ func TestLoadConfigYAML(t *testing.T) { t.Run("Loading YAML Config:", func(t *testing.T) { conf := LoadConfig("../config/example_config.yaml") - fmt.Println(conf) + + assert.Equal(t, "SPDX-2.2", conf.SPDXVersion) assert.Equal(t, "composed-1.0", conf.DocumentName) assert.Equal(t, "top-level-artifact", conf.PackageName) assert.Equal(t, "SPDXRef-DOCUMENT", conf.SPDXID) diff --git a/parser/save.go b/parser/save.go index a9884d2..9ca1289 100644 --- a/parser/save.go +++ b/parser/save.go @@ -13,9 +13,6 @@ import ( "github.com/spdx/tools-golang/tvsaver" ) -// TODO: Make configurable -var SPDX_VERSION = "2.2" - func Save(doc *Document, composableDocs []*Document, output string, outFormat string) error { output = updateFileExtension(output, outFormat) @@ -29,17 +26,17 @@ func Save(doc *Document, composableDocs []*Document, output string, outFormat st // It's not necessary for the composed doc to // contain all merged documents as Files - doc = cleanDocumentFileData(SPDX_VERSION, doc) + doc = cleanDocumentFileData(doc) - updateRelationships(SPDX_VERSION, doc, composableDocs) + updateRelationships(doc, composableDocs) for _, cdoc := range composableDocs { if cdoc != nil { - AppendComposableDocument(SPDX_VERSION, doc, cdoc, w, outFormat) + AppendComposableDocument(doc, cdoc, w, outFormat) } } - err = SaveVersion(SPDX_VERSION, outFormat, doc, w) + err = SaveVersion(outFormat, doc, w) if err != nil { fmt.Printf("error while saving %v: %v\n", output, err) return err @@ -47,19 +44,19 @@ func Save(doc *Document, composableDocs []*Document, output string, outFormat st return nil } -func SaveVersion(version string, format string, doc *Document, w *os.File) error { +func SaveVersion(format string, doc *Document, w *os.File) error { switch format { case "tv": - if version == "2.2" { + if doc.ConfigDataRef.SPDXVersion == "SPDX-2.2" { return tvsaver.Save2_2(doc.SPDXDocRef.Doc2_2, w) } case "json": - if version == "2.2" { + if doc.ConfigDataRef.SPDXVersion == "SPDX-2.2" { return spdx_json.Save2_2(doc.SPDXDocRef.Doc2_2, w) } default: fmt.Printf("warn: %s is not proper output format; saving to default\n", format) - if version == "2.2" { + if doc.ConfigDataRef.SPDXVersion == "SPDX-2.2" { return tvsaver.Save2_2(doc.SPDXDocRef.Doc2_2, w) } } @@ -69,10 +66,10 @@ func SaveVersion(version string, format string, doc *Document, w *os.File) error // RenderComposableDocument processes a composable document // and renders it to the composed document -func AppendComposableDocument(spdxVersion string, res *Document, cdoc *Document, w io.Writer, outFormat string) { +func AppendComposableDocument(res *Document, cdoc *Document, w io.Writer, outFormat string) { - switch spdxVersion { - case "2.2": + switch res.ConfigDataRef.SPDXVersion { + case "SPDX-2.2": res.SPDXDocRef.Doc2_2.Annotations = append(res.SPDXDocRef.Doc2_2.Annotations, cdoc.SPDXDocRef.Doc2_2.Annotations...) res.SPDXDocRef.Doc2_2.ExternalDocumentReferences = append(res.SPDXDocRef.Doc2_2.ExternalDocumentReferences, cdoc.SPDXDocRef.Doc2_2.ExternalDocumentReferences...) res.SPDXDocRef.Doc2_2.Files = append(res.SPDXDocRef.Doc2_2.Files, cdoc.SPDXDocRef.Doc2_2.Files...) @@ -84,9 +81,15 @@ func AppendComposableDocument(spdxVersion string, res *Document, cdoc *Document, } } -func cleanDocumentFileData(spdxVersion string, doc *Document) *Document { - switch spdxVersion { - case "2.2": +func cleanDocumentFileData(doc *Document) *Document { + switch doc.ConfigDataRef.SPDXVersion { + case "SPDX-2.2": + doc.SPDXDocRef.Doc2_2.Files = []*spdx.File2_2{} + + for i := range doc.SPDXDocRef.Doc2_2.Packages { + doc.SPDXDocRef.Doc2_2.Packages[i].Files = []*spdx.File2_2{} + } + default: doc.SPDXDocRef.Doc2_2.Files = []*spdx.File2_2{} for i := range doc.SPDXDocRef.Doc2_2.Packages { @@ -97,12 +100,26 @@ func cleanDocumentFileData(spdxVersion string, doc *Document) *Document { return doc } -func updateRelationships(spdxVersion string, doc *Document, composableDocs []*Document) (*Document, []*Document) { +func updateRelationships(doc *Document, composableDocs []*Document) (*Document, []*Document) { - rootDocElID := setDocElID(spdxVersion, doc) + rootDocElID := setDocElID(doc) for _, cdoc := range composableDocs { - switch spdxVersion { - case "2.2": + switch cdoc.ConfigDataRef.SPDXVersion { + case "SPDX-2.2": + if cdoc != nil && len(cdoc.SPDXDocRef.Doc2_2.Packages) > 0 { + elId := spdx.MakeDocElementID("", + fmt.Sprintf("%s-%s", cdoc.SPDXDocRef.Doc2_2.Packages[0].PackageName, cdoc.SPDXDocRef.Doc2_2.Packages[0].PackageVersion)) + newRelationship := &spdx.Relationship2_2{ + RefA: rootDocElID, + RefB: elId, + Relationship: "DESCRIBES", + } + doc.SPDXDocRef.Doc2_2.Relationships = append(doc.SPDXDocRef.Doc2_2.Relationships, newRelationship) + } + if cdoc != nil && len(cdoc.SPDXDocRef.Doc2_2.Relationships) > 0 { + cdoc.SPDXDocRef.Doc2_2.Relationships = cdoc.SPDXDocRef.Doc2_2.Relationships[1:] + } + default: if cdoc != nil && len(cdoc.SPDXDocRef.Doc2_2.Packages) > 0 { elId := spdx.MakeDocElementID("", fmt.Sprintf("%s-%s", cdoc.SPDXDocRef.Doc2_2.Packages[0].PackageName, cdoc.SPDXDocRef.Doc2_2.Packages[0].PackageVersion)) @@ -122,10 +139,10 @@ func updateRelationships(spdxVersion string, doc *Document, composableDocs []*Do return doc, composableDocs } -func setDocElID(spdxVersion string, doc *Document) spdx.DocElementID { +func setDocElID(doc *Document) spdx.DocElementID { rootDocElID := spdx.DocElementID{} - switch spdxVersion { - case "2.2": + switch doc.ConfigDataRef.SPDXVersion { + case "SPDX-2.2": if len(doc.SPDXDocRef.Doc2_2.Packages) > 0 { rootDocElID = spdx.MakeDocElementID("", fmt.Sprintf("%s-%s", doc.SPDXDocRef.Doc2_2.Packages[0].PackageName, doc.SPDXDocRef.Doc2_2.Packages[0].PackageVersion))