From 11c444dff815ac9df1e9df71f319b4b3a5118430 Mon Sep 17 00:00:00 2001 From: Chris Sansone Date: Wed, 31 Jul 2019 17:15:49 -0400 Subject: [PATCH 1/3] Remove ostruct as explicit dependency as it is part of the Ruby stdlib --- cyclonedx-ruby.gemspec | 1 - 1 file changed, 1 deletion(-) diff --git a/cyclonedx-ruby.gemspec b/cyclonedx-ruby.gemspec index d781f84..19b0c0e 100644 --- a/cyclonedx-ruby.gemspec +++ b/cyclonedx-ruby.gemspec @@ -12,7 +12,6 @@ Gem::Specification.new do |spec| spec.executables << "cyclonedx-ruby" spec.add_dependency('json', '~> 2.2') spec.add_dependency('nokogiri', '~> 1.8') - spec.add_dependency('ostruct', '~> 0.1') spec.add_dependency('rest-client', '~> 2.0') spec.add_development_dependency 'rake', '~> 12' spec.add_development_dependency 'rspec', '~> 3.7' From 909e85ff2cf784f1ba8c6d669f53d0844352d7c8 Mon Sep 17 00:00:00 2001 From: Chris Sansone Date: Wed, 31 Jul 2019 17:21:09 -0400 Subject: [PATCH 2/3] Updates in order to be compatible with versions of Ruby < 2.3.0 --- cyclonedx-ruby.gemspec | 3 +- lib/bom_builder.rb | 2 +- lib/bom_helpers.rb | 74 ++++++++++++++++++++++++------------------ 3 files changed, 45 insertions(+), 34 deletions(-) diff --git a/cyclonedx-ruby.gemspec b/cyclonedx-ruby.gemspec index 19b0c0e..6338768 100644 --- a/cyclonedx-ruby.gemspec +++ b/cyclonedx-ruby.gemspec @@ -11,7 +11,8 @@ Gem::Specification.new do |spec| spec.license = "Apache-2.0" spec.executables << "cyclonedx-ruby" spec.add_dependency('json', '~> 2.2') - spec.add_dependency('nokogiri', '~> 1.8') + spec.add_dependency('rexml', '~> 3.2') + spec.add_dependency('bundler', '~> 1.17') spec.add_dependency('rest-client', '~> 2.0') spec.add_development_dependency 'rake', '~> 12' spec.add_development_dependency 'rspec', '~> 3.7' diff --git a/lib/bom_builder.rb b/lib/bom_builder.rb index 47f8f12..b172924 100644 --- a/lib/bom_builder.rb +++ b/lib/bom_builder.rb @@ -2,10 +2,10 @@ require "fileutils" require "json" require "logger" -require "nokogiri" require "optparse" require "ostruct" require "rest_client" +require "rexml/document" require 'securerandom' require_relative "bom_helpers" diff --git a/lib/bom_helpers.rb b/lib/bom_helpers.rb index 0386696..2015649 100644 --- a/lib/bom_helpers.rb +++ b/lib/bom_helpers.rb @@ -7,39 +7,49 @@ def random_urn_uuid() end def build_bom(gems) - builder = Nokogiri::XML::Builder.new(:encoding => "UTF-8") do |xml| - attributes = {"xmlns" => "http://cyclonedx.org/schema/bom/1.1", "version" => "1", "serialNumber" => random_urn_uuid} - xml.bom(attributes) do - xml.components { - gems.each do |gem| - xml.component("type" => "library") { - xml.name gem["name"] - xml.version gem["version"] - xml.description gem["description"] - xml.hashes{ - xml.hash_ gem["hash"], :alg => "SHA-256" - } - if gem["license_id"] - xml.licenses { - xml.license{ - xml.id gem["license_id"] - } - } - elsif gem["license_name"] - xml.licenses { - xml.license{ - xml.name gem["license_name"] - } - } - end - xml.purl gem["purl"] - } - end - } + + xml_doc = REXML::Document.new('') + bom_xml_element = xml_doc.add_element "bom", {"xmlns" => "http://cyclonedx.org/schema/bom/1.0", "version"=>"1", "serialNumber" => random_urn_uuid} + components_xml_element = bom_xml_element.add_element "components" + + gems.each do |gem| + component_xml_element = components_xml_element.add_element "component", {"type" => "library"} + + name_xml_element = component_xml_element.add_element "name" + name_xml_element.text = gem["name"] + + version_xml_element = component_xml_element.add_element "version" + version_xml_element.text = gem["version"] + + description_xml_element = component_xml_element.add_element "description" + description_xml_element.text = gem["description"] + + hashes_xml_element = component_xml_element.add_element "hashes" + hash_xml_element = hashes_xml_element.add_element "hash", {"alg" => "SHA-256"} + hash_xml_element.text = gem["hash"] + + if gem["license_id"] || gem["license_name"] + licenses_xml_element = component_xml_element.add_element "licenses" + + license_xml_element = licenses_xml_element.add_element "license" + + if gem["license_id"] + license_id_xml_element = license_xml_element.add_element "id" + license_id_xml_element.text = gem["license_id"] + elsif gem["license_name"] + license_name_xml_element = license_xml_element.add_element "id" + license_name_xml_element.text = gem["license_name"] + end end - end - builder.to_xml -end + + purl_xml_element = component_xml_element.add_element "purl" + purl_xml_element.text = gem["purl"] + end + + output="" + xml_doc.write(output=output) + output +end def get_gem(name, version) url = "https://rubygems.org/api/v1/versions/#{name}.json" From e8e5c9633a0d644d9d71ba8adeb4226e53a1a16d Mon Sep 17 00:00:00 2001 From: Chris Sansone Date: Thu, 1 Aug 2019 16:40:22 -0400 Subject: [PATCH 3/3] Add some retry logic to gem fetching --- lib/bom_helpers.rb | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/bom_helpers.rb b/lib/bom_helpers.rb index 2015649..af5ebb4 100644 --- a/lib/bom_helpers.rb +++ b/lib/bom_helpers.rb @@ -53,12 +53,24 @@ def build_bom(gems) def get_gem(name, version) url = "https://rubygems.org/api/v1/versions/#{name}.json" + max_retries = 3 + retry_count = 0 + delay = 1 + begin response = RestClient.get(url) body = JSON.parse(response.body) - body.select {|item| item["number"] == version.to_s}.first - rescue - @logger.warn("#{name} couldn't be fetched") - return nil + gem_details = body.select {|item| item["number"] == version.to_s}.first + return gem_details if gem_details + rescue => e + if max_retries > retry_count + @logger.warn("gem #{name} could NOT be fetched. Error was: #{e.message}. Retrying #{max_retries - retry_count} more time(s)...") + sleep delay + retry_count + retry_count += 1 + retry + else + @logger.warn("gem #{name} could NOT be fetched. Error was: #{e.message}. Gave up after retrying #{max_retries} times!") + return nil + end end -end \ No newline at end of file +end \ No newline at end of file