licensed 3.1.0 → 3.2.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +28 -11
  3. data/CHANGELOG.md +19 -0
  4. data/README.md +25 -80
  5. data/docs/adding_a_new_source.md +11 -8
  6. data/docs/commands/README.md +59 -0
  7. data/docs/commands/cache.md +35 -0
  8. data/docs/commands/env.md +10 -0
  9. data/docs/commands/list.md +23 -0
  10. data/docs/commands/migrate.md +10 -0
  11. data/docs/commands/notices.md +12 -0
  12. data/docs/commands/status.md +73 -0
  13. data/docs/commands/version.md +3 -0
  14. data/docs/configuration.md +9 -173
  15. data/docs/configuration/README.md +11 -0
  16. data/docs/configuration/allowed_licenses.md +17 -0
  17. data/docs/configuration/application_name.md +63 -0
  18. data/docs/configuration/application_source.md +64 -0
  19. data/docs/configuration/configuration_root.md +27 -0
  20. data/docs/configuration/configuring_multiple_apps.md +58 -0
  21. data/docs/configuration/dependency_source_enumerators.md +28 -0
  22. data/docs/configuration/ignoring_dependencies.md +19 -0
  23. data/docs/configuration/metadata_cache.md +106 -0
  24. data/docs/configuration/reviewing_dependencies.md +18 -0
  25. data/lib/licensed.rb +1 -0
  26. data/lib/licensed/cli.rb +2 -2
  27. data/lib/licensed/commands/cache.rb +19 -20
  28. data/lib/licensed/commands/command.rb +104 -72
  29. data/lib/licensed/commands/environment.rb +12 -11
  30. data/lib/licensed/commands/list.rb +0 -19
  31. data/lib/licensed/commands/notices.rb +0 -19
  32. data/lib/licensed/commands/status.rb +13 -15
  33. data/lib/licensed/configuration.rb +77 -7
  34. data/lib/licensed/report.rb +44 -0
  35. data/lib/licensed/reporters/cache_reporter.rb +48 -64
  36. data/lib/licensed/reporters/json_reporter.rb +19 -21
  37. data/lib/licensed/reporters/list_reporter.rb +45 -58
  38. data/lib/licensed/reporters/notices_reporter.rb +33 -46
  39. data/lib/licensed/reporters/reporter.rb +37 -104
  40. data/lib/licensed/reporters/status_reporter.rb +58 -56
  41. data/lib/licensed/reporters/yaml_reporter.rb +19 -21
  42. data/lib/licensed/sources/bundler.rb +1 -1
  43. data/lib/licensed/sources/gradle.rb +2 -2
  44. data/lib/licensed/sources/npm.rb +4 -3
  45. data/lib/licensed/version.rb +1 -1
  46. data/script/source-setup/go +1 -1
  47. metadata +21 -3
  48. data/docs/commands.md +0 -95
