licensed 3.1.0 → 3.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +19 -0
  3. data/.github/workflows/release.yml +4 -4
  4. data/.github/workflows/test.yml +169 -48
  5. data/.ruby-version +1 -1
  6. data/CHANGELOG.md +51 -1
  7. data/README.md +25 -80
  8. data/docker/Dockerfile.build-linux +1 -1
  9. data/docs/adding_a_new_source.md +11 -8
  10. data/docs/commands/README.md +59 -0
  11. data/docs/commands/cache.md +35 -0
  12. data/docs/commands/env.md +10 -0
  13. data/docs/commands/list.md +23 -0
  14. data/docs/commands/migrate.md +10 -0
  15. data/docs/commands/notices.md +12 -0
  16. data/docs/commands/status.md +74 -0
  17. data/docs/commands/version.md +3 -0
  18. data/docs/configuration/README.md +11 -0
  19. data/docs/configuration/allowed_licenses.md +17 -0
  20. data/docs/configuration/application_name.md +63 -0
  21. data/docs/configuration/application_source.md +64 -0
  22. data/docs/configuration/configuration_root.md +27 -0
  23. data/docs/configuration/configuring_multiple_apps.md +58 -0
  24. data/docs/configuration/dependency_source_enumerators.md +28 -0
  25. data/docs/configuration/ignoring_dependencies.md +19 -0
  26. data/docs/configuration/metadata_cache.md +106 -0
  27. data/docs/configuration/reviewing_dependencies.md +18 -0
  28. data/docs/configuration.md +9 -173
  29. data/lib/licensed/cli.rb +2 -2
  30. data/lib/licensed/commands/cache.rb +21 -20
  31. data/lib/licensed/commands/command.rb +108 -73
  32. data/lib/licensed/commands/environment.rb +12 -11
  33. data/lib/licensed/commands/list.rb +0 -19
  34. data/lib/licensed/commands/notices.rb +0 -19
  35. data/lib/licensed/commands/status.rb +13 -15
  36. data/lib/licensed/configuration.rb +77 -7
  37. data/lib/licensed/report.rb +44 -0
  38. data/lib/licensed/reporters/cache_reporter.rb +48 -64
  39. data/lib/licensed/reporters/json_reporter.rb +19 -21
  40. data/lib/licensed/reporters/list_reporter.rb +45 -58
  41. data/lib/licensed/reporters/notices_reporter.rb +33 -46
  42. data/lib/licensed/reporters/reporter.rb +37 -104
  43. data/lib/licensed/reporters/status_reporter.rb +58 -56
  44. data/lib/licensed/reporters/yaml_reporter.rb +19 -21
  45. data/lib/licensed/sources/bundler/definition.rb +36 -0
  46. data/lib/licensed/sources/bundler/missing_specification.rb +10 -7
  47. data/lib/licensed/sources/bundler.rb +34 -70
  48. data/lib/licensed/sources/dep.rb +2 -2
  49. data/lib/licensed/sources/go.rb +3 -3
  50. data/lib/licensed/sources/gradle.rb +2 -2
  51. data/lib/licensed/sources/helpers/content_versioning.rb +2 -1
  52. data/lib/licensed/sources/npm.rb +4 -3
  53. data/lib/licensed/sources/nuget.rb +1 -2
  54. data/lib/licensed/version.rb +1 -1
  55. data/lib/licensed.rb +1 -0
  56. data/licensed.gemspec +4 -4
  57. data/script/source-setup/go +1 -1
  58. metadata +45 -13
  59. data/docs/commands.md +0 -95
data/lib/licensed/cli.rb CHANGED
@@ -20,12 +20,12 @@ module Licensed
20
20
  end
21
21
 
22
22
  desc "status", "Check status of dependencies' cached licenses"
23
- method_option :format, enum: ["yaml", "json"],
24
- desc: "Output format"
25
23
  method_option :config, aliases: "-c", type: :string,
26
24
  desc: "Path to licensed configuration file"
27
25
  method_option :sources, aliases: "-s", type: :array,
