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