Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,13 @@ local_path_override(
module_name = "supply_chain_tools",
path = "../tools",
)

bazel_dep(
name = "supply-chain-go",
# Always overridden to use local path.
version = "HEAD",
)
local_path_override(
module_name = "supply-chain-go",
path = "../lib/supplychain-go",
)
8 changes: 4 additions & 4 deletions lib/supplychain-go/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,20 @@ package_metadata(
gazelle(name = "gazelle")

go_library(
name = "supply-chain-go",
name = "supplychain-go",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mind splitting this one out into its own PR?

Also, we should probably add an alias for the old name

srcs = [
"package_attribute.go",
"package_metadata.go",
],
importpath = "github.com/bazel-contrib/supply-chain/lib/supply-chain-go",
importpath = "github.com/bazel-contrib/supply-chain/lib/supplychain-go",
visibility = ["//visibility:public"],
deps = ["@com_github_package_url_packageurl_go//:packageurl-go"],
)

go_test(
name = "supply-chain-go_test",
name = "supplychain-go_test",
srcs = ["package_metadata_test.go"],
embed = [":supply-chain-go"],
embed = [":supplychain-go"],
deps = [
"@com_github_package_url_packageurl_go//:packageurl-go",
"@com_github_stretchr_testify//assert",
Expand Down
2 changes: 2 additions & 0 deletions lib/supplychain-go/MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ go_deps.from_file(go_mod = "//:go.mod")
use_repo(
go_deps,
"com_github_package_url_packageurl_go",
"com_github_spdx_tools_golang",
"com_github_stretchr_testify",
)

local_path_override(
module_name = "package_metadata",
path = "../../metadata",
)

23 changes: 23 additions & 0 deletions lib/supplychain-go/cmd/spdx/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
load("@rules_go//go:def.bzl", "go_binary", "go_library")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd put this into //sbom/spdx/generator. I'd like to keep //lib/supplychain-go light and not pull in lots of dependencies for all kinds of sbom formats


go_library(
name = "spdx_lib",
srcs = ["spdx.go"],
importpath = "github.com/bazel-contrib/supply-chain/lib/supplychain-go/cmd/spdx",
visibility = ["//visibility:private"],
deps = [
"//:supplychain-go",
"//internal/sbom",
"@com_github_spdx_tools_golang//json",
"@com_github_spdx_tools_golang//spdx",
"@com_github_spdx_tools_golang//spdx/v2/common",
"@com_github_spdx_tools_golang//tagvalue",
"@com_github_spdx_tools_golang//yaml",
],
)

go_binary(
name = "spdx",
embed = [":spdx_lib"],
visibility = ["//visibility:public"],
)
93 changes: 93 additions & 0 deletions lib/supplychain-go/cmd/spdx/spdx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package main

import (
"encoding/json"
"flag"
"fmt"
"os"

supplychain "github.com/bazel-contrib/supply-chain/lib/supplychain-go"
"github.com/bazel-contrib/supply-chain/lib/supplychain-go/internal/sbom"
spdxJson "github.com/spdx/tools-golang/json"
"github.com/spdx/tools-golang/spdx"
"github.com/spdx/tools-golang/spdx/v2/common"
spdxTV "github.com/spdx/tools-golang/tagvalue"
spdxYaml "github.com/spdx/tools-golang/yaml"
)

var (
outPath = flag.String("out", "", "The path to write the generated SPDX SBOM.")
configPath = flag.String("config", "", "The path to the SBOM generation configuration file.")
format = flag.String("format", "", "The output format of the SPDX SBOM.")
)

func main() {
flag.Parse()
var config sbom.GenConfig

configBytes, err := os.ReadFile(*configPath)
if err != nil {
panic(err)
}

json.Unmarshal(configBytes, &config)

out, err := os.OpenFile(*outPath, os.O_WRONLY|os.O_CREATE, 0664)
if err != nil {
panic(err)
}
defer out.Close()

doc, err := GenerateDocument(config)
if err != nil {
panic(err)
}

must := func(err error) {
if err != nil {
panic(err)
}
}

switch *format {
case "json":
must(spdxJson.Write(doc, out))
case "yaml":
must(spdxYaml.Write(doc, out))
case "tag-value":
must(spdxTV.Write(doc, out))
default:
panic(fmt.Sprintf("'%s' is not a supported format", *format))
}
}

func GenerateDocument(config sbom.GenConfig) (*spdx.Document, error) {
spdxPackages := make([]*spdx.Package, len(config.Deps))

for i, dep := range config.Deps {
pkgMetadata, err := supplychain.ReadPackageMetadataFromFile(dep.Metadata)
if err != nil {
return nil, err
}
spdxPackages[i] = &spdx.Package{
PackageSPDXIdentifier: common.ElementID(fmt.Sprintf("dep-%d", i)),
PackageExternalReferences: []*spdx.PackageExternalReference{
{
Category: "PACKAGE-MANAGER",
RefType: "purl",
Locator: pkgMetadata.GetPURL().String(),
},
},
PackageName: pkgMetadata.GetPURL().Name,
}
}

doc := spdx.Document{
SPDXIdentifier: "DOCUMENT",
SPDXVersion: "SPDX-2.3",
Packages: spdxPackages,
CreationInfo: &spdx.CreationInfo{},
}

return &doc, nil
}
3 changes: 3 additions & 0 deletions lib/supplychain-go/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ go 1.24.2

require (
github.com/package-url/packageurl-go v0.1.3
github.com/spdx/tools-golang v0.5.5
github.com/stretchr/testify v1.11.1
)

require (
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
24 changes: 20 additions & 4 deletions lib/supplychain-go/go.sum
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc=
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/package-url/packageurl-go v0.1.3 h1:4juMED3hHiz0set3Vq3KeQ75KD1avthoXLtmE3I0PLs=
github.com/package-url/packageurl-go v0.1.3/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM=
github.com/spdx/tools-golang v0.5.5 h1:61c0KLfAcNqAjlg6UNMdkwpMernhw3zVRwDZ2x9XOmk=
github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
8 changes: 8 additions & 0 deletions lib/supplychain-go/internal/sbom/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
load("@rules_go//go:def.bzl", "go_library")

go_library(
name = "sbom",
srcs = ["genconfig.go"],
importpath = "github.com/bazel-contrib/supply-chain/lib/supplychain-go/internal/sbom",
visibility = ["//:__subpackages__"],
)
9 changes: 9 additions & 0 deletions lib/supplychain-go/internal/sbom/genconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package sbom

type GenConfig struct {
Deps []DepConfig `json:"deps"`
}

type DepConfig struct {
Metadata string `json:"metadata"`
}
15 changes: 10 additions & 5 deletions tools/MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@ module(

# Do not update to newer versions until you need a specific new feature.
bazel_dep(name = "bazel_skylib", version = "1.8.1")
bazel_dep(name = "package_metadata", version = "0.0.5")
bazel_dep(
name = "package_metadata",
version = "HEAD", # Automatically updated by release pipeline.
)
bazel_dep(
name = "supply-chain-go",
version = "HEAD", # Automatically updated by release pipeline.
)


# Development tools
local_path_override(
module_name = "package_metadata",
path = "../metadata",
)
local_path_override(module_name = "package_metadata", path = "../metadata")
local_path_override(module_name = "supply-chain-go", path = "../lib/supplychain-go")

bazel_dep(name = "rules_python", version = "1.5.1", dev_dependency = True)
bazel_dep(name = "platforms", version = "1.0.0", dev_dependency = True)
Expand Down
23 changes: 23 additions & 0 deletions tools/sbom/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Rules for generating a SBOM out of a target."""

load("@bazel_skylib//:bzl_library.bzl", "bzl_library")

package(
default_package_metadata = ["//:package_metadata"],
default_visibility = ["//visibility:public"],
)

filegroup(
name = "srcs",
srcs = glob(["**"]),
)

bzl_library(
name = "sbom",
srcs = [
":sbom.bzl",
"//gather_metadata",
"//:core_metadata",
],
visibility = ["//visibility:public"],
)
12 changes: 12 additions & 0 deletions tools/sbom/providers.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Providers for the sbom rules.

Warning: This is private to the aspect that walks the tree. The API is subject
to change at any release.
"""

SbomInfo = provider(
doc = "A provider that contains the configuration for generating an SBOM.",
fields = {
"config": "The configuration file for generating the SBOM.",
}
)
72 changes: 72 additions & 0 deletions tools/sbom/sbom.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
load("providers.bzl", "SbomInfo")
load(
"@package_metadata//:defs.bzl",
"PackageAttributeInfo",
"PackageMetadataInfo",
)
load(
"@package_metadata//licenses/providers:license_kind_info.bzl",
"LicenseKindInfo",
)
load(
"@supply_chain_tools//gather_metadata:core.bzl",
"gather_metadata_info_common",
"should_traverse",
)
load(
"@supply_chain_tools//gather_metadata:providers.bzl",
"null_transitive_metadata_info",
"TransitiveMetadataInfo",
)

def _gather_metadata_info_impl(target, ctx):
return gather_metadata_info_common(
target,
ctx,
want_providers = [PackageAttributeInfo, PackageMetadataInfo, LicenseKindInfo],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do we need AttributeInfo and LicenseKindInfo for? They are referenced in the JSON file

provider_factory = TransitiveMetadataInfo,
null_provider_instance = null_transitive_metadata_info,
filter_func = should_traverse,
)

gather_metadata_info = aspect(
doc = """Collects metadata providers into a single TransitiveMetadataInfo provider.""",
implementation = _gather_metadata_info_impl,
attr_aspects = ["*"],
attrs = {
"_trace": attr.label(default = "@supply_chain_tools//gather_metadata:trace_target"),
},
provides = [TransitiveMetadataInfo],
apply_to_generating_rules = True,
)

def _sbom_impl(ctx):
transitive_metadata_info = ctx.attr.target[TransitiveMetadataInfo]
transitive_inputs = []
config = { "deps": [] }
for m in transitive_metadata_info.metadata.to_list():
config["deps"].append({
"metadata": m.metadata.path
})
transitive_inputs.append(m.files)

sbom_gen_config = ctx.actions.declare_file("{name}.sbom.config.json".format(name=ctx.attr.name))
ctx.actions.write(sbom_gen_config, json.encode(config))

return [
DefaultInfo(files=depset(
[sbom_gen_config],
transitive=transitive_inputs
)),
SbomInfo(config=sbom_gen_config),
]

def sbom_rule(gathering_aspect):
return rule(
_sbom_impl,
attrs = {
"target": attr.label(aspects = [gathering_aspect], doc="The target for which to generate an SBOM."),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From internal experience, this should be label_list

},
)

sbom = sbom_rule(gathering_aspect=gather_metadata_info)
Loading