licensed 3.1.0 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
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