28
26
  desc: "Individual source(s) to evaluate. Must also be enabled via configuration."
27
+ method_option :format, aliases: "-f", enum: ["yaml", "json"],
28
+ desc: "Output format"
29
29
  def status
30
30
  run Licensed::Commands::Status.new(config: config), sources: options[:sources], reporter: options[:format]
31
31
  end
@@ -11,6 +11,8 @@ module Licensed
11
11
  Licensed::Reporters::CacheReporter.new
12
12
  end
13
13
 
14
+ protected
15
+
14
16
  # Run the command.
15
17
  # Removes any cached records that don't match a current application
16
18
  # dependency.
@@ -18,20 +20,15 @@ module Licensed
18
20
  # options - Options to run the command with
19
21
  #
20
22
  # Returns whether the command was a success
21
- def run(**options)
22
- begin
23
- result = super
23
+ def run_command(report)
24
+ super do |result|
24
25
  clear_stale_cached_records if result
25
-
26
- result
27
- ensure
28
- cache_paths.clear
29
- files.clear
30
26
  end
27
+ ensure
28
+ cache_paths.clear
29
+ files.clear
31
30
  end
32
31
 
33
- protected
34
-
35
32
  # Run the command for all enumerated dependencies found in a dependency source,
36
33
  # recording results in a report.
37
34
  # Enumerating dependencies in the source is skipped if a :sources option
@@ -41,17 +38,14 @@ module Licensed
41
38
  # source - A dependency source enumerator
42
39
  #
43
40
  # Returns whether the command succeeded for the dependency source enumerator
44
- def run_source(app, source)
45
- super do |report|
46
- if Array(options[:sources]).any? && !options[:sources].include?(source.class.type)
47
- report.warnings << "skipped source"
48
- next :skip
49
- end
41
+ def run_source(app, source, report)
42
+ result = super
50
43
 
51
- # add the full cache path to the list of cache paths
52
- # that should be cleaned up after the command run
53
- cache_paths << app.cache_path.join(source.class.type)
54
- end
44
+ # add the full cache path to the list of cache paths
45
+ # that should be cleaned up after the command run
46
+ cache_paths << app.cache_path.join(source.class.type) unless result == :skipped
47
+
48
+ result
55
49
  end
56
50
 
57
51
  # Cache dependency record data.
@@ -70,6 +64,12 @@ module Licensed
70
64
 
71
65
  filename = app.cache_path.join(source.class.type, "#{dependency.name}.#{DependencyRecord::EXTENSION}")
72
66
  cached_record = Licensed::DependencyRecord.read(filename)
67
+
68
+ report["cached"] = false
69
+ report["license"] = cached_record["license"] if cached_record
70
+ report["filename"] = filename.to_s
71
+ report["version"] = dependency.version
72
+
73
73
  if options[:force] || save_dependency_record?(dependency, cached_record)
74
74
  if dependency.record.matches?(cached_record)
75
75
  # use the cached license value if the license text wasn't updated
@@ -82,6 +82,7 @@ module Licensed
82
82
 
83
83
  dependency.record.save(filename)
84
84
  report["cached"] = true
85
+ report["license"] = dependency.record["license"]
85
86
  end
86
87
 
87
88
  if !dependency.exist?
@@ -18,23 +18,12 @@ module Licensed
18
18
  def run(**options)
19
19
  @options = options
20
20
  @reporter = create_reporter(options)
21
- begin
22
- result = reporter.report_run(self) do |report|
23
- # allow additional report data to be given by commands
24
- if block_given?
25
- next true if (yield report) == :skip
26
- end
27
-
28
- config.apps.sort_by { |app| app["name"] }
29
- .map { |app| run_app(app) }
30
- .all?
31
- end
32
- ensure
33
- @options = nil
34
- @reporter = nil
35
- end
36
21
 
37
- result
22
+ command_report = Licensed::Report.new(name: nil, target: self)
23
+ run_command(command_report)
24
+ ensure
25
+ @options = nil
26
+ @reporter = nil
38
27
  end
39
28
 
40
29
  # Creates a reporter to use during a command run