@@ -0,0 +1,27 @@
1
+ # Configuration root
2
+
3
+ **Key**: root
4
+ **Default value**:
5
+
6
+ 1. the root of the local git repository, if run inside a git repository
7
+ 1. the directory that `licensed` is run from
8
+
9
+ An application's root path is used as the base for any relative configuration paths in the application.
10
+
11
+ From a configuration file, the root value can be specified as one of the following. Path string values can contain special path characters.
12
+
13
+ - a relative path from the configuration file location
14
+ - an absolute path
15
+ - `true` to use the configuration file's directory as the root
16
+
17
+ When creating a `Licensed::AppConfiguration` manually with a `root` property, the property must be an absolute path - no path expansion will occur.
18
+
19
+ ```yml
20
+ root: path/from/configuration
21
+ # or
22
+ root: /absolute/path/to/root
23
+ # or
24
+ root: ~/path/from/home/to/root
25
+ # or
26
+ root: true
27
+ ```
@@ -0,0 +1,58 @@
1
+ # Configuring multiple application definitions
2
+
3
+ **Key**: apps
4
+ **Required**: false
5
+
6
+ The configuration file can specify multiple source paths to enumerate metadata, each with their own configuration by using the `apps` key.
7
+ Each source path and any additional configuration make up an "application". Root configuration settings are inherited into each application,
8
+ allowing applications to share a common configuration and reducing the overall size of the configuration file.
9
+
10
+ When the apps key is not given, the root configuration is treated as a single application.
11
+
12
+ ```yml
13
+ apps:
14
+ # application definition for "go-application"
15
+ - source_path: path/to/go-application
16
+ sources:
17
+ go: true
18
+ allowed:
19
+ - mit
20
+
21
+ # application definition for "ruby-application"
22
+ - source_path: path/to/ruby-application
23
+ sources:
24
+ bundler: true
25
+ allowed:
26
+ - bsd-3-clause
27
+ ```
28
+
29
+ ## Inheriting configuration
30
+
31
+ Applications inherit all root configuration settings. Inherited settings will be overridden by any configuration set directly on the application definition.
32
+
33
+ In this example, two apps have been declared. The first app, with `source_path: path/to/application1`, inherits all configuration settings from the root configuration. The second app, with `source_path: path/to/application2`, overrides the `sources` configuration and inherits all other settings.
34
+
35
+ ```yml
36
+ sources:
37
+ go: true
38
+ bundler: false
39
+
40
+ ignored:
41
+ bundler:
42
+ - some-internal-gem
43
+
44
+ reviewed:
45
+ bundler:
46
+ - bcrypt-ruby
47
+
48
+ cache_path: 'path/to/cache'
49
+ apps:
50
+ # inherits all settings from the root configuration
51
+ - source_path: 'path/to/application1'
52
+
53
+ # inherits all settings except for "sources" from the root configuration
54
+ - source_path: 'path/to/application2'
55
+ sources:
56
+ bundler: true
57
+ go: false
58
+ ```
@@ -0,0 +1,28 @@
1
+ # Specifying dependency sources to use in an application
2
+
3
+ **Key**: sources
4
+ **Required**: false
5
+ **Default value**: All sources enabled
6
+
7
+ The sources configuration property specifies which sources `licensed` will use to enumerate dependencies.
8
+ By default, `licensed` will try to enumerate dependencies from all sources. As a result,
9
+ the configuration property should be used to explicitly disable sources rather than to enable a particular source.
10
+
11
+ This configuration value does not guarantee that a source will enumerate dependencies. Each
12
+ configured source's `enabled?` method must return true for licensed to pull dependency information.
13
+
14
+ `licensed` determines which sources will try to enumerate dependencies based on the following rules:
15
+
16
+ 1. If no sources are configured, all sources are enabled
17
+ 2. If no sources are set to true, any unconfigured sources are enabled
18
+ 3. If any sources are set to true, any unconfigured sources are disabled
19
+
20
+ ```yml
21
+ # all other sources are enabled by default since there are no sources set to true
22
+ sources:
23
+ bower: false
24
+
25
+ # all other sources are disabled by default because a source was set to true
26
+ sources:
27
+ bower: true
28
+ ```
@@ -0,0 +1,19 @@
1
+ # Ignoring dependencies
2
+
3
+ **Key**: ignored
4
+ **Default value**: none
5
+
6
+ This configuration property is used to fully ignore a dependency during all `licensed` commands. Any dependency on this list will not
7
+ be enumerated, or have its metadata cached or checked for compliance. This is intended for dependencies that do not require attribution
8
+ or compliance checking - internal or 1st party dependencies, or dependencies that do not ship with the product such as test frameworks.
9
+
10
+ The ignored 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 no longer take action on the dependency. Glob patterns can be used to identify multiple internal dependencies without having to manage a large list.
11
+
12
+ ```yml
13
+ ignored:
14
+ bundler:
15
+ - my-internal-gem
16
+ - my-first-party-gem
17
+ go:
18
+ - github.com/me/my-repo/**/*
19
+ ```
@@ -0,0 +1,106 @@
1
+ # Dependency metadata cache
2
+
3
+ **Key**: cache_path
4
+ **Default value**: .licenses
5
+
6
+ The cache path is the root directory where `licensed` will write cached metadata files for each dependency.
7
+ By default, files will be written to a folder under the cache path in a structure like:
8
+
9
+ ```text
10
+ <cache path>
11
+ |_<source name>
12
+ |_<dependency identifier>.dep.yml
13
+ ```
14
+
15
+ Cache paths can be given as an absolute or relative path and can contain special path characters
16
+
17
+ ```yml
18
+ cache_path: relative/path/to/cache
19
+ # or
20
+ cache_path: /absolute/path/to/cache
21
+ # or
22
+ cache_path: ~/path/from/home/to/cache
23
+ ```
24
+
25
+ ## Configuring caches for multiple applications
26
+
27
+ When multiple applications are specified in a configuration file caches can be shared, inherited and explicitly configured.
28
+ Unless otherwise specified, preference is given based on the locality and intention of the configuration options.
29
+
30
+ 1. explicitly configured cache paths are preferred to inherited or shared cache paths
31
+ 2. shared cache paths are preferred to inherited cache paths
32
+ 3. cache paths that are not otherwise set by an application will be inherited from the root configuration
33
+
34
+ ### Explicit cache usage for multiple applications
35
+
36
+ Individual applications in a multi-application configuration can explicitly set cache paths.
37
+
38
+ ```yml
39
+ apps:
40
+ - source_path: path/to/application1
41
+ cache_path: path/to/application1/.licenses
42
+ - source_path: path/to/application2
43
+ cache_path: path/to/application2/.licenses
44
+ ```
45
+
46
+ ### Sharing a single cache for multiple applications
47
+
48
+ Sharing a cache across multiple applications is possible by setting `cache_path: <path>` and `shared_cache: true` at the root level.
49
+ Individual applications can opt out of sharing a cache by explicitly setting a cache path.
50
+
51
+ ```yml
52
+ shared_cache: true
53
+ cache_path: .cache/shared
54
+ apps:
55
+ # application1 and application2 will share a cache at .cache/shared
56
+ - source_path: path/to/application1
57
+ - source_path: path/to/application2
58
+ # application3 will use a separate cache at .cache/application3
59
+ - source_path: path/to/application3
60
+ cache_path: .cache/application3
61
+ ```
62
+
63
+ This is equivalent to explicitly configuring the same cache for many applications
64
+
65
+ ```yaml
66
+ apps:
67
+ - source_path: "path/to/application1"
68
+ cache_path: ".cache/shared"
69
+ - source_path: "path/to/application2"
70
+ cache_path: ".cache/shared"
71
+ ```
72
+
73
+ Using the `shared_cache` key is primarily useful when specifying `source_path` as a glob pattern.
74
+
75
+ ```yml
76
+ shared_cache: true
77
+ cache_path: .cache/shared
78
+ source_path: path/to/*
79
+ ```
80
+
81
+ ### Inheriting cache usage from the root configuration
82
+
83
+ When not otherwise specified, applications will inherit a cache path from the root configuration.
84
+ If the root configuration does not explicitly set a cache path value, the default cache path value is used.
85
+
86
+ Inherited cache paths are treated as the root location for each application's metadata cache. Each application
87
+ will store metadata in a named subdirectory of the root location to avoid file path clashes between
88
+ applications.
89
+
90
+ ```yml
91
+ # optional. if not specified, the default value `.licenses` will be used
92
+ cache_path: .cache
93
+ apps:
94
+ - source_path: path/to/application1
95
+ - source_path: path/to/application2
96
+ ```
97
+
98
+ ```text
99
+ .cache
100
+ |_application1
101
+ |_<source type>
102
+ |_<dependency identifier>.dep.yml
103
+ |_application2
104
+ |_<source type>
105
+ |_<dependency identifier>.dep.yml
106
+ ```
@@ -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
+ ```
data/lib/licensed.rb CHANGED
@@ -6,6 +6,7 @@ require "licensed/dependency"
6
6
  require "licensed/git"
7
7
  require "licensed/sources"
8
8
  require "licensed/configuration"
9
+ require "licensed/report"
9
10
  require "licensed/reporters"
10
11
  require "licensed/commands"
11
12
  require "licensed/ui/shell"
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