kitchen-inspector 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.travis.yml +9 -0
- data/CHANGELOG.md +24 -0
- data/Gemfile +0 -2
- data/README.md +268 -39
- data/Rakefile +41 -0
- data/kitchen-inspector.gemspec +21 -3
- data/lib/kitchen-inspector/inspector.rb +11 -4
- data/lib/kitchen-inspector/inspector/chef_inspector.rb +66 -0
- data/lib/kitchen-inspector/inspector/cli.rb +29 -3
- data/lib/kitchen-inspector/inspector/{error.rb → common.rb} +43 -1
- data/lib/kitchen-inspector/inspector/dependency.rb +26 -40
- data/lib/kitchen-inspector/inspector/health_bureau.rb +181 -0
- data/lib/kitchen-inspector/inspector/mixin/utils.rb +83 -0
- data/lib/kitchen-inspector/inspector/report/report.rb +182 -0
- data/lib/kitchen-inspector/inspector/report/status_reporter.rb +105 -0
- data/lib/kitchen-inspector/inspector/repository_inspector.rb +134 -0
- data/lib/kitchen-inspector/inspector/repository_managers/base.rb +110 -0
- data/lib/kitchen-inspector/inspector/repository_managers/github.rb +97 -0
- data/lib/kitchen-inspector/inspector/repository_managers/gitlab.rb +100 -0
- data/lib/kitchen-inspector/inspector/version.rb +1 -2
- data/spec/cli_spec.rb +46 -0
- data/spec/data/cookbook_deps/metadata.rb +10 -0
- data/spec/data/cookbook_no_deps/metadata.rb +7 -0
- data/spec/data/test_client.pem +27 -0
- data/spec/data/test_config_invalid.rb +4 -0
- data/spec/data/test_config_valid.rb +4 -0
- data/spec/dependency_inspector_spec.rb +296 -0
- data/spec/github_manager_spec.rb +79 -0
- data/spec/gitlab_manager_spec.rb +58 -0
- data/spec/report_spec.rb +237 -0
- data/spec/support/spec_helper.rb +81 -0
- data/spec/utils_spec.rb +29 -0
- metadata +129 -15
- data/INFO.md +0 -44
- data/info.css +0 -31
- data/lib/kitchen-inspector/inspector/dependency_inspector.rb +0 -153
- data/lib/kitchen-inspector/inspector/report.rb +0 -148
@@ -0,0 +1,83 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2013 Stefano Tortarolo <stefano.tortarolo@gmail.com>
|
3
|
+
#
|
4
|
+
# MIT License
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
# a copy of this software and associated documentation files (the
|
8
|
+
# "Software"), to deal in the Software without restriction, including
|
9
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
# the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
#
|
25
|
+
|
26
|
+
module KitchenInspector
|
27
|
+
module Inspector
|
28
|
+
module Utils
|
29
|
+
def config_msg(human_name, field)
|
30
|
+
"#{human_name} not configured. Please set #{field} in your config file."
|
31
|
+
end
|
32
|
+
|
33
|
+
# Import a configuration from a file or StringIO
|
34
|
+
def read_config(config)
|
35
|
+
if config.is_a?(StringIO)
|
36
|
+
config.string
|
37
|
+
elsif File.exists?(config) && File.readable?(config)
|
38
|
+
IO.read(config)
|
39
|
+
else
|
40
|
+
raise ConfigurationError, "Unable to load the configuration: '#{config}'.\nPlease refer to README.md and check that a valid configuration was provided."
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Normalize version names to x.y.z...
|
45
|
+
def fix_version_name(version)
|
46
|
+
version.gsub(/[v][\.]*/i, "")
|
47
|
+
end
|
48
|
+
|
49
|
+
# Return from versions the best match that satisfies the given constraint
|
50
|
+
def satisfy(constraint, versions)
|
51
|
+
Solve::Solver.satisfy_best(constraint, versions).to_s
|
52
|
+
rescue Solve::Errors::NoSolutionError
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
|
56
|
+
def get_latest_version(versions)
|
57
|
+
versions.collect do |v|
|
58
|
+
begin
|
59
|
+
Solve::Version.new(v)
|
60
|
+
rescue Solve::Errors::InvalidVersionFormat => e
|
61
|
+
# Skip invalid tags
|
62
|
+
Solve::Version.new("0.0.0")
|
63
|
+
end
|
64
|
+
end.max
|
65
|
+
end
|
66
|
+
|
67
|
+
# Evaluate only interesting lines
|
68
|
+
#
|
69
|
+
# Used also to ignore errors for missing
|
70
|
+
# files referenced in metadata.rb e.g., README.md
|
71
|
+
def eval_metadata(raw_response)
|
72
|
+
clean_response = raw_response.split("\n").select do |line|
|
73
|
+
line.strip!
|
74
|
+
line =~ /^depends|^name|^version/
|
75
|
+
end
|
76
|
+
|
77
|
+
metadata = Ridley::Chef::Cookbook::Metadata.new
|
78
|
+
metadata.instance_eval clean_response.join("\n")
|
79
|
+
metadata
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2013 Stefano Tortarolo <stefano.tortarolo@gmail.com>
|
3
|
+
#
|
4
|
+
# MIT License
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
# a copy of this software and associated documentation files (the
|
8
|
+
# "Software"), to deal in the Software without restriction, including
|
9
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
# the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
#
|
25
|
+
|
26
|
+
module KitchenInspector
|
27
|
+
module Inspector
|
28
|
+
class Report
|
29
|
+
class << self
|
30
|
+
# Generates the status of dependent cookbooks in specified format
|
31
|
+
def generate(dependencies, format, opts={})
|
32
|
+
case format
|
33
|
+
when 'table'
|
34
|
+
TableReport.generate(dependencies, opts)
|
35
|
+
when'json'
|
36
|
+
JSONReport.generate(dependencies)
|
37
|
+
else
|
38
|
+
raise UnsupportedReportFormatError, "Report format '#{format}' is not supported"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Reports the cookbook dependency status in a table format
|
45
|
+
#
|
46
|
+
class TableReport
|
47
|
+
class << self
|
48
|
+
# Generate a report in tabular format
|
49
|
+
#
|
50
|
+
# @param dependencies [Array<Dependency>] list of cookbook dependency objects
|
51
|
+
# @param opts [Hash] options (only flag to show remarks at this stage)
|
52
|
+
# @return [Array] printable report and global status code
|
53
|
+
def generate(dependencies, opts)
|
54
|
+
headings = ["Name", "Requirement", "Used", "Chef\nLatest", "Repository\nLatest", "Requirement\nStatus",
|
55
|
+
"Chef Server\nStatus", "Repository\nStatus"]
|
56
|
+
|
57
|
+
if opts[:remarks]
|
58
|
+
headings << "Remarks"
|
59
|
+
remarks_counter = 0
|
60
|
+
remarks = []
|
61
|
+
end
|
62
|
+
|
63
|
+
rows, remarks = generate_rows(dependencies, opts)
|
64
|
+
|
65
|
+
# Show Table
|
66
|
+
table = Terminal::Table.new headings: headings, rows: rows
|
67
|
+
|
68
|
+
# Show Status
|
69
|
+
g_status, g_status_code = StatusReporter.global_status(dependencies)
|
70
|
+
|
71
|
+
if opts[:remarks]
|
72
|
+
remarks_result = remarks.each_with_index.collect{|remark, idx| "[#{idx + 1}]: #{remark}"}.join("\n")
|
73
|
+
output = "#{table}\n#{g_status}\n\nRemarks:\n#{remarks_result}"
|
74
|
+
else
|
75
|
+
output = "#{table}\n#{g_status}"
|
76
|
+
end
|
77
|
+
[output, g_status_code]
|
78
|
+
end
|
79
|
+
|
80
|
+
# Generate table rows
|
81
|
+
def generate_rows(dependencies, opts)
|
82
|
+
rows = []
|
83
|
+
remarks = []
|
84
|
+
|
85
|
+
dependencies.select{|d| d.parents.empty?}.each do |dependency|
|
86
|
+
dep_rows, dep_remarks = display_child(dependency, remarks.size, 0, opts)
|
87
|
+
|
88
|
+
rows.push(*dep_rows)
|
89
|
+
remarks.push(*dep_remarks)
|
90
|
+
end
|
91
|
+
[rows, remarks]
|
92
|
+
end
|
93
|
+
|
94
|
+
def display_child(dependency, remarks_counter, level, opts)
|
95
|
+
row, remarks = generate_row(dependency, remarks_counter, level, opts)
|
96
|
+
remarks_counter += remarks.size
|
97
|
+
children_rows = []
|
98
|
+
children_remarks = []
|
99
|
+
|
100
|
+
dependency.dependencies.each do |child|
|
101
|
+
child_row, child_remarks = display_child(child, remarks_counter, level + 1, opts)
|
102
|
+
remarks_counter += child_remarks.size
|
103
|
+
|
104
|
+
children_rows.push(*child_row)
|
105
|
+
children_remarks.push(*child_remarks)
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
[[row, *children_rows], [remarks, children_remarks].flatten]
|
110
|
+
end
|
111
|
+
|
112
|
+
def indent_name(name, level)
|
113
|
+
level > 0 ? "#{(' ' * level) + INDENT_MARK} #{name}" : name
|
114
|
+
end
|
115
|
+
|
116
|
+
# Generate a single row and its remarks
|
117
|
+
def generate_row(dependency, remarks_counter, level, opts)
|
118
|
+
row_remarks = []
|
119
|
+
|
120
|
+
status = StatusReporter.status_to_mark(dependency.status)
|
121
|
+
chef_status = StatusReporter.status_to_mark(dependency.chef[:status])
|
122
|
+
repomanager_status = StatusReporter.status_to_mark(dependency.repomanager[:status])
|
123
|
+
|
124
|
+
name = indent_name(dependency.name.dup, level)
|
125
|
+
name = name.red if dependency.status == :err_req
|
126
|
+
|
127
|
+
row = [
|
128
|
+
name,
|
129
|
+
dependency.requirement,
|
130
|
+
dependency.chef[:version_used],
|
131
|
+
dependency.chef[:latest_version],
|
132
|
+
dependency.repomanager[:latest_metadata],
|
133
|
+
{ value: status, alignment: :center },
|
134
|
+
{ value: chef_status, alignment: :center },
|
135
|
+
{ value: repomanager_status, alignment: :center }
|
136
|
+
]
|
137
|
+
|
138
|
+
if opts[:remarks]
|
139
|
+
remarks_idx, remarks_counter = remarks_indices(dependency.remarks, remarks_counter)
|
140
|
+
row_remarks.push(*dependency.remarks)
|
141
|
+
row << remarks_idx
|
142
|
+
end
|
143
|
+
|
144
|
+
[row, row_remarks]
|
145
|
+
end
|
146
|
+
|
147
|
+
# Return the indices of the remarks
|
148
|
+
def remarks_indices(remarks, remarks_counter)
|
149
|
+
end_counter = remarks_counter + remarks.count
|
150
|
+
|
151
|
+
return [((remarks_counter + 1)..end_counter).to_a.join(', '), end_counter] unless remarks.empty?
|
152
|
+
return ['', end_counter]
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Return Kitchen's status in JSON format
|
158
|
+
class JSONReport
|
159
|
+
class << self
|
160
|
+
# @param dependencies [Array<Dependency>] list of dependencies
|
161
|
+
# @return [Array] JSON report and global status code
|
162
|
+
def generate(dependencies)
|
163
|
+
# Show Status
|
164
|
+
g_status, g_status_code = StatusReporter.global_status(dependencies)
|
165
|
+
|
166
|
+
[JSON.pretty_generate(dependencies_hash(dependencies)), g_status_code]
|
167
|
+
end
|
168
|
+
|
169
|
+
# Converts the dependency objects to JSON object
|
170
|
+
#
|
171
|
+
# @param dependencies [Array<Dependency>] list of dependencies
|
172
|
+
def dependencies_hash(dependencies)
|
173
|
+
{}.tap do |hash|
|
174
|
+
dependencies.each do |dependency|
|
175
|
+
hash[dependency.name] = dependency.to_hash
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2013 Stefano Tortarolo <stefano.tortarolo@gmail.com>
|
3
|
+
#
|
4
|
+
# MIT License
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
# a copy of this software and associated documentation files (the
|
8
|
+
# "Software"), to deal in the Software without restriction, including
|
9
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
# the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
#
|
25
|
+
|
26
|
+
module KitchenInspector
|
27
|
+
module Inspector
|
28
|
+
# Provides functions to report single or global status
|
29
|
+
class StatusReporter
|
30
|
+
class << self
|
31
|
+
# Return a global status
|
32
|
+
def global_status(dependencies)
|
33
|
+
result = nil
|
34
|
+
dependencies.each do |dep|
|
35
|
+
status = single_status(dep)
|
36
|
+
|
37
|
+
if status
|
38
|
+
result = status
|
39
|
+
break
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
unless result
|
44
|
+
result = {:mark => TICK_MARK, :color => :green, :code => :up_to_date}
|
45
|
+
end
|
46
|
+
|
47
|
+
["Status: #{result[:code]} (#{result[:mark]})".send(result[:color]), result[:code]]
|
48
|
+
end
|
49
|
+
|
50
|
+
# Return a dependency local status if different from up-to-date
|
51
|
+
def single_status(dep)
|
52
|
+
return mark_structure(:err_req) if dep.status == :err_req
|
53
|
+
|
54
|
+
return mark_structure(:err_repo) if dep.repomanager[:status] == :err_repo
|
55
|
+
|
56
|
+
return mark_structure(:warn_outofdate_repo) if dep.repomanager[:status] == :warn_outofdate_repo
|
57
|
+
|
58
|
+
return mark_structure(:warn_req) if dep.status == :warn_req
|
59
|
+
|
60
|
+
return mark_structure(:warn_mismatch_repo) if dep.repomanager[:status] == :warn_mismatch_repo
|
61
|
+
|
62
|
+
return mark_structure(:warn_chef) if dep.chef[:status] == :warn_chef
|
63
|
+
|
64
|
+
return mark_structure(:warn_notunique_repo) if dep.repomanager[:status] == :warn_notunique_repo
|
65
|
+
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
|
69
|
+
# Given a status return instructions on how to draw it
|
70
|
+
def mark_structure(status)
|
71
|
+
case status
|
72
|
+
when :err_req, :err_chef
|
73
|
+
{:mark => STATUSES[status], :color => :red, :code => status }
|
74
|
+
when :err_repo
|
75
|
+
{:mark => STATUSES[status], :color => :yellow, :code => status }
|
76
|
+
when :warn_outofdate_repo
|
77
|
+
{:mark => STATUSES[status], :color => :light_red, :code => status, :style => :bold }
|
78
|
+
when :warn_req
|
79
|
+
{:mark => STATUSES[status], :color => :yellow, :code => status, :style => :bold }
|
80
|
+
when :warn_mismatch_repo
|
81
|
+
{:mark => STATUSES[status], :color => :light_red, :code => status, :style => :bold }
|
82
|
+
when :warn_chef
|
83
|
+
{:mark => STATUSES[status], :color => :blue, :code => status, :style => :bold }
|
84
|
+
when :warn_notunique_repo
|
85
|
+
{:mark => STATUSES[status], :color => :light_red, :code => status }
|
86
|
+
when :up_to_date
|
87
|
+
{:mark => STATUSES[status], :color => :green, :code => status }
|
88
|
+
else
|
89
|
+
raise StandardError, "Unknown status #{status}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Given a status draw a mark
|
94
|
+
def status_to_mark(status)
|
95
|
+
mark = mark_structure(status)
|
96
|
+
|
97
|
+
result = mark[:mark]
|
98
|
+
result = result.send(mark[:style]) if mark[:style]
|
99
|
+
result = result.send(mark[:color])
|
100
|
+
result
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2013 Stefano Tortarolo <stefano.tortarolo@gmail.com>
|
3
|
+
#
|
4
|
+
# MIT License
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
# a copy of this software and associated documentation files (the
|
8
|
+
# "Software"), to deal in the Software without restriction, including
|
9
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
# the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
#
|
25
|
+
|
26
|
+
module KitchenInspector
|
27
|
+
module Inspector
|
28
|
+
class RepositoryInspector
|
29
|
+
include Utils
|
30
|
+
|
31
|
+
attr_accessor :manager
|
32
|
+
|
33
|
+
def initialize(config)
|
34
|
+
begin
|
35
|
+
require "kitchen-inspector/inspector/repository_managers/#{config[:type].downcase}"
|
36
|
+
manager_cls = "KitchenInspector::Inspector::#{config[:type]}Manager".constantize
|
37
|
+
rescue LoadError, NameError => e
|
38
|
+
raise RepositoryManagerError, "Repository Manager '#{config[:type]}' not supported"
|
39
|
+
end
|
40
|
+
|
41
|
+
@manager = manager_cls.new config
|
42
|
+
end
|
43
|
+
|
44
|
+
# Given a dependency and a version provided by Chef Server,
|
45
|
+
# analyze that dependency and its transitive dependencies (if recursive)
|
46
|
+
#
|
47
|
+
# It also detects whether multiple projects exist with the same name
|
48
|
+
# e.g., different users or forks
|
49
|
+
#
|
50
|
+
# @return [Array] containing pairs [dependency, repository_information]
|
51
|
+
def investigate(dependency, version_used, recursive)
|
52
|
+
repo_dependencies = []
|
53
|
+
projects = @manager.projects_by_name(dependency.name)
|
54
|
+
|
55
|
+
if projects.empty?
|
56
|
+
repo_dependencies << [dependency, {}]
|
57
|
+
else
|
58
|
+
projects.each do |project|
|
59
|
+
repo_info = analyze_repository(project)
|
60
|
+
repo_info[:not_unique] = projects.size > 1
|
61
|
+
|
62
|
+
# Inherit only shallow information from dependency
|
63
|
+
prj_dependency = Dependency.new(dependency.name, dependency.requirement)
|
64
|
+
prj_dependency.parents = dependency.parents
|
65
|
+
|
66
|
+
prj_dependency.parents.each do |parent|
|
67
|
+
parent.dependencies << prj_dependency
|
68
|
+
end
|
69
|
+
|
70
|
+
repo_dependencies << [prj_dependency, repo_info]
|
71
|
+
|
72
|
+
# Analyze its dependencies based on Repository Manager
|
73
|
+
if recursive && repo_info[:tags]
|
74
|
+
reference_version = get_reference_version(version_used, repo_info)
|
75
|
+
|
76
|
+
children = @manager.project_dependencies(project,
|
77
|
+
repo_info[:tags][reference_version]).collect do |dep|
|
78
|
+
dep.parents << prj_dependency
|
79
|
+
investigate(dep, version_used, recursive)
|
80
|
+
end.flatten!(1)
|
81
|
+
|
82
|
+
repo_dependencies.push(*children)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
repo_dependencies
|
88
|
+
end
|
89
|
+
|
90
|
+
# Return the reference version to be used for recursive analysis
|
91
|
+
#
|
92
|
+
# It's the version used, if present. The latest available tag on the Repository
|
93
|
+
# Manager otherwise.
|
94
|
+
def get_reference_version(version_used, repo_info)
|
95
|
+
reference_version = nil
|
96
|
+
reference_version = version_used if version_used && repo_info[:tags].include?(version_used)
|
97
|
+
reference_version = repo_info[:latest_tag].to_s unless reference_version
|
98
|
+
reference_version
|
99
|
+
end
|
100
|
+
|
101
|
+
# Check whether tag and metadata's version match
|
102
|
+
def consistent_version?(info)
|
103
|
+
!(info[:latest_tag] &&
|
104
|
+
info[:latest_metadata] &&
|
105
|
+
info[:latest_tag] != info[:latest_metadata])
|
106
|
+
end
|
107
|
+
|
108
|
+
# Return an url pointing to the diff between startRev and endRev
|
109
|
+
def get_changelog(repo_info, startRev, endRev)
|
110
|
+
return unless repo_info[:tags]
|
111
|
+
|
112
|
+
url = @manager.changelog(repo_info[:source_url],
|
113
|
+
repo_info[:tags][startRev],
|
114
|
+
repo_info[:tags][endRev])
|
115
|
+
"Changelog: #{url}" if url
|
116
|
+
end
|
117
|
+
|
118
|
+
# Retrieve project info from Repository Manager
|
119
|
+
def analyze_repository(project)
|
120
|
+
tags = @manager.tags(project)
|
121
|
+
latest_tag = get_latest_version(tags.keys)
|
122
|
+
|
123
|
+
latest_metadata = @manager.project_metadata_version(project, tags[latest_tag.to_s])
|
124
|
+
latest_metadata = Solve::Version.new(latest_metadata) if latest_metadata
|
125
|
+
|
126
|
+
{:tags => tags,
|
127
|
+
:latest_tag => latest_tag,
|
128
|
+
:latest_metadata => latest_metadata,
|
129
|
+
:source_url => @manager.source_url(project)
|
130
|
+
}
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|