bundler-sbom 0.1.7 → 0.2.0
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 +1 -1
- data/README.md +43 -5
- data/Rakefile +1 -1
- data/lib/bundler/sbom/cli.rb +17 -17
- data/lib/bundler/sbom/cyclonedx.rb +40 -37
- data/lib/bundler/sbom/generator.rb +2 -2
- data/lib/bundler/sbom/reporter.rb +3 -3
- data/lib/bundler/sbom/spdx.rb +29 -22
- data/lib/bundler/sbom/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f578e54cb4d73e62b18d305684a0ff770edb7cde70b31fdd8cb9eaa7b53e0885
|
|
4
|
+
data.tar.gz: 9e853789b6b01962185dde4a528f84c1e1f16f4432d4a9c5f57b16d5680e0489
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4bd4342d874517d03999ba5166fac776610ae3832d79eb620732de9cb7fa6e82614b61ba52a1dc89c79580d1985dad790c6f2ca4c641fd10afbc65ed32fe1b85
|
|
7
|
+
data.tar.gz: 9f30c4813af67c7882dcec7d132e8c324bc243d6f8fb19cef7a8571a9678fbbf0b92d15fd1a598f0afe6f73497faeea428bbbcd3d03838f3347380f4e44f722c
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -14,24 +14,62 @@ $ bundler plugin install bundler-sbom
|
|
|
14
14
|
|
|
15
15
|
### Generate SBOM
|
|
16
16
|
|
|
17
|
-
To generate an SBOM file
|
|
17
|
+
To generate an SBOM file from your project's Gemfile.lock:
|
|
18
18
|
|
|
19
19
|
```
|
|
20
|
-
$ bundle sbom dump
|
|
20
|
+
$ bundle sbom dump [options]
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
Available options:
|
|
24
|
+
- `-f, --format FORMAT`: Output format (json or xml, default: json)
|
|
25
|
+
- `-s, --sbom FORMAT`: SBOM specification format (spdx or cyclonedx, default: spdx)
|
|
26
|
+
|
|
27
|
+
Generated files will be named according to the following pattern:
|
|
28
|
+
- SPDX format: `bom.json` or `bom.xml`
|
|
29
|
+
- CycloneDX format: `bom-cyclonedx.json` or `bom-cyclonedx.xml`
|
|
30
|
+
|
|
31
|
+
Examples:
|
|
32
|
+
```
|
|
33
|
+
$ bundle sbom dump # Generates SPDX format in JSON (bom.json)
|
|
34
|
+
$ bundle sbom dump -f xml # Generates SPDX format in XML (bom.xml)
|
|
35
|
+
$ bundle sbom dump -s cyclonedx # Generates CycloneDX format in JSON (bom-cyclonedx.json)
|
|
36
|
+
$ bundle sbom dump -s cyclonedx -f xml # Generates CycloneDX format in XML (bom-cyclonedx.xml)
|
|
37
|
+
```
|
|
24
38
|
|
|
25
39
|
### Analyze License Information
|
|
26
40
|
|
|
27
41
|
To view a summary of licenses used in your project's dependencies:
|
|
28
42
|
|
|
29
43
|
```
|
|
30
|
-
$ bundle sbom license
|
|
44
|
+
$ bundle sbom license [options]
|
|
31
45
|
```
|
|
32
46
|
|
|
47
|
+
Available options:
|
|
48
|
+
- `-f, --file PATH`: Input SBOM file path
|
|
49
|
+
- `-F, --format FORMAT`: Input format (json or xml)
|
|
50
|
+
|
|
51
|
+
If no options are specified, the command will automatically look for SBOM files in the following order:
|
|
52
|
+
1. `bom.xml` (if format is xml)
|
|
53
|
+
2. `bom-cyclonedx.json`
|
|
54
|
+
3. `bom-cyclonedx.xml`
|
|
55
|
+
4. `bom.json`
|
|
56
|
+
|
|
33
57
|
This command will show:
|
|
34
58
|
- A count of packages using each license
|
|
35
59
|
- A detailed list of packages grouped by license
|
|
36
60
|
|
|
37
|
-
Note: The `license` command requires that you've already generated the SBOM using `bundle sbom dump`.
|
|
61
|
+
Note: The `license` command requires that you've already generated the SBOM using `bundle sbom dump`.
|
|
62
|
+
|
|
63
|
+
## Supported SBOM Formats
|
|
64
|
+
|
|
65
|
+
### SPDX
|
|
66
|
+
[SPDX (Software Package Data Exchange)](https://spdx.dev/) is a standard format for communicating software bill of material information, including components, licenses, copyrights, and security references.
|
|
67
|
+
|
|
68
|
+
### CycloneDX
|
|
69
|
+
[CycloneDX](https://cyclonedx.org/) is a lightweight SBOM specification designed for use in application security contexts and supply chain component analysis.
|
|
70
|
+
|
|
71
|
+
## References
|
|
72
|
+
|
|
73
|
+
- [SPDX Specification](https://spdx.github.io/spdx-spec/)
|
|
74
|
+
- [CycloneDX Specification](https://cyclonedx.org/specification/overview/)
|
|
75
|
+
- [About Software Bill of Materials (SBOM)](https://www.cisa.gov/sbom)
|
data/Rakefile
CHANGED
data/lib/bundler/sbom/cli.rb
CHANGED
|
@@ -26,24 +26,24 @@ module Bundler
|
|
|
26
26
|
|
|
27
27
|
# Generate SBOM based on specified format
|
|
28
28
|
sbom = Bundler::Sbom::Generator.generate_sbom(sbom_format)
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
# Determine file extension based on output format
|
|
31
|
-
ext = format == "json" ? "json" : "xml"
|
|
32
|
-
|
|
31
|
+
ext = (format == "json") ? "json" : "xml"
|
|
32
|
+
|
|
33
33
|
# Determine filename prefix based on SBOM format
|
|
34
|
-
prefix = sbom_format == "spdx" ? "bom" : "bom-cyclonedx"
|
|
34
|
+
prefix = (sbom_format == "spdx") ? "bom" : "bom-cyclonedx"
|
|
35
35
|
output_file = "#{prefix}.#{ext}"
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
if format == "json"
|
|
38
38
|
File.write(output_file, JSON.pretty_generate(sbom))
|
|
39
39
|
else # xml
|
|
40
40
|
xml_content = Bundler::Sbom::Generator.convert_to_xml(sbom)
|
|
41
41
|
File.write(output_file, xml_content)
|
|
42
42
|
end
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
Bundler.ui.info("Generated #{sbom_format.upcase} SBOM at #{output_file}")
|
|
45
45
|
end
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
desc "license", "Display license report from SBOM file"
|
|
48
48
|
method_option :file, type: :string, desc: "Input SBOM file path", aliases: "-f"
|
|
49
49
|
method_option :format, type: :string, desc: "Input format: json or xml", aliases: "-F"
|
|
@@ -59,19 +59,19 @@ module Bundler
|
|
|
59
59
|
|
|
60
60
|
# Determine input file based on format or find default files
|
|
61
61
|
if input_file.nil?
|
|
62
|
-
if format == "xml" || (format.nil? && File.exist?("bom.xml"))
|
|
63
|
-
|
|
62
|
+
input_file = if format == "xml" || (format.nil? && File.exist?("bom.xml"))
|
|
63
|
+
"bom.xml"
|
|
64
64
|
elsif File.exist?("bom-cyclonedx.json")
|
|
65
|
-
|
|
65
|
+
"bom-cyclonedx.json"
|
|
66
66
|
elsif File.exist?("bom-cyclonedx.xml")
|
|
67
|
-
|
|
67
|
+
"bom-cyclonedx.xml"
|
|
68
68
|
else
|
|
69
|
-
|
|
69
|
+
"bom.json"
|
|
70
70
|
end
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
unless File.exist?(input_file)
|
|
74
|
-
file_type = File.extname(input_file) == ".xml" ? "xml" : "json"
|
|
74
|
+
file_type = (File.extname(input_file) == ".xml") ? "xml" : "json"
|
|
75
75
|
sbom_type = input_file.include?("cyclonedx") ? "cyclonedx" : "spdx"
|
|
76
76
|
Bundler.ui.error("Error: #{input_file} not found. Run 'bundle sbom dump --format=#{file_type} --sbom=#{sbom_type}' first.")
|
|
77
77
|
exit 1
|
|
@@ -79,18 +79,18 @@ module Bundler
|
|
|
79
79
|
|
|
80
80
|
begin
|
|
81
81
|
content = File.read(input_file)
|
|
82
|
-
|
|
82
|
+
|
|
83
83
|
sbom = if format == "xml" || (!format && File.extname(input_file) == ".xml")
|
|
84
84
|
Bundler::Sbom::Generator.parse_xml(content)
|
|
85
85
|
else
|
|
86
86
|
JSON.parse(content)
|
|
87
87
|
end
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
Bundler::Sbom::Reporter.display_license_report(sbom)
|
|
90
90
|
rescue JSON::ParserError
|
|
91
91
|
Bundler.ui.error("Error: #{input_file} is not a valid JSON file")
|
|
92
92
|
exit 1
|
|
93
|
-
rescue
|
|
93
|
+
rescue => e
|
|
94
94
|
Bundler.ui.error("Error processing #{input_file}: #{e.message}")
|
|
95
95
|
exit 1
|
|
96
96
|
end
|
|
@@ -102,4 +102,4 @@ module Bundler
|
|
|
102
102
|
end
|
|
103
103
|
end
|
|
104
104
|
end
|
|
105
|
-
end
|
|
105
|
+
end
|
|
@@ -31,7 +31,12 @@ module Bundler
|
|
|
31
31
|
"components" => []
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
# Deduplicate specs by name and version
|
|
35
|
+
seen_gems = Set.new
|
|
34
36
|
lockfile.specs.each do |spec|
|
|
37
|
+
gem_key = "#{spec.name}:#{spec.version}"
|
|
38
|
+
next if seen_gems.include?(gem_key)
|
|
39
|
+
seen_gems.add(gem_key)
|
|
35
40
|
begin
|
|
36
41
|
gemspec = Gem::Specification.find_by_name(spec.name, spec.version)
|
|
37
42
|
licenses = []
|
|
@@ -56,7 +61,7 @@ module Bundler
|
|
|
56
61
|
}
|
|
57
62
|
|
|
58
63
|
unless licenses.empty?
|
|
59
|
-
component["licenses"] = licenses.map { |license| {
|
|
64
|
+
component["licenses"] = licenses.map { |license| {"license" => {"id" => license}} }
|
|
60
65
|
end
|
|
61
66
|
|
|
62
67
|
sbom["components"] << component
|
|
@@ -68,72 +73,72 @@ module Bundler
|
|
|
68
73
|
def self.to_xml(sbom)
|
|
69
74
|
doc = REXML::Document.new
|
|
70
75
|
doc << REXML::XMLDecl.new("1.0", "UTF-8")
|
|
71
|
-
|
|
76
|
+
|
|
72
77
|
# Root element
|
|
73
78
|
root = REXML::Element.new("bom")
|
|
74
79
|
root.add_namespace("http://cyclonedx.org/schema/bom/1.4")
|
|
75
80
|
root.add_attributes({
|
|
76
81
|
"serialNumber" => sbom["serialNumber"],
|
|
77
|
-
"version" => sbom["version"].to_s
|
|
82
|
+
"version" => sbom["version"].to_s
|
|
78
83
|
})
|
|
79
84
|
doc.add_element(root)
|
|
80
|
-
|
|
85
|
+
|
|
81
86
|
# Metadata
|
|
82
87
|
metadata = REXML::Element.new("metadata")
|
|
83
88
|
root.add_element(metadata)
|
|
84
|
-
|
|
89
|
+
|
|
85
90
|
add_element(metadata, "timestamp", sbom["metadata"]["timestamp"])
|
|
86
|
-
|
|
91
|
+
|
|
87
92
|
# Tools
|
|
88
93
|
tools = REXML::Element.new("tools")
|
|
89
94
|
metadata.add_element(tools)
|
|
90
|
-
|
|
95
|
+
|
|
91
96
|
sbom["metadata"]["tools"].each do |tool_data|
|
|
92
97
|
tool = REXML::Element.new("tool")
|
|
93
98
|
tools.add_element(tool)
|
|
94
|
-
|
|
99
|
+
|
|
95
100
|
add_element(tool, "vendor", tool_data["vendor"])
|
|
96
101
|
add_element(tool, "name", tool_data["name"])
|
|
97
102
|
add_element(tool, "version", tool_data["version"].to_s)
|
|
98
103
|
end
|
|
99
|
-
|
|
104
|
+
|
|
100
105
|
# Component (root project)
|
|
101
106
|
component = REXML::Element.new("component")
|
|
102
107
|
component.add_attribute("type", sbom["metadata"]["component"]["type"])
|
|
103
108
|
metadata.add_element(component)
|
|
104
|
-
|
|
109
|
+
|
|
105
110
|
add_element(component, "name", sbom["metadata"]["component"]["name"])
|
|
106
111
|
add_element(component, "version", sbom["metadata"]["component"]["version"])
|
|
107
|
-
|
|
112
|
+
|
|
108
113
|
# Components
|
|
109
114
|
components = REXML::Element.new("components")
|
|
110
115
|
root.add_element(components)
|
|
111
|
-
|
|
116
|
+
|
|
112
117
|
sbom["components"].each do |comp_data|
|
|
113
118
|
comp = REXML::Element.new("component")
|
|
114
119
|
comp.add_attribute("type", comp_data["type"])
|
|
115
120
|
components.add_element(comp)
|
|
116
|
-
|
|
121
|
+
|
|
117
122
|
add_element(comp, "name", comp_data["name"])
|
|
118
123
|
add_element(comp, "version", comp_data["version"])
|
|
119
124
|
add_element(comp, "purl", comp_data["purl"])
|
|
120
|
-
|
|
125
|
+
|
|
121
126
|
# Licenses
|
|
122
127
|
if comp_data["licenses"] && !comp_data["licenses"].empty?
|
|
123
128
|
licenses = REXML::Element.new("licenses")
|
|
124
129
|
comp.add_element(licenses)
|
|
125
|
-
|
|
130
|
+
|
|
126
131
|
comp_data["licenses"].each do |license_data|
|
|
127
132
|
license = REXML::Element.new("license")
|
|
128
133
|
licenses.add_element(license)
|
|
129
|
-
|
|
134
|
+
|
|
130
135
|
if license_data["license"]["id"]
|
|
131
136
|
add_element(license, "id", license_data["license"]["id"])
|
|
132
137
|
end
|
|
133
138
|
end
|
|
134
139
|
end
|
|
135
140
|
end
|
|
136
|
-
|
|
141
|
+
|
|
137
142
|
formatter = REXML::Formatters::Pretty.new(2)
|
|
138
143
|
formatter.compact = true
|
|
139
144
|
output = ""
|
|
@@ -143,7 +148,7 @@ module Bundler
|
|
|
143
148
|
|
|
144
149
|
def self.parse_xml(doc)
|
|
145
150
|
root = doc.root
|
|
146
|
-
|
|
151
|
+
|
|
147
152
|
sbom = {
|
|
148
153
|
"bomFormat" => "CycloneDX",
|
|
149
154
|
"specVersion" => "1.4",
|
|
@@ -160,7 +165,7 @@ module Bundler
|
|
|
160
165
|
},
|
|
161
166
|
"components" => []
|
|
162
167
|
}
|
|
163
|
-
|
|
168
|
+
|
|
164
169
|
# Collect tools
|
|
165
170
|
REXML::XPath.each(root, "metadata/tools/tool") do |tool|
|
|
166
171
|
tool_data = {
|
|
@@ -170,7 +175,7 @@ module Bundler
|
|
|
170
175
|
}
|
|
171
176
|
sbom["metadata"]["tools"] << tool_data
|
|
172
177
|
end
|
|
173
|
-
|
|
178
|
+
|
|
174
179
|
# Collect components
|
|
175
180
|
REXML::XPath.each(root, "components/component") do |comp|
|
|
176
181
|
component = {
|
|
@@ -179,26 +184,26 @@ module Bundler
|
|
|
179
184
|
"version" => get_element_text(comp, "version"),
|
|
180
185
|
"purl" => get_element_text(comp, "purl")
|
|
181
186
|
}
|
|
182
|
-
|
|
187
|
+
|
|
183
188
|
# Collect licenses
|
|
184
189
|
licenses = []
|
|
185
190
|
REXML::XPath.each(comp, "licenses/license") do |license|
|
|
186
191
|
license_id = get_element_text(license, "id")
|
|
187
|
-
licenses << {
|
|
192
|
+
licenses << {"license" => {"id" => license_id}} if license_id
|
|
188
193
|
end
|
|
189
|
-
|
|
194
|
+
|
|
190
195
|
component["licenses"] = licenses unless licenses.empty?
|
|
191
196
|
sbom["components"] << component
|
|
192
197
|
end
|
|
193
198
|
|
|
194
199
|
# Convert CycloneDX format to SPDX-like format for compatibility with Reporter
|
|
195
|
-
|
|
200
|
+
{
|
|
196
201
|
"packages" => sbom["components"].map do |comp|
|
|
197
202
|
license_string = if comp["licenses"]
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
203
|
+
comp["licenses"].map { |l| l["license"]["id"] }.join(", ")
|
|
204
|
+
else
|
|
205
|
+
"NOASSERTION"
|
|
206
|
+
end
|
|
202
207
|
{
|
|
203
208
|
"name" => comp["name"],
|
|
204
209
|
"versionInfo" => comp["version"],
|
|
@@ -206,18 +211,16 @@ module Bundler
|
|
|
206
211
|
}
|
|
207
212
|
end
|
|
208
213
|
}
|
|
209
|
-
|
|
210
|
-
converted_sbom
|
|
211
214
|
end
|
|
212
215
|
|
|
213
216
|
def self.to_report_format(sbom)
|
|
214
217
|
{
|
|
215
218
|
"packages" => sbom["components"].map do |comp|
|
|
216
219
|
license_string = if comp["licenses"]
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
220
|
+
comp["licenses"].map { |l| l["license"]["id"] }.join(", ")
|
|
221
|
+
else
|
|
222
|
+
"NOASSERTION"
|
|
223
|
+
end
|
|
221
224
|
{
|
|
222
225
|
"name" => comp["name"],
|
|
223
226
|
"versionInfo" => comp["version"],
|
|
@@ -228,17 +231,17 @@ module Bundler
|
|
|
228
231
|
end
|
|
229
232
|
|
|
230
233
|
private
|
|
231
|
-
|
|
234
|
+
|
|
232
235
|
def self.add_element(parent, name, value)
|
|
233
236
|
element = REXML::Element.new(name)
|
|
234
237
|
element.text = value
|
|
235
238
|
parent.add_element(element)
|
|
236
239
|
end
|
|
237
|
-
|
|
240
|
+
|
|
238
241
|
def self.get_element_text(element, xpath)
|
|
239
242
|
result = REXML::XPath.first(element, xpath)
|
|
240
243
|
result ? result.text : nil
|
|
241
244
|
end
|
|
242
245
|
end
|
|
243
246
|
end
|
|
244
|
-
end
|
|
247
|
+
end
|
|
@@ -39,7 +39,7 @@ module Bundler
|
|
|
39
39
|
def self.parse_xml(xml_content)
|
|
40
40
|
doc = REXML::Document.new(xml_content)
|
|
41
41
|
root = doc.root
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
# Determine if it's CycloneDX or SPDX
|
|
44
44
|
if root.name == "bom" && root.namespace.include?("cyclonedx.org")
|
|
45
45
|
CycloneDX.parse_xml(doc)
|
|
@@ -49,4 +49,4 @@ module Bundler
|
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
|
-
end
|
|
52
|
+
end
|
|
@@ -8,7 +8,7 @@ module Bundler
|
|
|
8
8
|
else
|
|
9
9
|
SPDX.to_report_format(sbom)
|
|
10
10
|
end
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
display_report(sbom)
|
|
13
13
|
end
|
|
14
14
|
|
|
@@ -16,7 +16,7 @@ module Bundler
|
|
|
16
16
|
|
|
17
17
|
def self.sbom_format(sbom)
|
|
18
18
|
return :cyclonedx if sbom["bomFormat"] == "CycloneDX"
|
|
19
|
-
|
|
19
|
+
:spdx
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def self.display_report(sbom)
|
|
@@ -60,4 +60,4 @@ module Bundler
|
|
|
60
60
|
end
|
|
61
61
|
end
|
|
62
62
|
end
|
|
63
|
-
end
|
|
63
|
+
end
|
data/lib/bundler/sbom/spdx.rb
CHANGED
|
@@ -6,7 +6,7 @@ module Bundler
|
|
|
6
6
|
module Sbom
|
|
7
7
|
class SPDX
|
|
8
8
|
def self.generate(lockfile, document_name)
|
|
9
|
-
spdx_id =
|
|
9
|
+
spdx_id = generate_spdx_id
|
|
10
10
|
sbom = {
|
|
11
11
|
"SPDXID" => "SPDXRef-DOCUMENT",
|
|
12
12
|
"spdxVersion" => "SPDX-2.3",
|
|
@@ -21,7 +21,12 @@ module Bundler
|
|
|
21
21
|
"packages" => []
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
# Deduplicate specs by name and version
|
|
25
|
+
seen_gems = Set.new
|
|
24
26
|
lockfile.specs.each do |spec|
|
|
27
|
+
gem_key = "#{spec.name}:#{spec.version}"
|
|
28
|
+
next if seen_gems.include?(gem_key)
|
|
29
|
+
seen_gems.add(gem_key)
|
|
25
30
|
begin
|
|
26
31
|
gemspec = Gem::Specification.find_by_name(spec.name, spec.version)
|
|
27
32
|
licenses = []
|
|
@@ -67,39 +72,39 @@ module Bundler
|
|
|
67
72
|
def self.to_xml(sbom)
|
|
68
73
|
doc = REXML::Document.new
|
|
69
74
|
doc << REXML::XMLDecl.new("1.0", "UTF-8")
|
|
70
|
-
|
|
75
|
+
|
|
71
76
|
# Root element
|
|
72
77
|
root = REXML::Element.new("SpdxDocument")
|
|
73
78
|
root.add_namespace("https://spdx.org/spdxdocs/")
|
|
74
79
|
doc.add_element(root)
|
|
75
|
-
|
|
80
|
+
|
|
76
81
|
# Document info
|
|
77
82
|
add_element(root, "SPDXID", sbom["SPDXID"])
|
|
78
83
|
add_element(root, "spdxVersion", sbom["spdxVersion"])
|
|
79
84
|
add_element(root, "name", sbom["name"])
|
|
80
85
|
add_element(root, "dataLicense", sbom["dataLicense"])
|
|
81
86
|
add_element(root, "documentNamespace", sbom["documentNamespace"])
|
|
82
|
-
|
|
87
|
+
|
|
83
88
|
# Creation info
|
|
84
89
|
creation_info = REXML::Element.new("creationInfo")
|
|
85
90
|
root.add_element(creation_info)
|
|
86
91
|
add_element(creation_info, "created", sbom["creationInfo"]["created"])
|
|
87
92
|
add_element(creation_info, "licenseListVersion", sbom["creationInfo"]["licenseListVersion"])
|
|
88
|
-
|
|
93
|
+
|
|
89
94
|
sbom["creationInfo"]["creators"].each do |creator|
|
|
90
95
|
add_element(creation_info, "creator", creator)
|
|
91
96
|
end
|
|
92
|
-
|
|
97
|
+
|
|
93
98
|
# Describes
|
|
94
99
|
sbom["documentDescribes"].each do |describes|
|
|
95
100
|
add_element(root, "documentDescribes", describes)
|
|
96
101
|
end
|
|
97
|
-
|
|
102
|
+
|
|
98
103
|
# Packages
|
|
99
104
|
sbom["packages"].each do |pkg|
|
|
100
105
|
package = REXML::Element.new("package")
|
|
101
106
|
root.add_element(package)
|
|
102
|
-
|
|
107
|
+
|
|
103
108
|
add_element(package, "SPDXID", pkg["SPDXID"])
|
|
104
109
|
add_element(package, "name", pkg["name"])
|
|
105
110
|
add_element(package, "versionInfo", pkg["versionInfo"])
|
|
@@ -109,20 +114,20 @@ module Bundler
|
|
|
109
114
|
add_element(package, "licenseDeclared", pkg["licenseDeclared"])
|
|
110
115
|
add_element(package, "copyrightText", pkg["copyrightText"])
|
|
111
116
|
add_element(package, "supplier", pkg["supplier"])
|
|
112
|
-
|
|
117
|
+
|
|
113
118
|
# External references
|
|
114
119
|
if pkg["externalRefs"]
|
|
115
120
|
pkg["externalRefs"].each do |ref|
|
|
116
121
|
ext_ref = REXML::Element.new("externalRef")
|
|
117
122
|
package.add_element(ext_ref)
|
|
118
|
-
|
|
123
|
+
|
|
119
124
|
add_element(ext_ref, "referenceCategory", ref["referenceCategory"])
|
|
120
125
|
add_element(ext_ref, "referenceType", ref["referenceType"])
|
|
121
126
|
add_element(ext_ref, "referenceLocator", ref["referenceLocator"])
|
|
122
127
|
end
|
|
123
128
|
end
|
|
124
129
|
end
|
|
125
|
-
|
|
130
|
+
|
|
126
131
|
formatter = REXML::Formatters::Pretty.new(2)
|
|
127
132
|
formatter.compact = true
|
|
128
133
|
output = ""
|
|
@@ -132,7 +137,7 @@ module Bundler
|
|
|
132
137
|
|
|
133
138
|
def self.parse_xml(doc)
|
|
134
139
|
root = doc.root
|
|
135
|
-
|
|
140
|
+
|
|
136
141
|
sbom = {
|
|
137
142
|
"SPDXID" => get_element_text(root, "SPDXID"),
|
|
138
143
|
"spdxVersion" => get_element_text(root, "spdxVersion"),
|
|
@@ -147,17 +152,17 @@ module Bundler
|
|
|
147
152
|
"packages" => [],
|
|
148
153
|
"documentDescribes" => []
|
|
149
154
|
}
|
|
150
|
-
|
|
155
|
+
|
|
151
156
|
# Collect creators
|
|
152
157
|
REXML::XPath.each(root, "creationInfo/creator") do |creator|
|
|
153
158
|
sbom["creationInfo"]["creators"] << creator.text
|
|
154
159
|
end
|
|
155
|
-
|
|
160
|
+
|
|
156
161
|
# Collect documentDescribes
|
|
157
162
|
REXML::XPath.each(root, "documentDescribes") do |describes|
|
|
158
163
|
sbom["documentDescribes"] << describes.text
|
|
159
164
|
end
|
|
160
|
-
|
|
165
|
+
|
|
161
166
|
# Collect packages
|
|
162
167
|
REXML::XPath.each(root, "package") do |pkg_element|
|
|
163
168
|
package = {
|
|
@@ -172,7 +177,7 @@ module Bundler
|
|
|
172
177
|
"supplier" => get_element_text(pkg_element, "supplier"),
|
|
173
178
|
"externalRefs" => []
|
|
174
179
|
}
|
|
175
|
-
|
|
180
|
+
|
|
176
181
|
# Collect external references
|
|
177
182
|
REXML::XPath.each(pkg_element, "externalRef") do |ref_element|
|
|
178
183
|
ref = {
|
|
@@ -182,10 +187,10 @@ module Bundler
|
|
|
182
187
|
}
|
|
183
188
|
package["externalRefs"] << ref
|
|
184
189
|
end
|
|
185
|
-
|
|
190
|
+
|
|
186
191
|
sbom["packages"] << package
|
|
187
192
|
end
|
|
188
|
-
|
|
193
|
+
|
|
189
194
|
sbom
|
|
190
195
|
end
|
|
191
196
|
|
|
@@ -203,18 +208,20 @@ module Bundler
|
|
|
203
208
|
}
|
|
204
209
|
end
|
|
205
210
|
|
|
206
|
-
|
|
207
|
-
|
|
211
|
+
def self.generate_spdx_id
|
|
212
|
+
SecureRandom.uuid
|
|
213
|
+
end
|
|
214
|
+
|
|
208
215
|
def self.add_element(parent, name, value)
|
|
209
216
|
element = REXML::Element.new(name)
|
|
210
217
|
element.text = value
|
|
211
218
|
parent.add_element(element)
|
|
212
219
|
end
|
|
213
|
-
|
|
220
|
+
|
|
214
221
|
def self.get_element_text(element, xpath)
|
|
215
222
|
result = REXML::XPath.first(element, xpath)
|
|
216
223
|
result ? result.text : nil
|
|
217
224
|
end
|
|
218
225
|
end
|
|
219
226
|
end
|
|
220
|
-
end
|
|
227
|
+
end
|
data/lib/bundler/sbom/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bundler-sbom
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- SHIBATA Hiroshi
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: bundler
|
|
@@ -78,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
78
78
|
- !ruby/object:Gem::Version
|
|
79
79
|
version: '0'
|
|
80
80
|
requirements: []
|
|
81
|
-
rubygems_version:
|
|
81
|
+
rubygems_version: 4.0.3
|
|
82
82
|
specification_version: 4
|
|
83
83
|
summary: Generate SPDX SBOM(Software Bill of Materials) files with Bundler
|
|
84
84
|
test_files: []
|