@@ -64,36 +53,65 @@ module Licensed
64
53
 
65
54
  protected
66
55
 
56
+ # Run the command for all application configurations
57
+ #
58
+ # report - A Licensed::Report object for this command
59
+ #
60
+ # Returns whether the command succeeded
61
+ def run_command(report)
62
+ reporter.begin_report_command(self, report)
63
+ apps = config.apps.sort_by { |app| app["name"] }
64
+ results = apps.map do |app|
65
+ app_report = Licensed::Report.new(name: app["name"], target: app)
66
+ report.reports << app_report
67
+ run_app(app, app_report)
68
+ end
69
+
70
+ result = results.all?
71
+
72
+ yield(result) if block_given?
73
+
74
+ result
75
+ ensure
76
+ reporter.end_report_command(self, report)
77
+ end
78
+
67
79
  # Run the command for all enabled sources for an application configuration,
68
80
  # recording results in a report.
69
81
  #
70
82
  # app - An application configuration
83
+ # report - A report object for this application
71
84
  #
72
85
  # Returns whether the command succeeded for the application.
73
- def run_app(app)
74
- reporter.report_app(app) do |report|
75
- # ensure the app source path exists before evaluation
76
- if !Dir.exist?(app.source_path)
77
- report.errors << "No such directory #{app.source_path}"
78
- next false
79
- end
86
+ def run_app(app, report)
87
+ reporter.begin_report_app(app, report)
80
88
 
81
- Dir.chdir app.source_path do
82
- begin
83
- # allow additional report data to be given by commands
84
- if block_given?
85
- next true if (yield report) == :skip
86
- end
87
-
88
- app.sources.select(&:enabled?)
89
- .sort_by { |source| source.class.type }
90
- .map { |source| run_source(app, source) }.all?
91
- rescue Licensed::Shell::Error => err
92
- report.errors << err.message
93
- false
94
- end
89
+ # ensure the app source path exists before evaluation
90
+ if !Dir.exist?(app.source_path)
91
+ report.errors << "No such directory #{app.source_path}"
92
+ return false
93
+ end
94
+
95
+ Dir.chdir app.source_path do
96
+ sources = app.sources.select(&:enabled?)
97
+ .sort_by { |source| source.class.type }
98
+ results = sources.map do |source|
99
+ source_report = Licensed::Report.new(name: [report.name, source.class.type].join("."), target: source)
100
+ report.reports << source_report
101
+ run_source(app, source, source_report)
95
102
  end
103
+
104
+ result = results.all?
105
+
106
+ yield(result) if block_given?
107
+
108
+ result
96
109
  end
110
+ rescue Licensed::Shell::Error => err
111
+ report.errors << err.message
112
+ false
113
+ ensure
114
+ reporter.end_report_app(app, report)
97
115
  end
98
116
 
99
117
  # Run the command for all enumerated dependencies found in a dependency source,
@@ -101,27 +119,40 @@ module Licensed
101
119
  #
102
120
  # app - The application configuration for the source
103
121
  # source - A dependency source enumerator
122
+ # report - A report object for this source
104
123
  #
105
- # Returns whether the command succeeded for the dependency source enumerator
106
- def run_source(app, source)
107
- reporter.report_source(source) do |report|
108
- begin
109
- # allow additional report data to be given by commands
110
- if block_given?
111
- next true if (yield report) == :skip
112
- end
113
-
114
- source.dependencies.sort_by { |dependency| dependency.name }
115
- .map { |dependency| run_dependency(app, source, dependency) }
116
- .all?
117
- rescue Licensed::Shell::Error => err
118
- report.errors << err.message
119
- false
120
- rescue Licensed::Sources::Source::Error => err
121
- report.errors << err.message
122
- false
123
- end
124
+ # Returns whether the command succeeded, failed, or was skipped for the dependency source enumerator
125
+ def run_source(app, source, report)
126
+ reporter.begin_report_source(source, report)
127
+
128
+ if !sources_overrides.empty? && !sources_overrides.include?(source.class.type)
129
+ report.warnings << "skipped source"
130
+
131
+ # return a symbol to speficy the source was skipped.
132
+ # This is truthy and will result in the source being considered successful
133
+ return :skipped
134
+ end
135
+
136
+ dependencies = source.dependencies.sort_by { |dependency| dependency.name }
137
+ results = dependencies.map do |dependency|
138
+ dependency_report = Licensed::Report.new(name: [report.name, dependency.name].join("."), target: dependency)
139
+ report.reports << dependency_report
140
+ run_dependency(app, source, dependency, dependency_report)
124
141
  end
