spandx 0.11.0 → 0.12.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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -2
  3. data/README.md +59 -2
  4. data/exe/spandx +3 -4
  5. data/lib/spandx.rb +13 -32
  6. data/lib/spandx/cli.rb +1 -30
  7. data/lib/spandx/cli/commands/build.rb +41 -0
  8. data/lib/spandx/cli/commands/pull.rb +21 -0
  9. data/lib/spandx/cli/commands/scan.rb +17 -2
  10. data/lib/spandx/cli/main.rb +54 -0
  11. data/lib/spandx/core/cache.rb +3 -3
  12. data/lib/spandx/core/circuit.rb +34 -0
  13. data/lib/spandx/core/dependency.rb +32 -7
  14. data/lib/spandx/core/gateway.rb +19 -0
  15. data/lib/spandx/core/{database.rb → git.rb} +7 -2
  16. data/lib/spandx/core/guess.rb +42 -4
  17. data/lib/spandx/core/http.rb +30 -5
  18. data/lib/spandx/core/license_plugin.rb +54 -0
  19. data/lib/spandx/core/null_gateway.rb +11 -0
  20. data/lib/spandx/core/parser.rb +8 -25
  21. data/lib/spandx/core/plugin.rb +15 -0
  22. data/lib/spandx/core/registerable.rb +27 -0
  23. data/lib/spandx/core/report.rb +30 -6
  24. data/lib/spandx/core/table.rb +29 -0
  25. data/lib/spandx/dotnet/index.rb +10 -5
  26. data/lib/spandx/dotnet/nuget_gateway.rb +20 -31
  27. data/lib/spandx/dotnet/parsers/csproj.rb +3 -12
  28. data/lib/spandx/dotnet/parsers/packages_config.rb +2 -10
  29. data/lib/spandx/dotnet/parsers/sln.rb +2 -2
  30. data/lib/spandx/java/gateway.rb +37 -0
  31. data/lib/spandx/java/index.rb +84 -2
  32. data/lib/spandx/java/metadata.rb +6 -3
  33. data/lib/spandx/java/parsers/maven.rb +11 -21
  34. data/lib/spandx/js/parsers/npm.rb +39 -0
  35. data/lib/spandx/js/parsers/yarn.rb +30 -0
  36. data/lib/spandx/js/yarn_lock.rb +67 -0
  37. data/lib/spandx/js/yarn_pkg.rb +59 -0
  38. data/lib/spandx/php/packagist_gateway.rb +25 -0
  39. data/lib/spandx/php/parsers/composer.rb +33 -0
  40. data/lib/spandx/python/index.rb +78 -0
  41. data/lib/spandx/python/parsers/pipfile_lock.rb +12 -16
  42. data/lib/spandx/python/pypi.rb +91 -8
  43. data/lib/spandx/python/source.rb +5 -1
  44. data/lib/spandx/{rubygems → ruby}/gateway.rb +8 -9
  45. data/lib/spandx/{rubygems → ruby}/parsers/gemfile_lock.rb +14 -16
  46. data/lib/spandx/spdx/catalogue.rb +1 -1
  47. data/lib/spandx/spdx/license.rb +12 -2
  48. data/lib/spandx/version.rb +1 -1
  49. data/spandx.gemspec +4 -1
  50. metadata +66 -10
  51. data/lib/spandx/cli/command.rb +0 -65
  52. data/lib/spandx/cli/commands/index.rb +0 -36
  53. data/lib/spandx/cli/commands/index/build.rb +0 -32
  54. data/lib/spandx/cli/commands/index/update.rb +0 -27
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da243ee84b54d710a15eeb5182f7b70084cbc69cb86beaa93c25c13dc650fe2a
4
- data.tar.gz: 904eff3844f540311828386dde54ad70054f5c4fe90e456bb0af702efc9f7cc3
3
+ metadata.gz: 1f54b3c9f0a58ce0038a5ad242c02d7aad3b11090bdecb9d84e67a9f74d5dfd6
4
+ data.tar.gz: b4ed659d450474bff291b4ee0f723f3342fe9369b690894c1731badb797a4b6b
5
5
  SHA512:
