license_finder 0.6.0 → 0.7.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.
- data/.gitignore +4 -0
- data/Gemfile +0 -1
- data/bin/license_finder +21 -1
- data/features/approve_dependencies.feature +10 -0
- data/features/license_finder.feature +13 -3
- data/features/license_finder_rake_task.feature +3 -3
- data/features/set_license.feature +14 -0
- data/features/step_definitions/steps.rb +5 -0
- data/lib/license_finder.rb +13 -2
- data/lib/license_finder/bundle.rb +25 -3
- data/lib/license_finder/bundle_syncer.rb +12 -0
- data/lib/license_finder/bundled_gem.rb +12 -1
- data/lib/license_finder/cli.rb +42 -3
- data/lib/license_finder/configuration.rb +1 -31
- data/lib/license_finder/dependency.rb +30 -94
- data/lib/license_finder/dependency_report.rb +30 -0
- data/lib/license_finder/html_report.rb +14 -0
- data/lib/license_finder/persistence.rb +1 -0
- data/lib/license_finder/persistence/yaml.rb +7 -0
- data/lib/license_finder/persistence/yaml/configuration.rb +34 -0
- data/lib/license_finder/persistence/yaml/dependency.rb +127 -0
- data/lib/license_finder/reporter.rb +7 -38
- data/lib/license_finder/source_syncer.rb +40 -0
- data/lib/license_finder/text_report.rb +9 -0
- data/lib/templates/dependency.html.erb +2 -2
- data/lib/templates/html_report.erb +93 -0
- data/lib/templates/text_report.erb +2 -0
- data/license_finder.gemspec +2 -2
- data/{README.markdown → readme.md} +22 -11
- data/spec/lib/license_finder/bundle_spec.rb +58 -0
- data/spec/lib/license_finder/bundle_syncer_spec.rb +22 -0
- data/spec/lib/license_finder/bundled_gem_spec.rb +17 -14
- data/spec/lib/license_finder/cli_spec.rb +38 -0
- data/spec/lib/license_finder/dependency_spec.rb +130 -223
- data/spec/lib/license_finder/html_report_spec.rb +67 -0
- data/spec/lib/license_finder/persistence/yaml/configuration_spec.rb +5 -0
- data/spec/lib/license_finder/persistence/yaml/dependency_spec.rb +5 -0
- data/spec/lib/license_finder/possible_license_file_spec.rb +4 -9
- data/spec/lib/license_finder/reporter_spec.rb +0 -1
- data/spec/lib/license_finder/source_syncer_spec.rb +37 -0
- data/spec/lib/license_finder/text_report_spec.rb +29 -0
- data/spec/lib/license_finder_spec.rb +9 -11
- data/spec/spec_helper.rb +1 -1
- data/spec/support/license_examples.rb +1 -1
- data/spec/support/shared_examples/persistence/configuration.rb +34 -0
- data/spec/support/shared_examples/persistence/dependency.rb +139 -0
- metadata +38 -26
- data/lib/license_finder/dependency_list.rb +0 -80
- data/lib/license_finder/viewable.rb +0 -31
- data/lib/templates/dependency_list.html.erb +0 -38
- data/spec/lib/license_finder/dependency_list_spec.rb +0 -243
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/bin/license_finder
CHANGED
@@ -1,4 +1,24 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'license_finder'
|
4
|
-
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
options = {}
|
7
|
+
|
8
|
+
OptionParser.new do |opts|
|
9
|
+
opts.banner = "Usage: license_finder [options] [dependency]"
|
10
|
+
|
11
|
+
opts.on("-a", "--approve", "Approve a gem by name.") do |gem_name|
|
12
|
+
options[:approve] = true
|
13
|
+
end
|
14
|
+
|
15
|
+
opts.on("-l", "--license [LICENSE]", "Update a gem's license.") do |license|
|
16
|
+
options[:license] = license
|
17
|
+
end
|
18
|
+
end.parse!
|
19
|
+
|
20
|
+
unless options.empty?
|
21
|
+
options[:dependency] = ARGV.last
|
22
|
+
end
|
23
|
+
|
24
|
+
LicenseFinder::CLI.execute! options
|
@@ -3,6 +3,16 @@ Feature: Approving non-whitelisted Dependencies
|
|
3
3
|
As an application developer using license finder
|
4
4
|
I want to be able to manually approve dependencies that have licenses which fall outside of my whitelist
|
5
5
|
|
6
|
+
Scenario: Approving a non-whitelisted dependency via the `license_finder` command
|
7
|
+
Given I have an app with license finder
|
8
|
+
And my app depends on a gem "gpl_gem" licensed with "GPL"
|
9
|
+
When I run "license_finder"
|
10
|
+
Then I should see "gpl_gem" in its output
|
11
|
+
When I run "license_finder -a gpl_gem"
|
12
|
+
When I run "license_finder"
|
13
|
+
Then I should not see "gpl_gem" in its output
|
14
|
+
Then I should see the "gpl_gem" in the html flagged as "approved"
|
15
|
+
|
6
16
|
Scenario: Manually approving a non-whitelisted dependency
|
7
17
|
Given I have an app with license finder
|
8
18
|
And my app depends on a gem "gpl_gem" licensed with "GPL"
|
@@ -20,6 +20,14 @@ Feature: License Finder command line executable
|
|
20
20
|
dependencies_file_dir: './'
|
21
21
|
"""
|
22
22
|
|
23
|
+
Scenario: Running with an empty dependencies.yml
|
24
|
+
Given I have an app with license finder
|
25
|
+
And my app depends on a gem "mit_licensed_gem" licensed with "MIT"
|
26
|
+
And I have a truncated dependencies.yml file
|
27
|
+
When I run "license_finder"
|
28
|
+
Then it should exit with status code 1
|
29
|
+
And I should see "mit_licensed_gem" in its output
|
30
|
+
|
23
31
|
Scenario: Auditing an application with non-whitelisted licenses
|
24
32
|
Given I have an app with license finder
|
25
33
|
And my app depends on a gem "mit_licensed_gem" licensed with "MIT"
|
@@ -30,7 +38,9 @@ Feature: License Finder command line executable
|
|
30
38
|
Scenario: Auditing an application with whitelisted licenses
|
31
39
|
Given I have an app with license finder
|
32
40
|
And my app depends on a gem "mit_licensed_gem" licensed with "MIT"
|
33
|
-
And I whitelist the following licenses: "MIT, other"
|
34
41
|
When I run "license_finder"
|
35
|
-
Then
|
36
|
-
|
42
|
+
Then I should see "mit_licensed_gem" in its output
|
43
|
+
When I whitelist the following licenses: "MIT, other"
|
44
|
+
And I run "license_finder"
|
45
|
+
Then I should see "All gems are approved for use" in its output
|
46
|
+
And it should exit with status code 0
|
@@ -1,7 +1,7 @@
|
|
1
|
-
Feature: License Finder
|
2
|
-
So that I can
|
1
|
+
Feature: License Finder rake task
|
2
|
+
So that I can break my build suite if someone adds a dependency to the application with a non-whitelisted license
|
3
3
|
As an application developer
|
4
|
-
I want a
|
4
|
+
I want a rake task that exit's with a non-zero exit status if there are any action items
|
5
5
|
|
6
6
|
Scenario: Running without a configuration file
|
7
7
|
Given I have an app with rake and license finder
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Feature: Set a dependency's license through a command line interface
|
2
|
+
So that my dependencies all have the correct licenses
|
3
|
+
As an application developer
|
4
|
+
I want a command line interface to set licenses for specific dependencies
|
5
|
+
|
6
|
+
Scenario: Setting a license for a dependency
|
7
|
+
Given I have an app with license finder
|
8
|
+
And my app depends on a gem "other_license_gem" licensed with "other"
|
9
|
+
When I run "license_finder"
|
10
|
+
When I run "license_finder -l MIT other_license_gem"
|
11
|
+
Then I should see the following settings for "other_license_gem":
|
12
|
+
"""
|
13
|
+
license: "MIT"
|
14
|
+
"""
|
@@ -296,3 +296,8 @@ When /^the text "([^"]*)" should link to "([^"]*)"$/ do |text, link|
|
|
296
296
|
html = Capybara.string File.read(@user.dependencies_html_path)
|
297
297
|
html.find(:xpath, "//a[@href='#{link}']").text.should == text
|
298
298
|
end
|
299
|
+
When /^I have a truncated dependencies.yml file$/ do
|
300
|
+
File.open(@user.dependencies_file_path, 'w+') do |f|
|
301
|
+
f.puts ""
|
302
|
+
end
|
303
|
+
end
|
data/lib/license_finder.rb
CHANGED
@@ -1,20 +1,31 @@
|
|
1
1
|
require 'pathname'
|
2
2
|
require 'yaml'
|
3
|
+
require 'erb'
|
3
4
|
|
4
5
|
module LicenseFinder
|
5
6
|
ROOT_PATH = Pathname.new(__FILE__).dirname
|
6
7
|
|
8
|
+
DEPENDENCY_ATTRIBUTES = [
|
9
|
+
"name", "source", "version", "license", "license_url", "approved", "notes",
|
10
|
+
"license_files", "readme_files", "bundler_groups", "summary",
|
11
|
+
"description", "homepage", "children", "parents"
|
12
|
+
]
|
13
|
+
|
7
14
|
autoload :Bundle, 'license_finder/bundle'
|
8
15
|
autoload :BundledGem, 'license_finder/bundled_gem'
|
9
16
|
autoload :CLI, 'license_finder/cli'
|
10
17
|
autoload :Configuration, 'license_finder/configuration'
|
18
|
+
autoload :Persistence, 'license_finder/persistence/yaml'
|
11
19
|
autoload :Dependency, 'license_finder/dependency'
|
12
|
-
autoload :DependencyList, 'license_finder/dependency_list'
|
13
20
|
autoload :License, 'license_finder/license'
|
14
21
|
autoload :LicenseUrl, 'license_finder/license_url'
|
15
22
|
autoload :PossibleLicenseFile, 'license_finder/possible_license_file'
|
23
|
+
autoload :DependencyReport, 'license_finder/dependency_report'
|
24
|
+
autoload :HtmlReport, 'license_finder/html_report'
|
25
|
+
autoload :TextReport, 'license_finder/text_report'
|
16
26
|
autoload :Reporter, 'license_finder/reporter'
|
17
|
-
autoload :
|
27
|
+
autoload :BundleSyncer, 'license_finder/bundle_syncer'
|
28
|
+
autoload :SourceSyncer, 'license_finder/source_syncer'
|
18
29
|
|
19
30
|
def self.config
|
20
31
|
@config ||= Configuration.new
|
@@ -1,17 +1,39 @@
|
|
1
1
|
module LicenseFinder
|
2
2
|
class Bundle
|
3
|
+
def initialize(bundler_definition=nil)
|
4
|
+
@definition = bundler_definition || Bundler::Definition.build(gemfile_path, lockfile_path, nil)
|
5
|
+
end
|
6
|
+
|
3
7
|
def gems
|
4
|
-
|
8
|
+
return @gems if @gems
|
9
|
+
|
10
|
+
@gems ||= definition.specs_for(included_groups).map do |spec|
|
5
11
|
dependency = dependencies.detect { |dep| dep.name == spec.name }
|
6
12
|
|
7
13
|
BundledGem.new(spec, dependency)
|
8
14
|
end
|
15
|
+
|
16
|
+
setup_parent_child_relationships
|
17
|
+
|
18
|
+
@gems
|
9
19
|
end
|
10
20
|
|
11
21
|
private
|
22
|
+
attr_reader :definition
|
12
23
|
|
13
|
-
def
|
14
|
-
|
24
|
+
def setup_parent_child_relationships
|
25
|
+
dependency_index = {}
|
26
|
+
|
27
|
+
gems.each do |dep|
|
28
|
+
dependency_index[dep.dependency_name] = dep
|
29
|
+
end
|
30
|
+
|
31
|
+
gems.each do |dep|
|
32
|
+
dep.children.each do |child_dep|
|
33
|
+
license_finder_dependency = dependency_index[child_dep]
|
34
|
+
license_finder_dependency.parents << dep.dependency_name if license_finder_dependency
|
35
|
+
end
|
36
|
+
end
|
15
37
|
end
|
16
38
|
|
17
39
|
def dependencies
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module LicenseFinder
|
2
|
+
module BundleSyncer
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def sync!
|
6
|
+
source_dependencies = Bundle.new.gems.map(&:to_dependency)
|
7
|
+
target_dependencies = Dependency.all.select {|d| d.source == "bundle" }
|
8
|
+
SourceSyncer.new(source_dependencies, target_dependencies).sync!
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
@@ -3,6 +3,8 @@ module LicenseFinder
|
|
3
3
|
LICENSE_FILE_NAMES = %w(LICENSE License Licence COPYING README Readme ReadMe)
|
4
4
|
README_FILE_NAMES = %w(README Readme ReadMe)
|
5
5
|
|
6
|
+
attr_reader :parents
|
7
|
+
|
6
8
|
def initialize(spec, bundler_dependency = nil)
|
7
9
|
@spec = spec
|
8
10
|
@bundler_dependency = bundler_dependency
|
@@ -12,6 +14,10 @@ module LicenseFinder
|
|
12
14
|
"#{dependency_name} #{dependency_version}"
|
13
15
|
end
|
14
16
|
|
17
|
+
def parents
|
18
|
+
@parents ||= []
|
19
|
+
end
|
20
|
+
|
15
21
|
def dependency_name
|
16
22
|
@spec.name
|
17
23
|
end
|
@@ -20,6 +26,10 @@ module LicenseFinder
|
|
20
26
|
@spec.version.to_s
|
21
27
|
end
|
22
28
|
|
29
|
+
def children
|
30
|
+
@children ||= @spec.dependencies.collect(&:name)
|
31
|
+
end
|
32
|
+
|
23
33
|
def to_dependency
|
24
34
|
@dependency ||= LicenseFinder::Dependency.new(
|
25
35
|
'name' => @spec.name,
|
@@ -32,7 +42,8 @@ module LicenseFinder
|
|
32
42
|
'summary' => @spec.summary,
|
33
43
|
'description' => @spec.description,
|
34
44
|
'homepage' => @spec.homepage,
|
35
|
-
'children' =>
|
45
|
+
'children' => children,
|
46
|
+
'parents' => parents
|
36
47
|
)
|
37
48
|
end
|
38
49
|
|
data/lib/license_finder/cli.rb
CHANGED
@@ -3,15 +3,54 @@ module LicenseFinder
|
|
3
3
|
extend self
|
4
4
|
|
5
5
|
def check_for_action_items
|
6
|
-
|
6
|
+
create_default_configuration
|
7
|
+
BundleSyncer.sync!
|
8
|
+
generate_reports
|
7
9
|
|
8
|
-
|
10
|
+
unapproved = Dependency.unapproved
|
11
|
+
|
12
|
+
if unapproved.count == 0
|
9
13
|
puts "All gems are approved for use"
|
10
14
|
else
|
11
15
|
puts "Dependencies that need approval:"
|
12
|
-
puts
|
16
|
+
puts TextReport.new(unapproved)
|
13
17
|
exit 1
|
14
18
|
end
|
15
19
|
end
|
20
|
+
|
21
|
+
def execute!(options={})
|
22
|
+
create_default_configuration
|
23
|
+
|
24
|
+
if options.empty?
|
25
|
+
check_for_action_items
|
26
|
+
else
|
27
|
+
dependency = Dependency.find_by_name(options[:dependency])
|
28
|
+
|
29
|
+
if options[:approve]
|
30
|
+
dependency.approve!
|
31
|
+
puts "The #{dependency.name} has been approved!\n\n"
|
32
|
+
elsif options[:license]
|
33
|
+
dependency.update_attributes :license => options[:license]
|
34
|
+
puts "The #{dependency.name} has been marked as using #{options[:license]} license!\n\n"
|
35
|
+
end
|
36
|
+
|
37
|
+
generate_reports
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def generate_reports
|
43
|
+
LicenseFinder::Reporter.write_reports
|
44
|
+
end
|
45
|
+
|
46
|
+
def create_default_configuration
|
47
|
+
unless File.exists?(LicenseFinder.config.config_file_path)
|
48
|
+
FileUtils.mkdir_p(File.join('.', 'config'))
|
49
|
+
FileUtils.cp(
|
50
|
+
File.join(File.dirname(__FILE__), '..', '..', 'files', 'license_finder.yml'),
|
51
|
+
LicenseFinder.config.config_file_path
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
16
55
|
end
|
17
56
|
end
|
@@ -1,34 +1,4 @@
|
|
1
1
|
module LicenseFinder
|
2
|
-
class Configuration
|
3
|
-
attr_reader :whitelist, :ignore_groups, :dependencies_dir
|
4
|
-
|
5
|
-
def initialize
|
6
|
-
config = {}
|
7
|
-
|
8
|
-
if File.exists?(config_file_path)
|
9
|
-
yaml = File.open(config_file_path).readlines.join
|
10
|
-
config = YAML.load(yaml)
|
11
|
-
end
|
12
|
-
|
13
|
-
@whitelist = config['whitelist'] || []
|
14
|
-
@ignore_groups = (config["ignore_groups"] || []).map(&:to_sym)
|
15
|
-
@dependencies_dir = config['dependencies_file_dir'] || '.'
|
16
|
-
end
|
17
|
-
|
18
|
-
def config_file_path
|
19
|
-
File.join('.', 'config', 'license_finder.yml')
|
20
|
-
end
|
21
|
-
|
22
|
-
def dependencies_yaml
|
23
|
-
File.join(dependencies_dir, "dependencies.yml")
|
24
|
-
end
|
25
|
-
|
26
|
-
def dependencies_text
|
27
|
-
File.join(dependencies_dir, "dependencies.txt")
|
28
|
-
end
|
29
|
-
|
30
|
-
def dependencies_html
|
31
|
-
File.join(dependencies_dir, "dependencies.html")
|
32
|
-
end
|
2
|
+
class Configuration < LicenseFinder::Persistence::Configuration
|
33
3
|
end
|
34
4
|
end
|
@@ -1,37 +1,33 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
require "erb"
|
3
|
-
|
4
1
|
module LicenseFinder
|
5
|
-
class Dependency
|
6
|
-
|
2
|
+
class Dependency < LicenseFinder::Persistence::Dependency
|
3
|
+
def approved
|
4
|
+
return super if super
|
5
|
+
self.approved = config.whitelist.include?(license)
|
6
|
+
end
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
def license_files
|
9
|
+
super || (self.license_files = [])
|
10
|
+
end
|
11
|
+
|
12
|
+
def readme_files
|
13
|
+
super || (self.readme_files = [])
|
14
|
+
end
|
10
15
|
|
11
|
-
|
16
|
+
def bundler_groups
|
17
|
+
super || (self.bundler_groups = [])
|
18
|
+
end
|
12
19
|
|
13
|
-
def
|
14
|
-
|
15
|
-
|
20
|
+
def children
|
21
|
+
super || (self.children = [])
|
22
|
+
end
|
16
23
|
|
17
|
-
|
24
|
+
def parents
|
25
|
+
super || (self.parents = [])
|
18
26
|
end
|
19
27
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
23
|
-
@version = attributes['version']
|
24
|
-
@license = attributes['license']
|
25
|
-
@approved = attributes['approved'] || LicenseFinder.config.whitelist.include?(attributes['license'])
|
26
|
-
@notes = attributes['notes'] || ''
|
27
|
-
@license_files = attributes['license_files'] || []
|
28
|
-
@readme_files = attributes['readme_files'] || []
|
29
|
-
@bundler_groups = attributes['bundler_groups'] || []
|
30
|
-
@summary = attributes['summary']
|
31
|
-
@description = attributes['description']
|
32
|
-
@homepage = attributes['homepage']
|
33
|
-
@children = attributes.fetch('children', [])
|
34
|
-
@parents = attributes.fetch('parents', [])
|
28
|
+
def approve!
|
29
|
+
self.approved = true
|
30
|
+
save
|
35
31
|
end
|
36
32
|
|
37
33
|
def license_url
|
@@ -41,78 +37,18 @@ module LicenseFinder
|
|
41
37
|
def merge(other)
|
42
38
|
raise "Cannot merge dependencies with different names. Expected #{name}, was #{other.name}." unless other.name == name
|
43
39
|
|
44
|
-
|
45
|
-
'name' => name,
|
46
|
-
'version' => other.version,
|
47
|
-
'license_files' => other.license_files,
|
48
|
-
'readme_files' => other.readme_files,
|
49
|
-
'license_url' => other.license_url,
|
50
|
-
'notes' => notes,
|
51
|
-
'source' => other.source,
|
52
|
-
'summary' => other.summary,
|
53
|
-
'description' => other.description,
|
54
|
-
'bundler_groups' => other.bundler_groups,
|
55
|
-
'homepage' => other.homepage,
|
56
|
-
'children' => other.children,
|
57
|
-
'parents' => other.parents
|
58
|
-
)
|
40
|
+
new_attributes = other.attributes.merge("notes" => notes)
|
59
41
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
merged.approved = approved
|
42
|
+
if other.license == license || other.license == 'other'
|
43
|
+
new_attributes["approved"] = approved
|
44
|
+
new_attributes["license"] = license
|
64
45
|
else
|
65
|
-
|
66
|
-
merged.approved = other.approved
|
67
|
-
end
|
68
|
-
|
69
|
-
merged
|
70
|
-
end
|
71
|
-
|
72
|
-
def as_yaml
|
73
|
-
attrs = {
|
74
|
-
'name' => name,
|
75
|
-
'version' => version,
|
76
|
-
'license' => license,
|
77
|
-
'approved' => approved,
|
78
|
-
'source' => source,
|
79
|
-
'license_url' => license_url,
|
80
|
-
'homepage' => homepage,
|
81
|
-
'notes' => notes,
|
82
|
-
'license_files' => nil,
|
83
|
-
'readme_files' => nil
|
84
|
-
}
|
85
|
-
|
86
|
-
unless license_files.empty?
|
87
|
-
attrs['license_files'] = license_files.map do |file|
|
88
|
-
{'path' => file}
|
89
|
-
end
|
46
|
+
new_attributes["approved"] = nil
|
90
47
|
end
|
91
48
|
|
92
|
-
|
93
|
-
attrs['readme_files'] = readme_files.map do |file|
|
94
|
-
{'path' => file}
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
attrs
|
99
|
-
end
|
100
|
-
|
101
|
-
def to_s
|
102
|
-
[name, version, license].join ", "
|
103
|
-
end
|
104
|
-
|
105
|
-
private
|
49
|
+
update_attributes new_attributes
|
106
50
|
|
107
|
-
|
108
|
-
names = string.split('::')
|
109
|
-
names.shift if names.empty? || names.first.empty?
|
110
|
-
|
111
|
-
constant = Object
|
112
|
-
names.each do |name|
|
113
|
-
constant = constant.const_defined?(name, false) ? constant.const_get(name) : constant.const_missing(name)
|
114
|
-
end
|
115
|
-
constant
|
51
|
+
self
|
116
52
|
end
|
117
53
|
end
|
118
54
|
end
|