142
+
143
+ result = results.all?
144
+
145
+ yield(result) if block_given?
146
+
147
+ result
148
+ rescue Licensed::Shell::Error => err
149
+ report.errors << err.message
150
+ false
151
+ rescue Licensed::Sources::Source::Error => err
152
+ report.errors << err.message
153
+ false
154
+ ensure
155
+ reporter.end_report_source(source, report)
125
156
  end
126
157
 
127
158
  # Run the command for a dependency, evaluating the dependency and
@@ -131,27 +162,27 @@ module Licensed
131
162
  # app - The application configuration for the dependency
132
163
  # source - The dependency source enumerator for the dependency
133
164
  # dependency - An application dependency
165
+ # report - A report object for this dependency
134
166
  #
135
167
  # Returns whether the command succeeded for the dependency
136
- def run_dependency(app, source, dependency)
137
- reporter.report_dependency(dependency) do |report|
138
- if dependency.errors?
139
- report.errors.concat(dependency.errors)
140
- return false
141
- end
142
-
143
- begin
144
- # allow additional report data to be given by commands
145
- if block_given?
146
- next true if (yield report) == :skip
147
- end
168
+ def run_dependency(app, source, dependency, report)
169
+ reporter.begin_report_dependency(dependency, report)
148
170
 
149
- evaluate_dependency(app, source, dependency, report)
150
- rescue Licensed::DependencyRecord::Error, Licensed::Shell::Error => err
151
- report.errors << err.message
152
- false
153
- end
171
+ if dependency.errors?
172
+ report.errors.concat(dependency.errors)
173
+ return false
154
174
  end
175
+
176
+ result = evaluate_dependency(app, source, dependency, report)
177
+
178
+ yield(result) if block_given?
179
+
180
+ result
181
+ rescue Licensed::DependencyRecord::Error, Licensed::Shell::Error => err
182
+ report.errors << err.message
183
+ false
184
+ ensure
185
+ reporter.end_report_dependency(dependency, report)
155
186
  end
156
187
 
157
188
  # Evaluate a dependency for the command. Must be implemented by a command implementation.
@@ -165,6 +196,10 @@ module Licensed
165
196
  def evaluate_dependency(app, source, dependency, report)
166
197
  raise "`evaluate_dependency` must be implemented by a command"
167
198
  end
199
+
200
+ def sources_overrides
201
+ @sources_overrides = Array(options[:sources])
202
+ end
168
203
  end
169
204
  end
170
205
  end
@@ -29,12 +29,6 @@ module Licensed
29
29
  end
30
30
  end
31
31
 
32
- def run(**options)
33
- super do |report|
34
- report["git_repo"] = Licensed::Git.git_repo?
35
- end
36
- end
37
-
38
32
  # Returns the default reporter to use during the command run
39
33
  #
40
34
  # options - The options the command was run with
@@ -46,11 +40,18 @@ module Licensed
46
40
 
47
41
  protected
48
42
 
49
- def run_app(app)
50
- reporter.report_app(app) do |report|
51
- report.merge! AppEnvironment.new(app).to_h
52
- true
53
- end
43
+ def run_command(report)
44
+ report["git_repo"] = Licensed::Git.git_repo?
45
+ super
46
+ end
47
+
48
+ def run_app(app, report)
49
+ report.merge! AppEnvironment.new(app).to_h
50
+ super
51
+ end
52
+
53
+ def run_source(app, source, report)
54
+ true
54
55
  end
55
56
  end
56
57
  end
@@ -13,25 +13,6 @@ module Licensed
13
13
 