6
- metadata.gz: 67135d0cff701f454ce5d127a7f4ec7fd061d63bc832d5eb7df20bb26efa6e45bc90e28bc22eceaa2c135437858aa63337db8ff898245d870fe7acfcae14413d
7
- data.tar.gz: a4086a92383a8c334c3488004b6be02a0ce80a79b9c7386eea5948d3d52d031d46e742d9e290afa45e306c4d3d1e4bbb7c2849cdd1d2d3dc98b5aba74fd13ed8
6
+ metadata.gz: e94f49bff3619f876aa2157f15f8efde4b585720bd95417b1d4c6a48ce24208e18d836597604e4837ea37aee424124404825f8887311ab6697b2efee8474ea42
7
+ data.tar.gz: cc3553ebc60ff24667c0b182e9beb319bed075e837ef34b348b6b8c15ff16ebccfc56e4cb80c75743baa7862b07e7331e5c924ee2305e6008f5ff1f772e78a71
data/CHANGELOG.md CHANGED
@@ -1,4 +1,4 @@
1
- Version 0.11.0
1
+ Version 0.12.0
2
2
 
3
3
  # Changelog
4
4
 
@@ -9,6 +9,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [0.12.0] - 2020-04-14
13
+ ### Added
14
+ - Add `--format csv` option to scan command.
15
+ - Add `--format table` option to scan command.
16
+ - Add `--index` option to `build` command.
17
+ - Add pypi index.
18
+ - Add maven index.
19
+ - Add support for parsing `yarn.lock` files.
20
+ - Add support for parsing `package-lock.json` files.
21
+ - Add `--pull` option to fetch latest cache before scan.
22
+ - Add support for parsing `composer.lock` files.
23
+ - Add support for loading custom plugins via the `--require` option.
24
+
25
+ ### Changed
26
+ - Change the default `--format` to `table` for the scan command.
27
+
12
28
  ## [0.11.0] - 2020-03-20
13
29
  ### Added
14
30
  - Add `--format` option to scan command.
@@ -115,7 +131,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
115
131
  ### Added
116
132
  - Provide ruby API to the latest SPDX catalogue.
117
133
 
118
- [Unreleased]: https://github.com/mokhan/spandx/compare/v0.10.1...HEAD
134
+ [Unreleased]: https://github.com/mokhan/spandx/compare/v0.12.0...HEAD
135
+ [0.12.0]: https://github.com/mokhan/spandx/compare/v0.11.0...v0.12.0
136
+ [0.11.0]: https://github.com/mokhan/spandx/compare/v0.10.1...v0.11.0
119
137
  [0.10.1]: https://github.com/mokhan/spandx/compare/v0.10.0...v0.10.1
120
138
  [0.10.0]: https://github.com/mokhan/spandx/compare/v0.9.0...v0.10.0
121
139
  [0.9.0]: https://github.com/mokhan/spandx/compare/v0.8.0...v0.9.0
data/README.md CHANGED
@@ -1,8 +1,26 @@
1
- # Spandx
1
+ # Spandx ![badge](https://github.com/mokhan/spandx/workflows/ci/badge.svg)
2
2
 
3
3
  A ruby API for interacting with the https://spdx.org software license catalogue.
4
+ This gem includes a command line interface to scan a software project for the
5
+ software licenses that are associated with each dependency in the project.
6
+ `spandx` leverages an offline cache of software licenses for known dependencies.
7
+ The offline cache allows spandx to perform a truly airgap friendly scan of software
8
+ projects.
9
+
10
+ ### Supported project types
11
+
12
+ Spandx can work with following language's package managers. It utilises lock files generated by package managers to find dependencies.
13
+
14
+ | Language | Package Manager | Tested in |
15
+ | ------------ | --------------- | -------: |
16
+ | Ruby | bundler | 1.17.3 |
17
+ | Js | Npm | 6.13.4 |
18
+ | Js | Yarn | 1.19.1 |
19
+ | Python | Pypi(pipenv) | v2018.11.26 |
20
+ | C# | nuget | <> |
21
+ | Java | Maven | 3.6.3 |
22
+ | Php | Composer | 1.10.5 |
4
23
 
