licensed 3.0.0 → 3.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) 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 +180 -47
  5. data/.ruby-version +1 -1
  6. data/CHANGELOG.md +60 -1
  7. data/README.md +25 -79
  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 -161
  29. data/docs/sources/swift.md +4 -0
  30. data/lib/licensed/cli.rb +2 -2
  31. data/lib/licensed/commands/cache.rb +19 -20
  32. data/lib/licensed/commands/command.rb +104 -72
  33. data/lib/licensed/commands/environment.rb +12 -11
  34. data/lib/licensed/commands/list.rb +0 -19
  35. data/lib/licensed/commands/notices.rb +0 -19
  36. data/lib/licensed/commands/status.rb +13 -15
  37. data/lib/licensed/configuration.rb +105 -12
  38. data/lib/licensed/report.rb +44 -0
  39. data/lib/licensed/reporters/cache_reporter.rb +48 -64
  40. data/lib/licensed/reporters/json_reporter.rb +19 -21
  41. data/lib/licensed/reporters/list_reporter.rb +45 -58
  42. data/lib/licensed/reporters/notices_reporter.rb +33 -46
  43. data/lib/licensed/reporters/reporter.rb +37 -104
  44. data/lib/licensed/reporters/status_reporter.rb +58 -56
  45. data/lib/licensed/reporters/yaml_reporter.rb +19 -21
  46. data/lib/licensed/sources/bundler/definition.rb +36 -0
  47. data/lib/licensed/sources/bundler/missing_specification.rb +1 -1
  48. data/lib/licensed/sources/bundler.rb +38 -86
  49. data/lib/licensed/sources/dep.rb +2 -2
  50. data/lib/licensed/sources/go.rb +3 -3
  51. data/lib/licensed/sources/gradle.rb +2 -2
  52. data/lib/licensed/sources/helpers/content_versioning.rb +2 -1
  53. data/lib/licensed/sources/npm.rb +4 -3
  54. data/lib/licensed/sources/nuget.rb +56 -27
  55. data/lib/licensed/sources/swift.rb +69 -0
  56. data/lib/licensed/sources.rb +1 -0
  57. data/lib/licensed/version.rb +1 -1
  58. data/lib/licensed.rb +1 -0
  59. data/licensed.gemspec +4 -4
  60. data/script/source-setup/go +1 -1
  61. data/script/source-setup/swift +22 -0
  62. metadata +48 -13
  63. data/docs/commands.md +0 -95
@@ -0,0 +1,18 @@
1
+ # Reviewing dependencies
2
+
3
+ **Key**: reviewed
4
+ **Default value**: none
5
+
6
+ Sometimes your projects will use a dependency with an OSS license that you don't want to globally allow but can use with individual review.
7
+ The list of reviewed dependencies is meant to cover this scenario and will prevent the status command from raising an error for
8
+ a dependency with a license not on the allowed list.
9
+
10
+ The reviewed dependency list is organized based on the dependency source type - `bundler`, `go`, etc. Add a dependency's metadata identifier to the appropriate source type sub-property to cause `licensed` to ignore license compliance failures. Glob patterns can be used to identify multiple internal dependencies without having to manage a large list.
11
+
12
+ _NOTE: marking a dependency as reviewed will not prevent licensed from raising an error on missing license information._
13
+
14
+ ```yml
15
+ reviewed:
16
+ bundler:
17
+ - gem-using-unallowed-license
18
+ ```
@@ -2,75 +2,12 @@
2
2
 
3
3
  A configuration file specifies the details of enumerating and operating on license metadata for apps.
4
4
 
5
- Configuration can be specified in either YML or JSON formats. Examples below are given in YML.
5
+ Configuration can be specified in either YML or JSON formats, with examples given in YML. The example
6
+ below describes common configuration values and their purposes. See [configuration options documentation](./configuration)
7
+ for in depth information.
6
8
 
