bundler-sbom 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +11 -0
- data/Rakefile +6 -0
- data/lib/bundler/sbom/cli.rb +31 -0
- data/lib/bundler/sbom/generator.rb +118 -0
- data/lib/bundler/sbom/version.rb +5 -0
- data/lib/bundler/sbom.rb +3 -126
- data/plugins.rb +11 -8
- metadata +25 -8
- data/lib/bundler/cli/sbom.rb +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bb32eb6e21ec3b09fe1d3d199b978c44313f119ee57bb9d3899b20ae443020c0
|
4
|
+
data.tar.gz: 36b4b1b637fbafa93ac463418e5e26330146cff96b7c2e76733bc7c5b4a2cfc9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 39825222126b541eaf3df0b04c5b9784ce7cdc68d6fd837f642cad6e6ce5e6196a709a31154565f97efaea0ab8f1f483417daada360e331990a675880f3f75e6
|
7
|
+
data.tar.gz: f95ddbec1b92a9eeffd0732f31d30005e3fb36d85f3c381454977a7130e24630e32fe4d6fbfbf005f3cefaaa35d5bc264c92aa29403d64aff33050797727505b
|
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require "json"
|
2
|
+
require "bundler/sbom/generator"
|
3
|
+
|
4
|
+
module Bundler
|
5
|
+
module Sbom
|
6
|
+
class CLI < Thor
|
7
|
+
desc "dump", "Generate SBOM and save to bom.json"
|
8
|
+
def dump
|
9
|
+
sbom = Bundler::Sbom::Generator.generate_sbom
|
10
|
+
File.write("bom.json", JSON.pretty_generate(sbom))
|
11
|
+
Bundler.ui.info("Generated SBOM at bom.json")
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "license", "Display license report from existing bom.json"
|
15
|
+
def license
|
16
|
+
unless File.exist?("bom.json")
|
17
|
+
Bundler.ui.error("Error: bom.json not found. Run 'bundle sbom dump' first.")
|
18
|
+
exit 1
|
19
|
+
end
|
20
|
+
|
21
|
+
begin
|
22
|
+
sbom = JSON.parse(File.read("bom.json"))
|
23
|
+
Bundler::Sbom::Generator.display_license_report(sbom)
|
24
|
+
rescue JSON::ParserError
|
25
|
+
Bundler.ui.error("Error: bom.json is not a valid JSON file")
|
26
|
+
exit 1
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require "bundler"
|
2
|
+
require "securerandom"
|
3
|
+
|
4
|
+
module Bundler
|
5
|
+
module Sbom
|
6
|
+
class Generator
|
7
|
+
def self.generate_sbom
|
8
|
+
lockfile_path = Bundler.default_lockfile
|
9
|
+
unless lockfile_path.exist?
|
10
|
+
abort "No Gemfile.lock found. Run `bundle install` first."
|
11
|
+
end
|
12
|
+
|
13
|
+
lockfile = Bundler::LockfileParser.new(lockfile_path.read)
|
14
|
+
document_name = File.basename(Dir.pwd)
|
15
|
+
spdx_id = SecureRandom.uuid
|
16
|
+
|
17
|
+
sbom = {
|
18
|
+
"SPDXID" => "SPDXRef-DOCUMENT",
|
19
|
+
"spdxVersion" => "SPDX-2.2",
|
20
|
+
"creationInfo" => {
|
21
|
+
"created" => Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
22
|
+
"creators" => ["Tool: bundle-sbom"],
|
23
|
+
"licenseListVersion" => "3.17"
|
24
|
+
},
|
25
|
+
"name" => document_name,
|
26
|
+
"dataLicense" => "CC0-1.0",
|
27
|
+
"documentNamespace" => "https://spdx.org/spdxdocs/#{document_name}-#{spdx_id}",
|
28
|
+
"packages" => []
|
29
|
+
}
|
30
|
+
|
31
|
+
lockfile.specs.each do |spec|
|
32
|
+
begin
|
33
|
+
gemspec = Gem::Specification.find_by_name(spec.name, spec.version)
|
34
|
+
licenses = []
|
35
|
+
if gemspec
|
36
|
+
if gemspec.license && !gemspec.license.empty?
|
37
|
+
licenses << gemspec.license
|
38
|
+
end
|
39
|
+
|
40
|
+
if gemspec.licenses && !gemspec.licenses.empty?
|
41
|
+
licenses.concat(gemspec.licenses)
|
42
|
+
end
|
43
|
+
|
44
|
+
licenses.uniq!
|
45
|
+
end
|
46
|
+
|
47
|
+
license_string = licenses.empty? ? "NOASSERTION" : licenses.join(", ")
|
48
|
+
rescue Gem::LoadError
|
49
|
+
license_string = "NOASSERTION"
|
50
|
+
end
|
51
|
+
|
52
|
+
package = {
|
53
|
+
"SPDXID" => "SPDXRef-Package-#{spec.name}",
|
54
|
+
"name" => spec.name,
|
55
|
+
"versionInfo" => spec.version.to_s,
|
56
|
+
"downloadLocation" => "NOASSERTION",
|
57
|
+
"filesAnalyzed" => false,
|
58
|
+
"licenseConcluded" => license_string,
|
59
|
+
"licenseDeclared" => license_string,
|
60
|
+
"supplier" => "NOASSERTION",
|
61
|
+
"externalRefs" => [
|
62
|
+
{
|
63
|
+
"referenceCategory" => "PACKAGE_MANAGER",
|
64
|
+
"referenceType" => "purl",
|
65
|
+
"referenceLocator" => "pkg:gem/#{spec.name}@#{spec.version}"
|
66
|
+
}
|
67
|
+
]
|
68
|
+
}
|
69
|
+
sbom["packages"] << package
|
70
|
+
end
|
71
|
+
|
72
|
+
sbom
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.display_license_report(sbom)
|
76
|
+
license_count = analyze_licenses(sbom)
|
77
|
+
sorted_licenses = license_count.sort_by { |_, count| -count }
|
78
|
+
|
79
|
+
puts "=== License Usage in SBOM ==="
|
80
|
+
puts "Total packages: #{sbom["packages"].size}"
|
81
|
+
puts
|
82
|
+
|
83
|
+
sorted_licenses.each do |license, count|
|
84
|
+
puts "#{license}: #{count} package(s)"
|
85
|
+
end
|
86
|
+
|
87
|
+
puts "\n=== Packages by License ==="
|
88
|
+
sorted_licenses.each do |license, _|
|
89
|
+
packages = sbom["packages"].select do |package|
|
90
|
+
if package["licenseDeclared"].include?(",")
|
91
|
+
package["licenseDeclared"].split(",").map(&:strip).include?(license)
|
92
|
+
else
|
93
|
+
package["licenseDeclared"] == license
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
puts "\n#{license} (#{packages.size} package(s)):"
|
98
|
+
packages.each do |package|
|
99
|
+
puts " - #{package["name"]} (#{package["versionInfo"]})"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def self.analyze_licenses(sbom)
|
107
|
+
license_count = Hash.new(0)
|
108
|
+
sbom["packages"].each do |package|
|
109
|
+
licenses = package["licenseDeclared"].split(",").map(&:strip)
|
110
|
+
licenses.each do |license|
|
111
|
+
license_count[license] += 1
|
112
|
+
end
|
113
|
+
end
|
114
|
+
license_count
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
data/lib/bundler/sbom.rb
CHANGED
@@ -1,126 +1,3 @@
|
|
1
|
-
require "bundler"
|
2
|
-
require "
|
3
|
-
require "
|
4
|
-
require "rubygems"
|
5
|
-
|
6
|
-
module Bundler
|
7
|
-
module Sbom
|
8
|
-
class Generator
|
9
|
-
def self.generate_sbom
|
10
|
-
lockfile_path = Bundler.default_lockfile
|
11
|
-
unless lockfile_path.exist?
|
12
|
-
abort "No Gemfile.lock found. Run `bundle install` first."
|
13
|
-
end
|
14
|
-
|
15
|
-
lockfile = Bundler::LockfileParser.new(lockfile_path.read)
|
16
|
-
document_name = File.basename(Dir.pwd)
|
17
|
-
spdx_id = SecureRandom.uuid
|
18
|
-
|
19
|
-
sbom = {
|
20
|
-
"SPDXID" => "SPDXRef-DOCUMENT",
|
21
|
-
"spdxVersion" => "SPDX-2.2",
|
22
|
-
"creationInfo" => {
|
23
|
-
"created" => Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
24
|
-
"creators" => ["Tool: bundle-sbom"],
|
25
|
-
"licenseListVersion" => "3.17"
|
26
|
-
},
|
27
|
-
"name" => document_name,
|
28
|
-
"dataLicense" => "CC0-1.0",
|
29
|
-
"documentNamespace" => "https://spdx.org/spdxdocs/#{document_name}-#{spdx_id}",
|
30
|
-
"packages" => []
|
31
|
-
}
|
32
|
-
|
33
|
-
lockfile.specs.each do |spec|
|
34
|
-
begin
|
35
|
-
gemspec = Gem::Specification.find_by_name(spec.name, spec.version)
|
36
|
-
licenses = []
|
37
|
-
if gemspec
|
38
|
-
if gemspec.license && !gemspec.license.empty?
|
39
|
-
licenses << gemspec.license
|
40
|
-
end
|
41
|
-
|
42
|
-
if gemspec.licenses && !gemspec.licenses.empty?
|
43
|
-
licenses.concat(gemspec.licenses)
|
44
|
-
end
|
45
|
-
|
46
|
-
licenses.uniq!
|
47
|
-
end
|
48
|
-
|
49
|
-
license_string = licenses.empty? ? "NOASSERTION" : licenses.join(", ")
|
50
|
-
rescue Gem::LoadError
|
51
|
-
license_string = "NOASSERTION"
|
52
|
-
end
|
53
|
-
|
54
|
-
package = {
|
55
|
-
"SPDXID" => "SPDXRef-Package-#{spec.name}",
|
56
|
-
"name" => spec.name,
|
57
|
-
"versionInfo" => spec.version.to_s,
|
58
|
-
"downloadLocation" => "NOASSERTION",
|
59
|
-
"filesAnalyzed" => false,
|
60
|
-
"licenseConcluded" => license_string,
|
61
|
-
"licenseDeclared" => license_string,
|
62
|
-
"supplier" => "NOASSERTION",
|
63
|
-
"externalRefs" => [
|
64
|
-
{
|
65
|
-
"referenceCategory" => "PACKAGE_MANAGER",
|
66
|
-
"referenceType" => "purl",
|
67
|
-
"referenceLocator" => "pkg:gem/#{spec.name}@#{spec.version}"
|
68
|
-
}
|
69
|
-
]
|
70
|
-
}
|
71
|
-
sbom["packages"] << package
|
72
|
-
end
|
73
|
-
|
74
|
-
sbom
|
75
|
-
end
|
76
|
-
|
77
|
-
def self.analyze_licenses(sbom)
|
78
|
-
license_count = Hash.new(0)
|
79
|
-
|
80
|
-
sbom["packages"].each do |package|
|
81
|
-
license = package["licenseDeclared"]
|
82
|
-
|
83
|
-
if license.include?(",")
|
84
|
-
licenses = license.split(",").map(&:strip)
|
85
|
-
licenses.each do |lic|
|
86
|
-
license_count[lic] += 1
|
87
|
-
end
|
88
|
-
else
|
89
|
-
license_count[license] += 1
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
license_count
|
94
|
-
end
|
95
|
-
|
96
|
-
def self.display_license_report(sbom)
|
97
|
-
license_count = analyze_licenses(sbom)
|
98
|
-
sorted_licenses = license_count.sort_by { |_, count| -count }
|
99
|
-
|
100
|
-
puts "=== License Usage in SBOM ==="
|
101
|
-
puts "Total packages: #{sbom["packages"].size}"
|
102
|
-
puts
|
103
|
-
|
104
|
-
sorted_licenses.each do |license, count|
|
105
|
-
puts "#{license}: #{count} package(s)"
|
106
|
-
end
|
107
|
-
|
108
|
-
puts "\n=== Packages by License ==="
|
109
|
-
sorted_licenses.each do |license, _|
|
110
|
-
packages = sbom["packages"].select do |package|
|
111
|
-
if package["licenseDeclared"].include?(",")
|
112
|
-
package["licenseDeclared"].split(",").map(&:strip).include?(license)
|
113
|
-
else
|
114
|
-
package["licenseDeclared"] == license
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
puts "\n#{license} (#{packages.size} package(s)):"
|
119
|
-
packages.each do |package|
|
120
|
-
puts " - #{package["name"]} (#{package["versionInfo"]})"
|
121
|
-
end
|
122
|
-
end
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
1
|
+
require "bundler/sbom/version"
|
2
|
+
require "bundler/sbom/generator"
|
3
|
+
require "bundler/sbom/cli"
|
data/plugins.rb
CHANGED
@@ -1,11 +1,14 @@
|
|
1
|
-
require "bundler
|
1
|
+
require "bundler"
|
2
|
+
require "bundler/sbom"
|
2
3
|
|
3
|
-
Bundler
|
4
|
-
|
5
|
-
Bundler::
|
6
|
-
|
4
|
+
module Bundler
|
5
|
+
module Sbom
|
6
|
+
class Plugin < ::Bundler::Plugin::API
|
7
|
+
command "sbom"
|
7
8
|
|
8
|
-
|
9
|
-
|
9
|
+
def exec(command_name, args)
|
10
|
+
::Bundler::Sbom::CLI.start(args)
|
11
|
+
end
|
12
|
+
end
|
10
13
|
end
|
11
|
-
end
|
14
|
+
end
|
metadata
CHANGED
@@ -1,28 +1,45 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bundler-sbom
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- SHIBATA Hiroshi
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
10
|
date: 2025-03-05 00:00:00.000000000 Z
|
11
|
-
dependencies:
|
12
|
-
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: bundler
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '2.0'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '2.0'
|
26
|
+
description: Generate CycloneDX SBOM(Software Bill of Materials) files with Bundler
|
13
27
|
email:
|
14
28
|
- hsbt@ruby-lang.org
|
15
29
|
executables: []
|
16
30
|
extensions: []
|
17
31
|
extra_rdoc_files: []
|
18
32
|
files:
|
33
|
+
- Gemfile
|
19
34
|
- README.md
|
20
|
-
-
|
35
|
+
- Rakefile
|
21
36
|
- lib/bundler/sbom.rb
|
37
|
+
- lib/bundler/sbom/cli.rb
|
38
|
+
- lib/bundler/sbom/generator.rb
|
39
|
+
- lib/bundler/sbom/version.rb
|
22
40
|
- plugins.rb
|
23
41
|
homepage: https://github.com/hsbt/bundler-sbom
|
24
|
-
licenses:
|
25
|
-
- MIT
|
42
|
+
licenses: []
|
26
43
|
metadata:
|
27
44
|
homepage_uri: https://github.com/hsbt/bundler-sbom
|
28
45
|
source_code_uri: https://github.com/hsbt/bundler-sbom
|
@@ -35,7 +52,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
35
52
|
requirements:
|
36
53
|
- - ">="
|
37
54
|
- !ruby/object:Gem::Version
|
38
|
-
version:
|
55
|
+
version: 2.6.0
|
39
56
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
57
|
requirements:
|
41
58
|
- - ">="
|
@@ -44,5 +61,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
44
61
|
requirements: []
|
45
62
|
rubygems_version: 3.6.2
|
46
63
|
specification_version: 4
|
47
|
-
summary:
|
64
|
+
summary: Generate CycloneDX SBOM(Software Bill of Materials) files with Bundler
|
48
65
|
test_files: []
|
data/lib/bundler/cli/sbom.rb
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
require "bundler/cli"
|
2
|
-
require "bundler/sbom"
|
3
|
-
|
4
|
-
module Bundler
|
5
|
-
class CLI::Sbom
|
6
|
-
def initialize(options = {})
|
7
|
-
@options = options
|
8
|
-
end
|
9
|
-
|
10
|
-
def dump
|
11
|
-
sbom = Bundler::Sbom::Generator.generate_sbom
|
12
|
-
File.write("bom.json", JSON.pretty_generate(sbom))
|
13
|
-
Bundler.ui.info "Generated SBOM at bom.json"
|
14
|
-
end
|
15
|
-
|
16
|
-
def license
|
17
|
-
begin
|
18
|
-
sbom = JSON.parse(File.read("bom.json"))
|
19
|
-
Bundler::Sbom::Generator.display_license_report(sbom)
|
20
|
-
rescue Errno::ENOENT
|
21
|
-
Bundler.ui.error "Error: bom.json not found. Run 'bundle sbom dump' first."
|
22
|
-
exit 1
|
23
|
-
rescue JSON::ParserError
|
24
|
-
Bundler.ui.error "Error: bom.json is not a valid JSON file"
|
25
|
-
exit 1
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|