From 52be74dbefd47ca9b3de2721b2059c70645a699e Mon Sep 17 00:00:00 2001 From: Nisha Kumar Date: Thu, 11 Jan 2024 15:59:57 -0800 Subject: [PATCH] Plugin: Add example for SPDX SBOM generator plugin - Includes the `plugin` package which is an interface to be implemented by plugins. - Includes an example of an implementation of a plugin where a pom.xml file is parsed and the data is used to create an SPDX 2.3 JSON document. Signed-off-by: Nisha Kumar --- examples/pomtospdx/README.md | 13 ++ examples/pomtospdx/main.go | 25 +++ examples/pomtospdx/mvnpom/mvnpom.go | 171 ++++++++++++++ examples/pomtospdx/pom.xml | 330 ++++++++++++++++++++++++++++ go.mod | 5 +- go.sum | 6 +- pkg/plugin/plugin.go | 42 ++++ 7 files changed, 588 insertions(+), 4 deletions(-) create mode 100644 examples/pomtospdx/README.md create mode 100644 examples/pomtospdx/main.go create mode 100644 examples/pomtospdx/mvnpom/mvnpom.go create mode 100644 examples/pomtospdx/pom.xml create mode 100644 pkg/plugin/plugin.go diff --git a/examples/pomtospdx/README.md b/examples/pomtospdx/README.md new file mode 100644 index 0000000..397732a --- /dev/null +++ b/examples/pomtospdx/README.md @@ -0,0 +1,13 @@ +# Plugin Example: Parse a pom.xml file and generate an SPDX 2.3 JSON document + +## Writing the Plugin + +In this example, we will parse an example pom.xml file and create an SPDX document with the data. The plugin is called `mvnpom`. We use an existing pom.xml parser to get the data. We have restricted parsing to only SPDX 2.3 as the SPDX version and JSON as the data format, but other versions and formats can be supported. + +The required function to implement for the plugin is the `GetSpdxDocument` method. This must return an object of type `AnyDocument`. + +In order to make this plugin "discoverable", the `init` function must be implemented. This function should use the `plugin` module's `Register` function, giving the name of the plugin. In this case, the name is "mvnpom". + +## Using the Plugin + +In the `main.go` program, we use an empty import to import the plugin `mvnpom`. We first get the plugin object. Then we call the object's `GetSpdxDocument()` function. From here we can create a JSON string using the `Marshal` function which takes care of creating an SPDX JSON document. diff --git a/examples/pomtospdx/main.go b/examples/pomtospdx/main.go new file mode 100644 index 0000000..2280a37 --- /dev/null +++ b/examples/pomtospdx/main.go @@ -0,0 +1,25 @@ +package main + +import ( + "encoding/json" + "fmt" + + _ "github.com/spdx/spdx-sbom-generator/examples/pomtospdx/mvnpom" + "github.com/spdx/spdx-sbom-generator/pkg/plugin" +) + +func main() { + pg, err := plugin.GetPlugin("mvnpom") + if err != nil { + fmt.Println(err) + } + spdxdoc, err2 := pg.GetSpdxDocument() + if err2 != nil { + fmt.Println(err2) + } + spdxdocjson, err3 := json.Marshal(spdxdoc) + if err3 != nil { + fmt.Println(err3) + } + fmt.Println(string(spdxdocjson)) +} diff --git a/examples/pomtospdx/mvnpom/mvnpom.go b/examples/pomtospdx/mvnpom/mvnpom.go new file mode 100644 index 0000000..6c41c42 --- /dev/null +++ b/examples/pomtospdx/mvnpom/mvnpom.go @@ -0,0 +1,171 @@ +// package mvnpom implements the plugin.Plugin interface +package mvnpom + +import ( + "errors" + "fmt" + "path/filepath" + "time" + + "github.com/spdx/spdx-sbom-generator/pkg/plugin" + + purl "github.com/package-url/packageurl-go" + c "github.com/spdx/tools-golang/spdx/common" + v2 "github.com/spdx/tools-golang/spdx/v2/common" + "github.com/spdx/tools-golang/spdx/v2/v2_3" + "github.com/vifraa/gopom" + "golang.org/x/exp/slices" +) + +// name is used to register this plugin +var name = "mvnpom" + +// supportedSpdxVersions contains all supported SPDX versions for MvnPomPlugin +var supportedSpdxVersions = [1]string{"2.3"} + +// supportedFormats contains all supported formats for MvnPomPlugin +var supportedFormats = [1]string{"json"} + +var topSPDXPackageId v2.ElementID = "SPDXRef-0" + +// MvnPomPlugin implements Plugin +type MvnPomPlugin struct { + PluginPath string // this Plugin requires a path to the pom.xml file + SpdxVersion string // this Plugin requires an SPDX spec version + SpdxFormat string // this Plugin requires an SPDX format + +} + +// NewMvnPomPlugin returns a pointer to an MvnPomPlugin +// object with default values +// Ideally, this struct will also contain command line options +// for which a NewWithOptions function may be used +func NewMvnPomPlugin() *MvnPomPlugin { + return &MvnPomPlugin{ + PluginPath: ".", + SpdxVersion: "2.3", + SpdxFormat: "json", + } +} + +// GetSpdxDocument returns an SPDX document of supported format +func (mpp MvnPomPlugin) GetSpdxDocument() (c.AnyDocument, error) { + // return an error if the SPDX version is not supported + s := supportedSpdxVersions[:] + f := supportedFormats[:] + if !slices.Contains(s, mpp.SpdxVersion) { + return nil, errors.New("unsupported SPDX version") + } + if !slices.Contains(f, mpp.SpdxFormat) { + return nil, errors.New("unsupported SPDX format") + } + // read the pom.xml file + pomPath := filepath.Join(mpp.PluginPath, "pom.xml") + parsedPom, err := gopom.Parse(pomPath) + if err != nil { + return nil, err + } + // create all structs + // doc CreationInfo + cinfo := v2_3.CreationInfo{ + Creators: getDocCreators(), + Created: getDocTimestamp(), + } + doc := v2_3.Document{ + SPDXVersion: v2_3.Version, + DataLicense: v2_3.DataLicense, + SPDXIdentifier: "SPDXRef-DOCUMENT", + DocumentName: "example_mvn_pom_spdx", + DocumentNamespace: fmt.Sprintf("http://spdx.org/documents/%s-%s", *parsedPom.ArtifactID, *parsedPom.Version), + CreationInfo: &cinfo, + Packages: getPomPackages(parsedPom), + } + return doc, nil +} + +// init registers this plugin +func init() { + mpp := NewMvnPomPlugin() + if name != "" { + plugin.Register(name, mpp) + } +} + +// unexported functions + +func getDocCreators() []v2.Creator { + var creators []v2.Creator + tc := v2.Creator{ + Creator: "pomtospdx", + CreatorType: "Tool", + } + creators = append(creators, tc) + return creators +} + +func getDocTimestamp() string { + t := time.Now() + return fmt.Sprintf("%d-%02d-%02dT%02d:%02d:%02d", + t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second()) +} + +func getPomPackages(p *gopom.Project) []*v2_3.Package { + var packages []*v2_3.Package + // get the top package first + tsupplier := v2.Supplier{ + Supplier: *p.Organization.Name, + SupplierType: "Organization", + } + tdl := purl.NewPackageURL("maven", + *p.GroupID, + *p.Name, + *p.Version, + nil, + "") + tpackage := v2_3.Package{ + IsUnpackaged: false, + PackageName: *p.Name, + PackageSPDXIdentifier: topSPDXPackageId, + PackageVersion: *p.Version, + PackageSupplier: &tsupplier, + PackageDownloadLocation: tdl.ToString(), + FilesAnalyzed: false, + PackageLicenseDeclared: "NOASSERTION", + PrimaryPackagePurpose: "APPLICATION", + } + packages = append(packages, &tpackage) + // get declared dependencies + for i, d := range *p.Dependencies { + if d.Scope == nil { + spdxid := fmt.Sprintf("SPDXRef-%d", i+1) + pkg := v2_3.Package{ + PackageName: *d.ArtifactID, + PackageSPDXIdentifier: v2.ElementID(spdxid), + PackageVersion: *d.Version, + PackageSupplier: getDepSupplier(d), + PackageDownloadLocation: getDepPurl(d), + FilesAnalyzed: false, + } + packages = append(packages, &pkg) + } + + } + return packages +} + +func getDepSupplier(d gopom.Dependency) *v2.Supplier { + return &v2.Supplier{ + Supplier: *d.GroupID, + SupplierType: "Organization", + } +} + +func getDepPurl(d gopom.Dependency) string { + ddl := purl.NewPackageURL("maven", + *d.GroupID, + *d.ArtifactID, + *d.Version, + nil, + "") + return ddl.ToString() +} diff --git a/examples/pomtospdx/pom.xml b/examples/pomtospdx/pom.xml new file mode 100644 index 0000000..911c50c --- /dev/null +++ b/examples/pomtospdx/pom.xml @@ -0,0 +1,330 @@ + + 4.0.0 + + org.spdx + tools-java + 1.1.8-SNAPSHOT + jar + + tools-java + https://spdx.dev/ + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + goneall + Gary O'Neall + gary@sourceauditor.com + SPDX + http://spdx.org + + + + SPDX + http://spdx.org + + + https://github.com/spdx/tools-java + https://github.com/spdx/tools-java.git + https://github.com/spdx/tools-java.git + + + Github + https://github.com/spdx/tools-java/issues + + SPDX Command Line Tools using the Spdx-Java-Library + + Github Actions + https://github.com/spdx/tools-java/actions + + + + ossrh + spdx-spdx-tools + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + UTF-8 + https://sonarcloud.io + spdx + tools-java + 8.0.1 + 11 + -Xdoclint:none + + + + gpg-signing + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + ${gpg.keyname} + ${gpg.keyname} + + + + + + + + + + + junit + junit + 4.13.1 + test + + + org.spdx + java-spdx-library + 1.1.10 + + + org.spdx + spdx-rdf-store + 1.1.9 + + + org.spdx + spdx-jackson-store + 1.1.9 + + + org.apache.ws.xmlschema + xmlschema-core + 2.3.0 + + + org.spdx + spdx-spreadsheet-store + 1.1.7 + + + org.spdx + spdx-tagvalue-store + 1.1.7 + + + com.github.java-json-tools + json-schema-validator + 2.2.14 + + + + + + resources + false + resources + + **/* + + + + META-INF + false + . + + LICENSE + + + + src/main + + **/*.java + + + + src/main/resources + true + + + + + src/test + + **/*.java + + + + false + TestFiles + + **/* + + + + + + org.owasp + dependency-check-maven + ${dependency-check-maven.version} + + dependency-check-supress.xml + + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.2.1 + + + enforce-java + + enforce + + + + + ${maven.compiler.release} + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.1 + + ${project.build.sourceEncoding} + true + true + true + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9 + + true + 8 + ${env.JAVA_HOME}/bin/javadoc + -Xdoclint:none + + + + attach-javadocs + + ${javadoc.opts} + + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + attach-sources + verify + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-shade-plugin + + true + jar-with-dependencies + + + org.spdx.tools.Main + + + + + + *:* + + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + package + + + shade + + + + + + org.spdx + spdx-maven-plugin + 0.6.0 + + + build-spdx + prepare-package + + createSPDX + + + + + http://spdx.org/documents/tools-java-${project.version} + Copyright (c) 2020 Source Auditor Inc. + + Gary O'Neall + + Apache-2.0 + Apache-2.0 + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + Apache-2.0 + Apache-2.0 + + Person: Gary O'Neall + + Organization: Linux Foundation + + + + + diff --git a/go.mod b/go.mod index 6881d54..3cfacc9 100644 --- a/go.mod +++ b/go.mod @@ -9,12 +9,14 @@ require ( github.com/go-git/go-git/v5 v5.7.0 github.com/google/uuid v1.2.0 github.com/opensbom-generator/parsers v0.0.0-20230627202907-fc5a182b1325 + github.com/package-url/packageurl-go v0.1.2 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.3 github.com/spdx/tools-golang v0.5.2 github.com/spf13/cobra v1.7.0 github.com/stretchr/testify v1.8.4 - github.com/vifraa/gopom v0.2.1 + github.com/vifraa/gopom v1.0.0 + golang.org/x/exp v0.0.0-20221006183845-316c7553db56 golang.org/x/mod v0.11.0 ) @@ -49,7 +51,6 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect golang.org/x/crypto v0.10.0 // indirect - golang.org/x/exp v0.0.0-20221006183845-316c7553db56 // indirect golang.org/x/net v0.11.0 // indirect golang.org/x/sys v0.9.0 // indirect golang.org/x/text v0.10.0 // indirect diff --git a/go.sum b/go.sum index 3b4fe7e..08f51b4 100644 --- a/go.sum +++ b/go.sum @@ -78,6 +78,8 @@ github.com/neurosnap/sentences v1.0.6/go.mod h1:pg1IapvYpWCJJm/Etxeh0+gtMf1rI1ST github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/opensbom-generator/parsers v0.0.0-20230627202907-fc5a182b1325 h1:qDzp7GLqDahifTL4aiUHxN8HdWPBy1VWpcdEg+vBKdI= github.com/opensbom-generator/parsers v0.0.0-20230627202907-fc5a182b1325/go.mod h1:tnaZ++n4Okk8pPcYM9fEGDSDCW2midqZU+jabF5LgnU= +github.com/package-url/packageurl-go v0.1.2 h1:0H2DQt6DHd/NeRlVwW4EZ4oEI6Bn40XlNPRqegcxuo4= +github.com/package-url/packageurl-go v0.1.2/go.mod h1:uQd4a7Rh3ZsVg5j0lNyAfyxIeGde9yrlhjF78GzeW0c= github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= @@ -118,8 +120,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/vifraa/gopom v0.2.1 h1:MYVMAMyiGzXPPy10EwojzKIL670kl5Zbae+o3fFvQEM= -github.com/vifraa/gopom v0.2.1/go.mod h1:oPa1dcrGrtlO37WPDBm5SqHAT+wTgF8An1Q71Z6Vv4o= +github.com/vifraa/gopom v1.0.0 h1:L9XlKbyvid8PAIK8nr0lihMApJQg/12OBvMA28BcWh0= +github.com/vifraa/gopom v1.0.0/go.mod h1:oPa1dcrGrtlO37WPDBm5SqHAT+wTgF8An1Q71Z6Vv4o= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go new file mode 100644 index 0000000..dce4cd2 --- /dev/null +++ b/pkg/plugin/plugin.go @@ -0,0 +1,42 @@ +// package plugin provides the plugin interface for generating SPDX SBOMs +package plugin + +import ( + "errors" + + "github.com/spdx/tools-golang/spdx/common" +) + +// Plugin is the interface a plugin must implement +// GetSpdxDocument returns an object of type AnyDocument +type Plugin interface { + GetSpdxDocument() (common.AnyDocument, error) +} + +// plugins stores a mapping of the plugin name and its +// corresponding Plugin object +var plugins = make(map[string]Plugin) + +// Register registers a plugin by name +// A plugin can use this function to create a plugin entry in their +// init function +func Register(name string, p Plugin) { + if p == nil { + panic("A Plugin object is required, but is nil") + } + if _, dup := plugins[name]; dup { + panic("Register called twice for plugin name " + name) + } + plugins[name] = p +} + +// GetPlugin takes the name of a plugin and returns the +// Plugin object for that plugin +func GetPlugin(name string) (Plugin, error) { + p := plugins[name] + if p == nil { + return nil, errors.New("No plugin registered with name " + name) + } else { + return p, nil + } +}