7
- ## Configuration Paths
8
-
9
- `licensed` requires a path to enumerate dependencies at (`source_path`) and a path to store cached metadata (`cache_path`).
10
-
11
- To determine these paths across multiple environments where absolute paths will differ, a known root path is needed to evaluate relative paths against.
12
- In using a root, relative source and cache paths can be specified in the configuration file.
13
-
14
- When using a configuration file, the root property can be set as either a path that can be expanded from the configuration file directory using `File.expand_path`, or the value `true` to use the configuration file directory as the root.
15
-
16
- When creating a `Licensed::Dependency` manually with a `root` property, the property must be an absolute path - no path expansion will occur.
17
-
18
- If a root path is not specified, it will default to using the following, in order of precedence
19
- 1. the root of the local git repository, if run inside a git repository
20
- 2. the current directory
21
-
22
- ### Source path glob patterns
23
-
24
- The `source_path` property can use a glob path to share configuration properties across multiple application entrypoints.
25
-
26
- For example, there is a common pattern in Go projects to include multiple executable entrypoints under folders in `cmd`. Using a glob pattern allows users to avoid manually configuring and maintaining multiple licensed application `source_path`s. Using a glob pattern will also ensure that any new entrypoints matching the pattern are automatically picked up by licensed commands as they are added.
27
-
28
- ```yml
29
- sources:
30
- go: true
31
-
32
- # treat all directories under `cmd` as separate apps
33
- source_path: cmd/*
34
- ```
35
-
36
- Glob patterns are syntactic sugar for, and provide the same functionality as, manually specifying multiple `source_path` values. See the instructions on [specifying multiple apps](./#specifying-multiple-apps) below for additional considerations when using multiple apps.
37
-
38
- ## Restricting sources
39
-
40
- The `sources` configuration property specifies which sources `licensed` will use to enumerate dependencies.
41
- By default, `licensed` will generally try to enumerate dependencies from all sources. As a result,
42
- the configuration property should be used to explicitly disable sources rather than to enable a particular source.
43
-
44
- Be aware that this configuration is separate from an individual sources `#enabled?` method, which determines
45
- whether the source is valid for the current project. Even if a source is enabled in the configuration
46
- it may still determine that it can't enumerate dependencies for a project.
9
+ Additionally, some dependency sources have their own specific configuration options. See the [source documentation](./sources) for details.
47
10
 
48
- ```yml
49
- sources:
50
- bower: true
51
- bundler: false
52
- ```
53
-
54
- `licensed` determines which sources will try to enumerate dependencies based on the following rules:
55
- 1. If no sources are configured, all sources are enabled
56
- 2. If no sources are set to true, any unconfigured sources are enabled
57
- ```yml
58
- sources:
59
- bower: false
60
- # all other sources are enabled by default since there are no sources set to true
61
- ```
62
- 3. If any sources are set to true, any unconfigured sources are disabled
63
- ```yml
64
- sources:
65
- bower: true
66
- # all other sources are disabled by default because a source was set to true
67
- ```
68
-
69
- ## Applications
70
-
71
- What is an "app"? In the context of `licensed`, an app is a combination of a source path and a cache path.
72
-
73
- Configuration can be set up for single or multiple applications in the same repo. There are a number of settings available for each app:
74
11
  ```yml
75
12
  # If not set, defaults to the directory name of `source_path`
76
13
  name: 'My application'
@@ -129,100 +66,11 @@ reviewed:
129
66
  bower:
130
67
  - classlist # public domain
131
68
  - octicons
132
- ```
133
-
134
- ### Specifying a single app
135
- To specify a single app, either include a single app with `source_path` in the `apps` configuration, or remove the `apps` setting entirely.
136
-
137
- If the configuration does not contain an `apps` value, the root configuration will be used as an app definition. In this scenario, the `source_path` is not a required value and will default to the directory that `licensed` was executed from.
138
-
139
- If the configuration contains an `apps` value with a single app configuration, `source_path` must be specified. Additionally, the applications inherited `cache_path` value will contain the application name. See [Inherited cache_path values](#inherited_cache_path_values)
140
-
141
- ### Specifying multiple apps
142
- The configuration file can specify multiple source paths to enumerate metadata, each with their own configuration.
143
-
144
- Nearly all configuration settings can be inherited from root configuration to app configuration. Only `source_path` is required to define an app.
145
69
 
146
- Here are some examples:
147
-
148
- #### Inheriting configuration
149
- ```yml
150
- sources:
151
- go: true
152
- bundler: false
153
-
154
- ignored:
155
- bundler:
156
- - some-internal-gem
157
-
158
- reviewed:
159
- bundler:
160
- - bcrypt-ruby
161
-
162
- cache_path: 'path/to/cache'
163
- apps:
164
- - source_path: 'path/to/app1'
165
- - source_path: 'path/to/app2'
166
- sources:
167
- bundler: true
168
- go: false
169
- ```
170
-
171
- In this example, two apps have been declared. The first app, with `source_path` `path/to/app1`, inherits all configuration settings from the root configuration. The second app, with `source_path` `path/to/app2`, overrides the `sources` configuration and inherits all other settings.
172
-
173
- #### Default app names
174
- An app will not inherit a name set from the root configuration. If not provided, the `name` value will default to the directory name from `source_path`.
175
- ```yml
70
+ # A single configuration file can be used to enumerate dependencies for multiple
71
+ # projects. Each configuration is referred to as an "application" and must include
72
+ # a source path, at a minimum
176
73
  apps:
177
- - source_path: 'path/to/app1'
178
- - source_path: 'path/to/app2'
74
+ - source_path: path/to/application1
75
+ - source_path: path/to/application2
179
76
  ```
180
-
181
- In this example, the apps have names of `app1` and `app2`, respectively.
182
-
183
- #### Inherited cache_path values
184
- When an app inherits a `cache_path` from the root configuration, it will automatically append it's name to the end of the path to separate it's metadata from other apps. To force multiple apps to use the same path to cached metadata, explicitly set the `cache_path` value for each app.
185
- ```yml
186
- cache_path: 'path/to/cache'
187
- apps:
188
- - source_path: 'path/to/app1'
189
- name: 'app1'
190
- - source_path: 'path/to/app2'
191
- name: 'app2'
192
- - source_path: 'path/to/app3'
193
- name: 'app3'
194
- cache_path: 'path/to/app3/cache'
195
- ```
196
-
197
- In this example `app1` and `app2` have `cache_path` values of `path/to/cache/app1` and `path/to/cache/app2`, respectively. `app3` has an explicit path set to `path/to/app3/cache`
198
-
199
- ```yml
200
- apps:
201
- - source_path: 'path/to/app1'
202
- ```
203
-
204
- In this example, the root configuration will contain a default cache path of `.licenses`. `app1` will inherit this value and append it's name, resulting in a cache path of `.licenses/app1`.
205
-
206
- ### Sharing caches between apps
207
-
208
- Dependency caches can be shared between apps by setting the same cache path on each app.
209
-
210
- ```yaml
211
- apps:
212
- - source_path: "path/to/app1"
213
- cache_path: ".licenses/apps"
214
- - source_path: "path/to/app2"
215
- cache_path: ".licenses/apps"
216
- ```
217
-
218
- When using a source path with a glob pattern, the apps created from the glob pattern can share a dependency by setting an explicit cache path and setting `shared_cache` to true.
219
-
220
- ```yaml
221
- source_path: "path/to/apps/*"
222
- cache_path: ".licenses/apps"
223
- shared_cache: true
224
- ```
225
-
226
- ## Source specific configuration
227
-
228
- See the [source documentation](./sources) for details on any source specific configuration.
@@ -0,0 +1,4 @@
1
+ # Swift
2
+
3
+ The Swift source uses `swift package` subcommands
4
+ to enumerate dependencies and properties.
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,12 @@ 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
+ # add the full cache path to the list of cache paths
43
+ # that should be cleaned up after the command run
44
+ cache_paths << app.cache_path.join(source.class.type)
50
45
 
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
46
+ super
55
47
  end
56
48
 
57
49
  # Cache dependency record data.
@@ -70,6 +62,12 @@ module Licensed
70
62
 
71
63
  filename = app.cache_path.join(source.class.type, "#{dependency.name}.#{DependencyRecord::EXTENSION}")
72
64
  cached_record = Licensed::DependencyRecord.read(filename)
65
+
66
+ report["cached"] = false
67
+ report["license"] = cached_record["license"] if cached_record
68
+ report["filename"] = filename.to_s
69
+ report["version"] = dependency.version
70
+
73
71
  if options[:force] || save_dependency_record?(dependency, cached_record)
74
72
  if dependency.record.matches?(cached_record)
75
73
  # use the cached license value if the license text wasn't updated
@@ -82,6 +80,7 @@ module Licensed
82
80
 
83
81
  dependency.record.save(filename)
84
82
  report["cached"] = true
83
+ report["license"] = dependency.record["license"]
85
84
  end
86
85
 
87
86
  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,37 @@ 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
124
  # 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
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
+ return true
124
131
  end
132
+
133
+ dependencies = source.dependencies.sort_by { |dependency| dependency.name }
134
+ results = dependencies.map do |dependency|
135
+ dependency_report = Licensed::Report.new(name: [report.name, dependency.name].join("."), target: dependency)
136
+ report.reports << dependency_report
137
+ run_dependency(app, source, dependency, dependency_report)
138
+ end
139
+
140
+ result = results.all?
141
+
142
+ yield(result) if block_given?
143
+
144
+ result
145
+ rescue Licensed::Shell::Error => err
146
+ report.errors << err.message
147
+ false
148
+ rescue Licensed::Sources::Source::Error => err
149
+ report.errors << err.message
150
+ false
151
+ ensure
152
+ reporter.end_report_source(source, report)
125
153
  end
126
154
 
127
155
  # Run the command for a dependency, evaluating the dependency and
@@ -131,27 +159,27 @@ module Licensed
131
159
  # app - The application configuration for the dependency
132
160
  # source - The dependency source enumerator for the dependency
133
161
  # dependency - An application dependency
162
+ # report - A report object for this dependency
134
163
  #
135
164
  # 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
165
+ def run_dependency(app, source, dependency, report)
166
+ reporter.begin_report_dependency(dependency, report)
142
167
 
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
148
-
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
168
+ if dependency.errors?
169
+ report.errors.concat(dependency.errors)
170
+ return false
154
171
  end
172
+
173
+ result = evaluate_dependency(app, source, dependency, report)
174
+
175
+ yield(result) if block_given?
176
+
177
+ result
178
+ rescue Licensed::DependencyRecord::Error, Licensed::Shell::Error => err
179
+ report.errors << err.message
180
+ false
181
+ ensure
182
+ reporter.end_report_dependency(dependency, report)
155
183
  end
156
184
 
157
185
  # Evaluate a dependency for the command. Must be implemented by a command implementation.
@@ -165,6 +193,10 @@ module Licensed
165
193
  def evaluate_dependency(app, source, dependency, report)
166
194
  raise "`evaluate_dependency` must be implemented by a command"
167
195
  end
196
+
197
+ def sources_overrides
198
+ @sources_overrides = Array(options[:sources])
199
+ end
168
200
  end
169
201
  end
170
202
  end