5
- ![badge](https://github.com/mokhan/spandx/workflows/ci/badge.svg)
6
24
 
7
25
  ## Installation
8
26
 
@@ -22,6 +40,45 @@ Or install it yourself as:
22
40
 
23
41
  ## Usage
24
42
 
43
+ ### Command line interface
44
+
45
+ The command line interface supports operations to build and fetch the latest offline index.
46
+ See the help for each subcommand for more information on how to use the command.
47
+
48
+ ```bash
49
+ モ spandx
50
+ Commands:
51
+ spandx help [COMMAND] # Describe available commands or one specific command
52
+ spandx scan LOCKFILE # Scan a lockfile and list dependencies/licenses
53
+ spandx version # spandx version
54
+ ```
55
+
56
+ To scan a specific project file use the `scan` command:
57
+
58
+ ```bash
59
+ モ spandx scan dotnet/application.sln
60
+ モ spandx scan java/pom.xml
61
+ モ spandx scan python/Pipfile.lock
62
+ モ spandx scan ruby/Gemfile.lock
63
+ ```
64
+
65
+ To activate airgap mode use the `--airgap` option:
66
+
67
+ ```bash
68
+ モ spandx scan dotnet/application.sln --airgap
69
+ モ spandx scan ruby/Gemfile.lock --airgap
70
+ ```
71
+
72
+ Airgap mode assumes that an offline cache has been placed in `$HOME/.local/share/`.
73
+
74
+ To fetch the latest offline cache:
75
+
76
+ ```bash
77
+ モ spandx index update
78
+ ```
79
+
80
+ ### Ruby API
81
+
25
82
  To fetch the latest version of the catalogue data from [SPDX](https://spdx.org/licenses/licenses.json).
26
83
 
27
84
  ```ruby
data/exe/spandx CHANGED
@@ -2,8 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  lib_path = File.expand_path('../lib', __dir__)
5
- $LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path)
6
- require 'spandx/cli'
5
+ require "#{lib_path}/spandx"
7
6
 
8
7
  Signal.trap('INT') do
9
8
  warn("\n#{caller.join("\n")}: interrupted")
@@ -11,8 +10,8 @@ Signal.trap('INT') do
11
10
  end
12
11
 
13
12
  begin
14
- Spandx::CLI.start
15
- rescue Spandx::CLI::Error => error
13
+ Spandx::Cli::Main.start
14
+ rescue Spandx::Cli::Error => error
16
15
  puts "ERROR: #{error.message}"
17
16
  exit 1
18
17
  end
data/lib/spandx.rb CHANGED
@@ -9,38 +9,15 @@ require 'logger'
9
9
  require 'net/hippie'
10
10
  require 'nokogiri'
11
11
  require 'pathname'
12
+ require 'yaml'
13
+ require 'zeitwerk'
12
14
 
13
- require 'spandx/core/cache'
14
- require 'spandx/core/content'
15
- require 'spandx/core/database'
16
- require 'spandx/core/dependency'
17
- require 'spandx/core/guess'
18
- require 'spandx/core/http'
19
- require 'spandx/core/parser'
20
- require 'spandx/core/report'
21
- require 'spandx/core/score'
22
- require 'spandx/dotnet/index'
23
- require 'spandx/dotnet/nuget_gateway'
24
- require 'spandx/dotnet/package_reference'
25
- require 'spandx/dotnet/parsers/csproj'
26
- require 'spandx/dotnet/parsers/packages_config'
27
- require 'spandx/dotnet/parsers/sln'
28
- require 'spandx/dotnet/project_file'
29
- require 'spandx/java/index'
30
- require 'spandx/java/metadata'
31
- require 'spandx/java/parsers/maven'
32
- require 'spandx/python/parsers/pipfile_lock'
33
- require 'spandx/python/pypi'
34
- require 'spandx/python/source'
35
- require 'spandx/rubygems/gateway'
36
- require 'spandx/rubygems/parsers/gemfile_lock'
37
- require 'spandx/spdx/catalogue'
38
- require 'spandx/spdx/gateway'
39
- require 'spandx/spdx/license'
40
- require 'spandx/version'
15
+ loader = Zeitwerk::Loader.for_gem
16
+ loader.setup # ready!
41
17
 
42
18
  module Spandx
43
19
  class Error < StandardError; end
20
+ Rubygems = Ruby
44
21
 
45
22
  class << self
46
23
  attr_writer :airgap, :logger
@@ -61,10 +38,14 @@ module Spandx
61
38
  @logger ||= Logger.new('/dev/null')
62
39
  end
63
40
 
64
- def spdx_db
65
- @spdx_db ||= Spandx::Core::Database
66
- .new(url: 'https://github.com/spdx/license-list-data.git')
67
- .tap(&:update!)
41
+ def git
42
+ @git ||= {
43
+ cache: ::Spandx::Core::Git.new(url: 'https://github.com/mokhan/spandx-index.git'),
44
+ rubygems: ::Spandx::Core::Git.new(url: 'https://github.com/mokhan/spandx-rubygems.git'),
45
+ spdx: ::Spandx::Core::Git.new(url: 'https://github.com/spdx/license-list-data.git'),
46
+ }
68
47
  end
69
48
  end
70
49
  end
50
+
51
+ loader.eager_load
data/lib/spandx/cli.rb CHANGED
@@ -1,38 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'thor'
4
- require 'spandx'
5
- require 'spandx/cli/command'
6
- require 'spandx/cli/commands/index'
7
- require 'spandx/cli/commands/scan'
8
4
 
9
5
  module Spandx
10
- class CLI < Thor
6
+ module Cli
11
7
  Error = Class.new(StandardError)
12
-
13
- desc 'version', 'spandx version'
14
- def version
15
- puts "v#{Spandx::VERSION}"
16
- end
17
- map %w[--version -v] => :version
18
-
19
- register Spandx::Cli::Commands::Index, 'index', 'index [SUBCOMMAND]', 'Command description...'
20
-
21
- desc 'scan LOCKFILE', 'Scan a lockfile and list dependencies/licenses'
22
- method_option :help, aliases: '-h', type: :boolean, desc: 'Display usage information'
23
- method_option :recursive, aliases: '-r', type: :boolean, desc: 'Perform recursive scan', default: false
24
- method_option :airgap, aliases: '-a', type: :boolean, desc: 'Disable network connections', default: false
25
- method_option :logfile, aliases: '-l', type: :string, desc: 'Path to a logfile', default: '/dev/null'
26
- method_option :format, aliases: '-f', type: :string, desc: 'Format of report', default: 'json'
27
- def scan(lockfile)
28
- Spandx.airgap = options[:airgap]
29
- Spandx.logger = Logger.new(options[:logfile])
30
-
31
- if options[:help]
32
- invoke :help, ['scan']
33
- else
34
- Spandx::Cli::Commands::Scan.new(lockfile, options).execute
35
- end
36
- end
37
8
  end
38
9
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spandx
4
+ module Cli
5
+ module Commands
6
+ class Build
7
+ INDEXES = {
8
+ maven: Spandx::Java::Index,
9
+ nuget: Spandx::Dotnet::Index,
10
+ dotnet: Spandx::Dotnet::Index,
11
+ pypi: Spandx::Python::Index,
12
+ }.freeze
13
+
14
+ def initialize(options)
15
+ @options = options
16
+ end
17
+
18
+ def execute(output: $stdout)
19
+ catalogue = Spandx::Spdx::Catalogue.from_git
20
+ indexes.each do |index|
21
+ output.puts index.name
22
+ index.update!(catalogue: catalogue, output: output)
23
+ end
24
+ output.puts 'OK'
25
+ end
26
+
27
+ private
28
+
29
+ def indexes
30
+ index = INDEXES[@options[:index]&.to_sym]
31
+
32
+ if index.nil?
33
+ INDEXES.values.uniq.map { |x| x.new(directory: @options[:directory]) }
34
+ else
35
+ [index.new(directory: @options[:directory])]
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spandx
4
+ module Cli
5
+ module Commands
6
+ class Pull
7
+ def initialize(options)
8
+ @options = options
9
+ end
10
+
11
+ def execute(output: $stdout)
12
+ Spandx.git.each_value do |db|
13
+ output.puts "Updating #{db.url}..."
14
+ db.update!
15
+ end
16
+ output.puts 'OK'
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -3,12 +3,13 @@
3
3
  module Spandx
4
4
  module Cli
5
5
  module Commands
6
- class Scan < Spandx::Cli::Command
6
+ class Scan
7
7
  attr_reader :scan_path
8
8
 
9
9
  def initialize(scan_path, options)
10
10
  @scan_path = ::Pathname.new(scan_path)
11
11
  @options = options
12
+ require(options[:require]) if options[:require]
12
13
  end
13
14
 
14
15
  def execute(output: $stdout)
@@ -18,7 +19,7 @@ module Spandx
18
19
  report.add(dependency)
19
20
  end
20
21
  end
21
- output.puts report.to(@options[:format])
22
+ output.puts(format(report.to(@options[:format])))
22
23
  end
23
24
 
24
25
  private
@@ -33,6 +34,7 @@ module Spandx
33
34
  if File.directory?(file)
34
35
  each_file_in(file, &block) if recursive?
35
36
  else
37
+ Spandx.logger.debug(file)
36
38
  block.call(file)
37
39
  end
38
40
  end
@@ -42,7 +44,20 @@ module Spandx
42
44
  ::Spandx::Core::Parser
43
45
  .for(file)
44
46
  .parse(file)
47
+ .map { |dependency| enhance(dependency) }
45
48
  .each { |dependency| yield dependency }
49
+ rescue StandardError => error
50
+ Spandx.logger.error(error)
51
+ end
52
+
53
+ def format(output)
54
+ Array(output).map(&:to_s)
55
+ end
56
+
57
+ def enhance(dependency)
58
+ ::Spandx::Core::Plugin
59
+ .all
60
+ .inject(dependency) { |memo, plugin| plugin.enhance(memo) }
46
61
  end
47
62
  end
48
63
  end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spandx
4
+ module Cli
5
+ class Main < Thor
6
+ desc 'scan LOCKFILE', 'Scan a lockfile and list dependencies/licenses'
7
+ method_option :help, aliases: '-h', type: :boolean, desc: 'Display usage information'
8
+ method_option :recursive, aliases: '-R', type: :boolean, desc: 'Perform recursive scan', default: false
9
+ method_option :airgap, aliases: '-a', type: :boolean, desc: 'Disable network connections', default: false
10
+ method_option :logfile, aliases: '-l', type: :string, desc: 'Path to a logfile', default: '/dev/null'
11
+ method_option :format, aliases: '-f', type: :string, desc: 'Format of report', default: 'table'
12
+ method_option :pull, aliases: '-p', type: :boolean, desc: 'Pull the latest cache before the scan', default: false
13
+ method_option :require, aliases: '-r', type: :string, desc: 'Causes spandx to load the library using require.', default: nil
14
+ def scan(lockfile)
15
+ if options[:help]
16
+ invoke :help, ['scan']
17
+ else
18
+ Spandx.airgap = options[:airgap]
19
+ Spandx.logger = Logger.new(options[:logfile])
20
+ pull if options[:pull]
21
+ Spandx::Cli::Commands::Scan.new(lockfile, options).execute
22
+ end
23
+ end
24
+
25
+ desc 'pull', 'Pull the latest offline cache'
26
+ method_option :help, aliases: '-h', type: :boolean, desc: 'Display usage information'
27
+ def pull(*)
28
+ if options[:help]
29
+ invoke :help, ['pull']
30
+ else
31
+ Commands::Pull.new(options).execute
32
+ end
33
+ end
34
+
35
+ desc 'build', 'Build a package index'
36
+ method_option :help, aliases: '-h', type: :boolean, desc: 'Display usage information'
37
+ method_option :directory, aliases: '-d', type: :string, desc: 'Directory to build index in', default: '.index'
38
+ method_option :index, aliases: '-i', type: :string, desc: 'The specific index to build', default: :all
39
+ def build(*)
40
+ if options[:help]
41
+ invoke :help, ['build']
42
+ else
43
+ Commands::Build.new(options).execute
44
+ end
45
+ end
46
+
47
+ desc 'version', 'spandx version'
48
+ def version
49
+ puts "v#{Spandx::VERSION}"
50
+ end
51
+ map %w[--version -v] => :version
52
+ end
53
+ end
54
+ end
@@ -5,14 +5,14 @@ module Spandx
5
5
  class Cache
6
6
  attr_reader :db, :package_manager
7
7
 
8
- def initialize(package_manager, url:)
8
+ def initialize(package_manager, db: Spandx.git[:cache])
9
9
  @package_manager = package_manager
10
- @db = ::Spandx::Core::Database.new(url: url)
10
+ @db = db
11
11
  @cache = {}
12
12
  @lines = {}
13
13
  end
14
14
 
15
- def licenses_for(name:, version:)
15
+ def licenses_for(name, version)
16
16
  found = search(name: name, version: version)
17
17
  Spandx.logger.debug("Cache miss: #{name}-#{version}") if found.nil?
18
18
  found ? found[2].split('-|-') : []
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spandx
4
+ module Core
5
+ class Circuit
6
+ attr_reader :state
7
+
8
+ def initialize(state: :closed)
9
+ @state = state
10
+ end
11
+
12
+ def attempt
13
+ return if open?
14
+
15
+ open!
16
+ result = yield
17
+ close!
18
+ result
19
+ end
20
+
21
+ def open!
22
+ @state = :open
23
+ end
24
+
25
+ def close!
26
+ @state = :closed
27
+ end
28
+
29
+ def open?
30
+ state == :open
31
+ end
32
+ end
33
+ end
34
+ end