14
14
  protected
15
15
 
16
- # Run the command for all enumerated dependencies found in a dependency source,
17
- # recording results in a report.
18
- # Enumerating dependencies in the source is skipped if a :sources option
19
- # is provided and the evaluated `source.class.type` is not in the :sources values
20
- #
21
- # app - The application configuration for the source
22
- # source - A dependency source enumerator
23
- #
24
- # Returns whether the command succeeded for the dependency source enumerator
25
- def run_source(app, source)
26
- super do |report|
27
- next if Array(options[:sources]).empty?
28
- next if options[:sources].include?(source.class.type)
29
-
30
- report.warnings << "skipped source"
31
- :skip
32
- end
33
- end
34
-
35
16
  # Listing dependencies requires no extra work.
36
17
  #
37
18
  # app - The application configuration for the dependency
@@ -13,25 +13,6 @@ module Licensed
13
13
 
14
14
  protected
15
15
 
16
- # Run the command for all enumerated dependencies found in a dependency source,
17
- # recording results in a report.
18
- # Enumerating dependencies in the source is skipped if a :sources option
19
- # is provided and the evaluated `source.class.type` is not in the :sources values
20
- #
21
- # app - The application configuration for the source
22
- # source - A dependency source enumerator
23
- #
24
- # Returns whether the command succeeded for the dependency source enumerator
25
- def run_source(app, source)
26
- super do |report|
27
- next if Array(options[:sources]).empty?
28
- next if options[:sources].include?(source.class.type)
29
-
30
- report.warnings << "skipped source"
31
- :skip
32
- end
33
- end
34
-
35
16
  # Load stored dependency record data to add to the notices report.
36
17
  #
37
18
  # app - The application configuration for the dependency
@@ -15,22 +15,17 @@ module Licensed
15
15
 
16
16
  protected
17
17
 
18
- # Run the command for all enumerated dependencies found in a dependency source,
19
- # recording results in a report.
20
- # Enumerating dependencies in the source is skipped if a :sources option
21
- # is provided and the evaluated `source.class.type` is not in the :sources values
18
+ # Run the comand and set an error message to review the documentation
19
+ # when any errors have been reported
22
20
  #
23
- # app - The application configuration for the source
24
- # source - A dependency source enumerator
21
+ # report - A Licensed::Report object for this command
25
22
  #
26
- # Returns whether the command succeeded for the dependency source enumerator
27
- def run_source(app, source)
28
- super do |report|
29
- next if Array(options[:sources]).empty?
30
- next if options[:sources].include?(source.class.type)
31
-
32
- report.warnings << "skipped source"
33
- :skip
23
+ # Returns whether the command succeeded based on the call to super
24
+ def run_command(report)
25
+ super do |result|
26
+ next if result
27
+
28
+ report.errors << "Licensed found errors during source enumeration. Please see https://github.com/github/licensed/tree/master/docs/commands/status.md#status-errors-and-resolutions for possible resolutions."
34
29
  end
35
30
  end
36
31
 
@@ -47,11 +42,14 @@ module Licensed
47
42
  def evaluate_dependency(app, source, dependency, report)
48
43
  filename = app.cache_path.join(source.class.type, "#{dependency.name}.#{DependencyRecord::EXTENSION}")
49
44
  report["filename"] = filename
45
+ report["version"] = dependency.version
50
46
 
51
47
  cached_record = cached_record(filename)
52
48
  if cached_record.nil?
49
+ report["license"] = nil
53
50
  report.errors << "cached dependency record not found"
54
51
  else
52
+ report["license"] = cached_record["license"]
55
53
  report.errors << "cached dependency record out of date" if cached_record["version"] != dependency.version
56
54
  report.errors << "missing license text" if cached_record.licenses.empty?
57
55
  if cached_record["review_changed_license"]
@@ -61,7 +59,7 @@ module Licensed
61
59
  end
62
60
  end
63
61
 
64
- report.errors.empty?
62
+ report["allowed"] = report.errors.empty?
65
63
  end
66
64
 
67
65
  # Returns true if a cached record needs further review based on the