bundler-sbom 0.2.0 → 0.3.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/README.md +3 -0
- data/lib/bundler/sbom/cli.rb +13 -1
- data/lib/bundler/sbom/cyclonedx.rb +2 -2
- data/lib/bundler/sbom/generator.rb +44 -3
- data/lib/bundler/sbom/spdx.rb +2 -2
- data/lib/bundler/sbom/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c2d31deef79ab54416961ff900632cf386f95207f35afd2c2e491d97c53d4c0e
|
|
4
|
+
data.tar.gz: 6060186d5f3394f12f9c58a2ad1aa4a01b554500f58ef606403536675e506f18
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bb372a7bf2da186dcdf3a3406f236682b35b3aba690a21f3617a32cd94cebf85a4902e39a755a0e1698ff463a9f37fa8c79a425230ab6d1b68c16d1790dbb476
|
|
7
|
+
data.tar.gz: aab0fffe6b7c684d8bb07c33040f2f5897c00d9353e5809b665333a714ee6175a36a2eae4ac01d8427c5b273e3911e91fadc8580f486d4dc04db0ec9f6ec337d
|
data/README.md
CHANGED
|
@@ -23,6 +23,7 @@ $ bundle sbom dump [options]
|
|
|
23
23
|
Available options:
|
|
24
24
|
- `-f, --format FORMAT`: Output format (json or xml, default: json)
|
|
25
25
|
- `-s, --sbom FORMAT`: SBOM specification format (spdx or cyclonedx, default: spdx)
|
|
26
|
+
- `--without GROUPS`: Exclude groups (comma or colon separated, e.g., 'development:test' or 'development,test')
|
|
26
27
|
|
|
27
28
|
Generated files will be named according to the following pattern:
|
|
28
29
|
- SPDX format: `bom.json` or `bom.xml`
|
|
@@ -34,6 +35,8 @@ $ bundle sbom dump # Generates SPDX format in JSON (bo
|
|
|
34
35
|
$ bundle sbom dump -f xml # Generates SPDX format in XML (bom.xml)
|
|
35
36
|
$ bundle sbom dump -s cyclonedx # Generates CycloneDX format in JSON (bom-cyclonedx.json)
|
|
36
37
|
$ bundle sbom dump -s cyclonedx -f xml # Generates CycloneDX format in XML (bom-cyclonedx.xml)
|
|
38
|
+
$ bundle sbom dump --without development # Excludes development group
|
|
39
|
+
$ bundle sbom dump --without development:test # Excludes development and test groups
|
|
37
40
|
```
|
|
38
41
|
|
|
39
42
|
### Analyze License Information
|
data/lib/bundler/sbom/cli.rb
CHANGED
|
@@ -8,9 +8,11 @@ module Bundler
|
|
|
8
8
|
desc "dump", "Generate SBOM and save to file"
|
|
9
9
|
method_option :format, type: :string, default: "json", desc: "Output format: json or xml", aliases: "-f"
|
|
10
10
|
method_option :sbom, type: :string, default: "spdx", desc: "SBOM format: spdx or cyclonedx", aliases: "-s"
|
|
11
|
+
method_option :without, type: :string, desc: "Exclude groups (comma or colon separated, e.g., 'development:test' or 'development,test')"
|
|
11
12
|
def dump
|
|
12
13
|
format = options[:format].downcase
|
|
13
14
|
sbom_format = options[:sbom].downcase
|
|
15
|
+
without_groups = parse_without_groups(options[:without])
|
|
14
16
|
|
|
15
17
|
# Validate output format
|
|
16
18
|
unless ["json", "xml"].include?(format)
|
|
@@ -25,7 +27,7 @@ module Bundler
|
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
# Generate SBOM based on specified format
|
|
28
|
-
sbom = Bundler::Sbom::Generator.generate_sbom(sbom_format)
|
|
30
|
+
sbom = Bundler::Sbom::Generator.generate_sbom(sbom_format, without_groups: without_groups)
|
|
29
31
|
|
|
30
32
|
# Determine file extension based on output format
|
|
31
33
|
ext = (format == "json") ? "json" : "xml"
|
|
@@ -100,6 +102,16 @@ module Bundler
|
|
|
100
102
|
def self.exit_on_failure?
|
|
101
103
|
true
|
|
102
104
|
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
def parse_without_groups(without_option)
|
|
109
|
+
return [] unless without_option
|
|
110
|
+
|
|
111
|
+
# Split by comma or colon and clean up whitespace
|
|
112
|
+
groups = without_option.split(%r{[:,]}).map(&:strip).reject(&:empty?)
|
|
113
|
+
groups.map(&:to_sym)
|
|
114
|
+
end
|
|
103
115
|
end
|
|
104
116
|
end
|
|
105
117
|
end
|
|
@@ -5,7 +5,7 @@ require "rexml/document"
|
|
|
5
5
|
module Bundler
|
|
6
6
|
module Sbom
|
|
7
7
|
class CycloneDX
|
|
8
|
-
def self.generate(
|
|
8
|
+
def self.generate(gems, document_name)
|
|
9
9
|
serial_number = SecureRandom.uuid
|
|
10
10
|
timestamp = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
11
11
|
sbom = {
|
|
@@ -33,7 +33,7 @@ module Bundler
|
|
|
33
33
|
|
|
34
34
|
# Deduplicate specs by name and version
|
|
35
35
|
seen_gems = Set.new
|
|
36
|
-
|
|
36
|
+
gems.each do |spec|
|
|
37
37
|
gem_key = "#{spec.name}:#{spec.version}"
|
|
38
38
|
next if seen_gems.include?(gem_key)
|
|
39
39
|
seen_gems.add(gem_key)
|
|
@@ -10,7 +10,7 @@ module Bundler
|
|
|
10
10
|
class GemfileLockNotFoundError < StandardError; end
|
|
11
11
|
|
|
12
12
|
class Generator
|
|
13
|
-
def self.generate_sbom(format = "spdx")
|
|
13
|
+
def self.generate_sbom(format = "spdx", without_groups: [])
|
|
14
14
|
lockfile_path = Bundler.default_lockfile
|
|
15
15
|
if !lockfile_path || !lockfile_path.exist?
|
|
16
16
|
Bundler.ui.error "No Gemfile.lock found. Run `bundle install` first."
|
|
@@ -20,11 +20,14 @@ module Bundler
|
|
|
20
20
|
lockfile = Bundler::LockfileParser.new(lockfile_path.read)
|
|
21
21
|
document_name = File.basename(Dir.pwd)
|
|
22
22
|
|
|
23
|
+
# Get gems to include based on groups
|
|
24
|
+
gems = get_gems_for_groups(lockfile, without_groups)
|
|
25
|
+
|
|
23
26
|
case format.to_s.downcase
|
|
24
27
|
when "cyclonedx"
|
|
25
|
-
CycloneDX.generate(
|
|
28
|
+
CycloneDX.generate(gems, document_name)
|
|
26
29
|
else # default to spdx
|
|
27
|
-
SPDX.generate(
|
|
30
|
+
SPDX.generate(gems, document_name)
|
|
28
31
|
end
|
|
29
32
|
end
|
|
30
33
|
|
|
@@ -47,6 +50,44 @@ module Bundler
|
|
|
47
50
|
SPDX.parse_xml(doc)
|
|
48
51
|
end
|
|
49
52
|
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def self.get_gems_for_groups(lockfile, without_groups)
|
|
57
|
+
# If no groups specified, use all specs
|
|
58
|
+
if without_groups.empty?
|
|
59
|
+
return lockfile.specs
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Try to get group information from Bundler.definition if available
|
|
63
|
+
if defined?(Bundler::Definition) && Bundler.respond_to?(:definition)
|
|
64
|
+
begin
|
|
65
|
+
definition = Bundler.definition
|
|
66
|
+
all_groups = definition.groups
|
|
67
|
+
include_groups = all_groups - without_groups
|
|
68
|
+
|
|
69
|
+
# Use specs_for to get all gems (including transitive dependencies) for included groups
|
|
70
|
+
if definition.respond_to?(:specs_for)
|
|
71
|
+
definition.specs_for(include_groups)
|
|
72
|
+
else
|
|
73
|
+
# Fallback to old method if specs_for is not available
|
|
74
|
+
included_gems = Set.new
|
|
75
|
+
include_groups.each do |group|
|
|
76
|
+
definition.dependencies_for(group).each do |dep|
|
|
77
|
+
included_gems.add(dep.name)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
lockfile.specs.select { |spec| included_gems.include?(spec.name) }
|
|
81
|
+
end
|
|
82
|
+
rescue => e
|
|
83
|
+
# Fallback to all specs if there's any issue with Bundler.definition
|
|
84
|
+
Bundler.ui.warn("Warning: Could not determine group information: #{e.message}")
|
|
85
|
+
lockfile.specs
|
|
86
|
+
end
|
|
87
|
+
else
|
|
88
|
+
lockfile.specs
|
|
89
|
+
end
|
|
90
|
+
end
|
|
50
91
|
end
|
|
51
92
|
end
|
|
52
93
|
end
|
data/lib/bundler/sbom/spdx.rb
CHANGED
|
@@ -5,7 +5,7 @@ require "rexml/document"
|
|
|
5
5
|
module Bundler
|
|
6
6
|
module Sbom
|
|
7
7
|
class SPDX
|
|
8
|
-
def self.generate(
|
|
8
|
+
def self.generate(gems, document_name)
|
|
9
9
|
spdx_id = generate_spdx_id
|
|
10
10
|
sbom = {
|
|
11
11
|
"SPDXID" => "SPDXRef-DOCUMENT",
|
|
@@ -23,7 +23,7 @@ module Bundler
|
|
|
23
23
|
|
|
24
24
|
# Deduplicate specs by name and version
|
|
25
25
|
seen_gems = Set.new
|
|
26
|
-
|
|
26
|
+
gems.each do |spec|
|
|
27
27
|
gem_key = "#{spec.name}:#{spec.version}"
|
|
28
28
|
next if seen_gems.include?(gem_key)
|
|
29
29
|
seen_gems.add(gem_key)
|
data/lib/bundler/sbom/version.rb
CHANGED