Skip to content

Commit ab6dd4d

Browse files
Merge remote-tracking branch 'origin/master'
# Conflicts: # cyclonedx-ruby.gemspec # lib/bom_builder.rb # lib/bom_helpers.rb
2 parents 01c35c1 + be09820 commit ab6dd4d

8 files changed

Lines changed: 107 additions & 139 deletions

File tree

.github/workflows/ruby.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: Ruby CI
22

3-
on: [push]
3+
on: [push, pull_request]
44

55
jobs:
66
build:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.idea

Gemfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
source 'https://rubygems.org'
22

33
# Specify your gem's dependencies in cyclonedx-ruby.gemspec
4-
gemspec
4+
gemspec

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
[![Gem Version](https://badge.fury.io/rb/cyclonedx-ruby.svg)](https://badge.fury.io/rb/cyclonedx-ruby)
33
[![License](https://img.shields.io/badge/license-Apache%202.0-brightgreen.svg)][License]
44
[![Website](https://img.shields.io/badge/https://-cyclonedx.org-blue.svg)](https://cyclonedx.org/)
5+
[![Slack Invite](https://img.shields.io/badge/Slack-Join-blue?logo=slack&labelColor=393939)](https://cyclonedx.org/slack/invite)
56
[![Group Discussion](https://img.shields.io/badge/discussion-groups.io-blue.svg)](https://groups.io/g/CycloneDX)
67
[![Twitter](https://img.shields.io/twitter/url/http/shields.io.svg?style=social&label=Follow)](https://twitter.com/CycloneDX_Spec)
78

bin/cyclonedx-ruby

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
23

34
require 'bom_builder'
4-
Bombuilder.build(ARGV[0])
5+
Bombuilder.build(ARGV[0])

cyclonedx-ruby.gemspec

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
1+
# frozen_string_literal: true
2+
13
Gem::Specification.new do |spec|
2-
spec.name = "cyclonedx-ruby"
3-
spec.version = "1.1.0"
4-
spec.date = "2019-07-12"
5-
spec.summary = "CycloneDX Software Bill of Material (SBOM) generation utility"
6-
spec.description = "CycloneDX is a lightweight software bill-of-material (SBOM) specification designed for use in application security contexts and supply chain component analysis. This Gem generates CycloneDX BOMs from Ruby projects."
7-
spec.authors = ["Joseph Kobti", "Steve Springett"]
8-
spec.email = "josephkobti@outlook.com"
9-
spec.files = ["lib/bom_builder.rb", "lib/bom_helpers.rb", "lib/licenses.json"]
10-
spec.homepage = "https://github.com/CycloneDX/cyclonedx-ruby-gem"
11-
spec.license = "Apache-2.0"
12-
spec.executables << "cyclonedx-ruby"
4+
spec.name = 'cyclonedx-ruby'
5+
spec.version = '1.1.0'
6+
spec.date = '2019-07-12'
7+
spec.summary = 'CycloneDX software bill-of-material (SBoM) generation utility'
8+
spec.description = 'CycloneDX is a lightweight software bill-of-material (SBOM) specification designed for use in application security contexts and supply chain component analysis. This Gem generates CycloneDX BOMs from Ruby projects.'
9+
spec.authors = ['Joseph Kobti', 'Steve Springett']
10+
spec.email = 'josephkobti@outlook.com'
11+
spec.files = ['lib/bom_builder.rb', 'lib/bom_helpers.rb', 'lib/licenses.json']
12+
spec.homepage = 'https://github.com/CycloneDX/cyclonedx-ruby-gem'
13+
spec.license = 'Apache-2.0'
14+
spec.executables << 'cyclonedx-ruby'
1315
spec.add_dependency('json', '~> 2.2')
1416
spec.add_dependency('nokogiri', '~> 1.8')
1517
spec.add_dependency('ostruct', '~> 0.1')
1618
spec.add_dependency('rest-client', '~> 2.0')
1719
spec.add_development_dependency 'rake', '~> 12'
1820
spec.add_development_dependency 'rspec', '~> 3.7'
19-
end
21+
end

lib/bom_builder.rb

Lines changed: 50 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,15 @@
1-
# This file is part of CycloneDX Ruby Gem.
2-
#
3-
# Licensed to the Apache Software Foundation (ASF) under one
4-
# or more contributor license agreements. See the NOTICE file
5-
# distributed with this work for additional information
6-
# regarding copyright ownership. The ASF licenses this file
7-
# to you under the Apache License, Version 2.0 (the
8-
# "License"); you may not use this file except in compliance
9-
# with the License. You may obtain a copy of the License at
10-
#
11-
# http://www.apache.org/licenses/LICENSE-2.0
12-
#
13-
# Unless required by applicable law or agreed to in writing,
14-
# software distributed under the License is distributed on an
15-
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16-
# KIND, either express or implied. See the License for the
17-
# specific language governing permissions and limitations
18-
# under the License.
19-
#
20-
# SPDX-License-Identifier: Apache-2.0
21-
# Copyright (c) OWASP Foundation. All Rights Reserved.
22-
require "bundler"
23-
require "fileutils"
24-
require "json"
25-
require "logger"
26-
require "nokogiri"
27-
require "optparse"
28-
require "ostruct"
29-
require "rest_client"
1+
# frozen_string_literal: true
2+
3+
require 'bundler'
4+
require 'fileutils'
5+
require 'json'
6+
require 'logger'
7+
require 'nokogiri'
8+
require 'optparse'
9+
require 'ostruct'
10+
require 'rest_client'
3011
require 'securerandom'
31-
require_relative "bom_helpers"
12+
require_relative 'bom_helpers'
3213

3314
class Bombuilder
3415
def self.build(path)
@@ -40,29 +21,29 @@ def self.build(path)
4021
begin
4122
@logger.info("Changing directory to the original working directory located at #{original_working_directory}")
4223
Dir.chdir original_working_directory
43-
rescue => e
24+
rescue StandardError => e
4425
@logger.error("Unable to change directory the original working directory located at #{original_working_directory}. #{e.message}: #{e.backtrace.join('\n')}")
4526
abort
4627
end
4728

4829
bom_directory = File.dirname(@bom_file_path)
4930
begin
5031
FileUtils.mkdir_p(bom_directory) unless File.directory?(bom_directory)
51-
rescue => e
32+
rescue StandardError => e
5233
@logger.error("Unable to create the directory to hold the BOM output at #{@bom_directory}. #{e.message}: #{e.backtrace.join('\n')}")
5334
abort
5435
end
5536

5637
begin
5738
@logger.info("Writing BOM to #{@bom_file_path}...")
58-
File.open(@bom_file_path, "w") {|file| file.write(bom)}
39+
File.open(@bom_file_path, 'w') { |file| file.write(bom) }
5940

6041
if @options[:verbose]
6142
@logger.info("#{@gems.size} gems were written to BOM located at #{@bom_file_path}")
6243
else
6344
puts "#{@gems.size} gems were written to BOM located at #{@bom_file_path}"
6445
end
65-
rescue => e
46+
rescue StandardError => e
6647
@logger.error("Unable to write BOM to #{@bom_file_path}. #{e.message}: #{e.backtrace.join('\n')}")
6748
abort
6849
end
@@ -71,96 +52,96 @@ def self.build(path)
7152
def self.setup(path)
7253
@options = {}
7354
OptionParser.new do |opts|
74-
opts.banner = "Usage: cyclonedx-ruby [options]"
75-
76-
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
55+
opts.banner = 'Usage: cyclonedx-ruby [options]'
56+
57+
opts.on('-v', '--[no-]verbose', 'Run verbosely') do |v|
7758
@options[:verbose] = v
7859
end
79-
opts.on("-p", "--path path", "(Required) Path to Ruby project directory") do |path|
60+
opts.on('-p', '--path path', '(Required) Path to Ruby project directory') do |path|
8061
@options[:path] = path
8162
end
82-
opts.on("-o", "--output bom_file_path", "(Optional) Path to output the bom.xml file to") do |bom_file_path|
63+
opts.on('-o', '--output bom_file_path', '(Optional) Path to output the bom.xml file to') do |bom_file_path|
8364
@options[:bom_file_path] = bom_file_path
8465
end
85-
opts.on_tail("-h", "--help", "Show help message") do
66+
opts.on_tail('-h', '--help', 'Show help message') do
8667
puts opts
8768
exit
8869
end
8970
end.parse!
9071

91-
@logger = Logger.new(STDOUT)
92-
if @options[:verbose]
93-
@logger.level = Logger::INFO
94-
else
95-
@logger.level = Logger::ERROR
96-
end
72+
@logger = Logger.new($stdout)
73+
@logger.level = if @options[:verbose]
74+
Logger::INFO
75+
else
76+
Logger::ERROR
77+
end
9778

9879
@gems = []
9980
licenses_file = File.read "#{__dir__}/licenses.json"
10081
@licenses_list = JSON.parse(licenses_file)
10182

10283
if @options[:path].nil?
103-
@logger.error("missing path to project directory")
84+
@logger.error('missing path to project directory')
10485
abort
10586
end
10687

107-
if !File.directory?(@options[:path])
88+
unless File.directory?(@options[:path])
10889
@logger.error("path provided is not a valid directory. path provided was: #{@options[:path]}")
10990
abort
11091
end
11192

11293
begin
11394
@logger.info("Changing directory to Ruby project directory located at #{@options[:path]}")
11495
Dir.chdir @options[:path]
115-
rescue => e
96+
rescue StandardError => e
11697
@logger.error("Unable to change directory to Ruby project directory located at #{@options[:path]}. #{e.message}: #{e.backtrace.join('\n')}")
11798
abort
11899
end
119100

120-
if @options[:bom_file_path].nil?
121-
@bom_file_path = "./bom.xml"
122-
else
123-
@bom_file_path = @options[:bom_file_path]
124-
end
101+
@bom_file_path = if @options[:bom_file_path].nil?
102+
'./bom.xml'
103+
else
104+
@options[:bom_file_path]
105+
end
125106

126107
@logger.info("BOM will be written to #{@bom_file_path}")
127108

128109
begin
129-
gemfile_path = @options[:path] + "/" + "Gemfile.lock"
110+
gemfile_path = "#{@options[:path]}/Gemfile.lock"
130111
@logger.info("Parsing specs from #{gemfile_path}...")
131112
gemfile_contents = File.read(gemfile_path)
132113
@specs = Bundler::LockfileParser.new(gemfile_contents).specs
133-
@logger.info("Specs successfully parsed!")
134-
rescue => e
114+
@logger.info('Specs successfully parsed!')
115+
rescue StandardError => e
135116
@logger.error("Unable to parse specs from #{gemfile_path}. #{e.message}: #{e.backtrace.join('\n')}")
136117
abort
137118
end
138119
end
139-
120+
140121
def self.specs_list
141122
count = 0
142123
@specs.each do |dependency|
143124
object = OpenStruct.new
144-
object.name = dependency.name
145-
object.version = dependency.version
125+
object.name = dependency.name
126+
object.version = dependency.version
146127
object.purl = purl(object.name, object.version)
147128
gem = get_gem(object.name, object.version)
148129
next if gem.nil?
149-
150-
if gem["licenses"] and gem["licenses"].length > 0
151-
if @licenses_list.include? gem["licenses"].first
152-
object.license_id = gem["licenses"].first
130+
131+
if gem['licenses']&.length&.positive?
132+
if @licenses_list.include? gem['licenses'].first
133+
object.license_id = gem['licenses'].first
153134
else
154-
object.license_name = gem["licenses"].first
135+
object.license_name = gem['licenses'].first
155136
end
156137
end
157138

158-
object.author = gem["authors"]
159-
object.description = gem["summary"]
160-
object.hash = gem["sha"]
139+
object.author = gem['authors']
140+
object.description = gem['summary']
141+
object.hash = gem['sha']
161142
@gems.push(object)
162143
count += 1
163144
@logger.info("#{object.name}:#{object.version} gem added")
164145
end
165-
end
146+
end
166147
end

lib/bom_helpers.rb

Lines changed: 37 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,57 @@
1-
# This file is part of CycloneDX Ruby Gem.
2-
#
3-
# Licensed to the Apache Software Foundation (ASF) under one
4-
# or more contributor license agreements. See the NOTICE file
5-
# distributed with this work for additional information
6-
# regarding copyright ownership. The ASF licenses this file
7-
# to you under the Apache License, Version 2.0 (the
8-
# "License"); you may not use this file except in compliance
9-
# with the License. You may obtain a copy of the License at
10-
#
11-
# http://www.apache.org/licenses/LICENSE-2.0
12-
#
13-
# Unless required by applicable law or agreed to in writing,
14-
# software distributed under the License is distributed on an
15-
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16-
# KIND, either express or implied. See the License for the
17-
# specific language governing permissions and limitations
18-
# under the License.
19-
#
20-
# SPDX-License-Identifier: Apache-2.0
21-
# Copyright (c) OWASP Foundation. All Rights Reserved.
1+
# frozen_string_literal: true
2+
223
def purl(name, version)
23-
purl = "pkg:gem/" + name + "@" + version.to_s
4+
"pkg:gem/#{name}@#{version}"
245
end
256

26-
def random_urn_uuid()
27-
random_urn_uuid = "urn:uuid:" + SecureRandom.uuid
7+
def random_urn_uuid
8+
"urn:uuid:#{SecureRandom.uuid}"
289
end
2910

3011
def build_bom(gems)
31-
builder = Nokogiri::XML::Builder.new(:encoding => "UTF-8") do |xml|
32-
attributes = {"xmlns" => "http://cyclonedx.org/schema/bom/1.1", "version" => "1", "serialNumber" => random_urn_uuid}
12+
builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
13+
attributes = { 'xmlns' => 'http://cyclonedx.org/schema/bom/1.1', 'version' => '1', 'serialNumber' => random_urn_uuid }
3314
xml.bom(attributes) do
34-
xml.components {
15+
xml.components do
3516
gems.each do |gem|
36-
xml.component("type" => "library") {
37-
xml.name gem["name"]
38-
xml.version gem["version"]
39-
xml.description gem["description"]
40-
xml.hashes{
41-
xml.hash_ gem["hash"], :alg => "SHA-256"
42-
}
43-
if gem["license_id"]
44-
xml.licenses {
45-
xml.license{
46-
xml.id gem["license_id"]
47-
}
48-
}
49-
elsif gem["license_name"]
50-
xml.licenses {
51-
xml.license{
52-
xml.name gem["license_name"]
53-
}
54-
}
17+
xml.component('type' => 'library') do
18+
xml.name gem['name']
19+
xml.version gem['version']
20+
xml.description gem['description']
21+
xml.hashes do
22+
xml.hash_ gem['hash'], alg: 'SHA-256'
23+
end
24+
if gem['license_id']
25+
xml.licenses do
26+
xml.license do
27+
xml.id gem['license_id']
28+
end
29+
end
30+
elsif gem['license_name']
31+
xml.licenses do
32+
xml.license do
33+
xml.name gem['license_name']
34+
end
35+
end
5536
end
56-
xml.purl gem["purl"]
57-
}
37+
xml.purl gem['purl']
38+
end
5839
end
59-
}
40+
end
6041
end
61-
end
42+
end
6243
builder.to_xml
63-
end
44+
end
6445

6546
def get_gem(name, version)
6647
url = "https://rubygems.org/api/v1/versions/#{name}.json"
6748
begin
49+
RestClient.proxy = ENV['http_proxy']
6850
response = RestClient.get(url)
6951
body = JSON.parse(response.body)
70-
body.select {|item| item["number"] == version.to_s}.first
71-
rescue
52+
body.select { |item| item['number'] == version.to_s }.first
53+
rescue StandardError
7254
@logger.warn("#{name} couldn't be fetched")
73-
return nil
55+
nil
7456
end
75-
end
57+
end

0 commit comments

Comments
 (0)