inspec 0.29.0 → 0.30.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +32 -2
- data/Rakefile +53 -0
- data/docs/cli.rst +442 -0
- data/examples/inheritance/inspec.yml +3 -0
- data/inspec.gemspec +1 -0
- data/lib/inspec/cli.rb +10 -1
- data/lib/inspec/completions/bash.sh.erb +45 -0
- data/lib/inspec/completions/zsh.sh.erb +61 -0
- data/lib/inspec/dependencies.rb +307 -0
- data/lib/inspec/dsl.rb +5 -20
- data/lib/inspec/env_printer.rb +149 -0
- data/lib/inspec/errors.rb +17 -0
- data/lib/inspec/metadata.rb +4 -0
- data/lib/inspec/profile.rb +12 -0
- data/lib/inspec/profile_context.rb +5 -2
- data/lib/inspec/shell.rb +7 -2
- data/lib/inspec/shell_detector.rb +90 -0
- data/lib/inspec/version.rb +1 -1
- data/lib/resources/postgres.rb +94 -12
- data/lib/resources/registry_key.rb +106 -27
- data/lib/utils/hash_map.rb +37 -0
- data/test/bench/startup.flat.txt +998 -0
- data/test/bench/startup.graph.html +71420 -0
- data/test/bench/startup.grind.dat +103554 -0
- data/test/bench/startup.stack.html +25015 -0
- data/test/bench/startup/startup.flat.txt +1005 -0
- data/test/bench/startup/startup.graph.html +71958 -0
- data/test/bench/startup/startup.grind.dat +101602 -0
- data/test/bench/startup/startup.stack.html +24516 -0
- data/test/cookbooks/os_prepare/metadata.rb +1 -0
- data/test/cookbooks/os_prepare/recipes/file.rb +5 -0
- data/test/cookbooks/os_prepare/recipes/registry_key.rb +13 -0
- data/test/docker_run.rb +3 -1
- data/test/functional/inheritance_test.rb +26 -13
- data/test/helper.rb +2 -2
- data/test/integration/default/file_spec.rb +16 -0
- data/test/integration/default/powershell_spec.rb +4 -1
- data/test/integration/default/registry_key_spec.rb +47 -4
- data/test/integration/default/secpol_spec.rb +4 -1
- data/test/integration/default/wmi_spec.rb +4 -1
- data/test/unit/mock/profiles/resource-tiny/inspec.yml +10 -0
- data/test/unit/mock/profiles/resource-tiny/libraries/resource.rb +3 -0
- data/test/unit/shell_detector_test.rb +78 -0
- metadata +47 -4
- data/docs/ctl_inspec.rst +0 -247
data/inspec.gemspec
CHANGED
@@ -34,6 +34,7 @@ Gem::Specification.new do |spec|
|
|
34
34
|
spec.add_dependency 'rspec-its', '~> 1.2'
|
35
35
|
spec.add_dependency 'pry', '~> 0'
|
36
36
|
spec.add_dependency 'hashie', '~> 3.4'
|
37
|
+
spec.add_dependency 'molinillo', '~> 0.5'
|
37
38
|
|
38
39
|
spec.add_development_dependency 'mocha', '~> 1.1'
|
39
40
|
end
|
data/lib/inspec/cli.rb
CHANGED
@@ -4,12 +4,15 @@
|
|
4
4
|
# author: Dominik Richter
|
5
5
|
# author: Christoph Hartmann
|
6
6
|
|
7
|
+
require 'logger'
|
7
8
|
require 'thor'
|
8
9
|
require 'json'
|
9
10
|
require 'pp'
|
10
11
|
require 'utils/json_log'
|
11
12
|
require 'inspec/base_cli'
|
13
|
+
require 'inspec/plugins'
|
12
14
|
require 'inspec/runner_mock'
|
15
|
+
require 'inspec/env_printer'
|
13
16
|
|
14
17
|
class Inspec::InspecCLI < Inspec::BaseCLI # rubocop:disable Metrics/ClassLength
|
15
18
|
class_option :diagnose, type: :boolean,
|
@@ -152,7 +155,7 @@ class Inspec::InspecCLI < Inspec::BaseCLI # rubocop:disable Metrics/ClassLength
|
|
152
155
|
desc 'shell', 'open an interactive debugging shell'
|
153
156
|
target_options
|
154
157
|
option :command, aliases: :c
|
155
|
-
option :format, type: :string, default:
|
158
|
+
option :format, type: :string, default: nil, hide: true
|
156
159
|
def shell_func
|
157
160
|
diagnose
|
158
161
|
o = opts.dup
|
@@ -170,6 +173,12 @@ class Inspec::InspecCLI < Inspec::BaseCLI # rubocop:disable Metrics/ClassLength
|
|
170
173
|
$stderr.puts e.message
|
171
174
|
end
|
172
175
|
|
176
|
+
desc 'env', 'Output shell-appropriate completion configuration'
|
177
|
+
def env(shell = nil)
|
178
|
+
p = Inspec::EnvPrinter.new(self.class, shell)
|
179
|
+
p.print_and_exit!
|
180
|
+
end
|
181
|
+
|
173
182
|
desc 'version', 'prints the version of this tool'
|
174
183
|
def version
|
175
184
|
puts Inspec::VERSION
|
@@ -0,0 +1,45 @@
|
|
1
|
+
_inspec() {
|
2
|
+
local _inspec_top_level_commands="<%= top_level_commands.join(" ") %>"
|
3
|
+
<% subcommands_with_commands.each do |name, subcommands| -%>
|
4
|
+
local _inspec_<%= name %>_commands="<%= subcommands.join(" ") -%>"
|
5
|
+
<% end -%>
|
6
|
+
|
7
|
+
cur=${COMP_WORDS[COMP_CWORD]}
|
8
|
+
prev=${COMP_WORDS[COMP_CWORD-1]}
|
9
|
+
|
10
|
+
if [ "$COMP_CWORD" -eq 1 ]; then
|
11
|
+
case "$prev" in
|
12
|
+
inspec)
|
13
|
+
COMPREPLY=( $( compgen -W "$_inspec_top_level_commands" -- "$cur" ) )
|
14
|
+
;;
|
15
|
+
esac
|
16
|
+
elif [ "$COMP_CWORD" -eq 2 ]; then
|
17
|
+
case "$prev" in
|
18
|
+
archive|check|exec|json)
|
19
|
+
COMPREPLY=( $( compgen -f -- "$cur" ) )
|
20
|
+
;;
|
21
|
+
help)
|
22
|
+
COMPREPLY=( $( compgen -W "$_inspec_top_level_commands" -- "$cur" ) )
|
23
|
+
;;
|
24
|
+
<% subcommands_with_commands.each do |name, subcommands| -%>
|
25
|
+
<%= name %>)
|
26
|
+
COMPREPLY=( $( compgen -W "$_inspec_<%= name %>_commands" -- "$cur" ) )
|
27
|
+
;;
|
28
|
+
<% end -%>
|
29
|
+
esac
|
30
|
+
elif [ "$COMP_CWORD" -eq 3 ]; then
|
31
|
+
prev2=${COMP_WORDS[COMP_CWORD-2]}
|
32
|
+
case "$prev2-$prev" in
|
33
|
+
compliance-upload)
|
34
|
+
COMPREPLY=( $( compgen -f -- "$cur" ) )
|
35
|
+
;;
|
36
|
+
<% subcommands_with_commands.each do |name, subcommands| -%>
|
37
|
+
<%= name %>-help)
|
38
|
+
COMPREPLY=( $( compgen -W "$_inspec_<%= name %>_commands" -- "$cur" ) )
|
39
|
+
;;
|
40
|
+
<% end -%>
|
41
|
+
esac
|
42
|
+
fi
|
43
|
+
}
|
44
|
+
|
45
|
+
complete -F _inspec inspec
|
@@ -0,0 +1,61 @@
|
|
1
|
+
function _inspec() {
|
2
|
+
local curcontext="$curcontext" state line
|
3
|
+
typeset -A opt_args
|
4
|
+
|
5
|
+
local -a _top_level_commands <%= subcommands_with_commands_and_descriptions.keys.map {|i| "_#{i}_commands" }.join(' ') %>
|
6
|
+
|
7
|
+
_top_level_commands=(
|
8
|
+
<%= top_level_commands_with_descriptions.map {|i| " "*8 + "\"#{i}\"" }. join("\n") %>
|
9
|
+
)
|
10
|
+
|
11
|
+
<% subcommands_with_commands_and_descriptions.each do |name, entry| -%>
|
12
|
+
_<%= name %>_commands=(
|
13
|
+
<%= entry.map {|i| " "*8 + "\"#{i}\"" }.join("\n") %>
|
14
|
+
)
|
15
|
+
|
16
|
+
<% end -%>
|
17
|
+
_arguments '1:::->toplevel' && return 0
|
18
|
+
_arguments '2:::->subcommand' && return 0
|
19
|
+
_arguments '3:::->subsubcommand' && return 0
|
20
|
+
|
21
|
+
#
|
22
|
+
# Are you thinking? "Jeez, whoever wrote this really doesn't get
|
23
|
+
# zsh's completion system?" If so, you are correct. However, I
|
24
|
+
# have goodnews! Pull requests are accepted!
|
25
|
+
#
|
26
|
+
case $state in
|
27
|
+
toplevel)
|
28
|
+
_describe -t commands "InSpec subcommands" _top_level_commands
|
29
|
+
;;
|
30
|
+
subcommand)
|
31
|
+
case "$words[2]" in
|
32
|
+
archive|check|exec|json)
|
33
|
+
_alternative 'files:filenames:_files'
|
34
|
+
;;
|
35
|
+
help)
|
36
|
+
_describe -t commands "InSpec subcommands" _top_level_commands
|
37
|
+
;;
|
38
|
+
<% subcommands_with_commands_and_descriptions.each do |name, entry| -%>
|
39
|
+
<%= name %>)
|
40
|
+
_describe -t <%= name %>_commands "InSpec <%= name -%> subcommands" _<%= name %>_commands
|
41
|
+
;;
|
42
|
+
<% end -%>
|
43
|
+
esac
|
44
|
+
;;
|
45
|
+
subsubcommand)
|
46
|
+
case "$words[2]-$words[3]" in
|
47
|
+
compliance-upload)
|
48
|
+
_alternative 'files:filenames:_files'
|
49
|
+
;;
|
50
|
+
<% subcommands_with_commands_and_descriptions.each do |name, entry| -%>
|
51
|
+
<%= name %>-help)
|
52
|
+
_describe -t <%= name %>_commands "InSpec <%= name %> subcommands" _<%= name %>_commands
|
53
|
+
;;
|
54
|
+
<% end -%>
|
55
|
+
esac
|
56
|
+
|
57
|
+
esac
|
58
|
+
|
59
|
+
}
|
60
|
+
|
61
|
+
compdef _inspec inspec
|
@@ -0,0 +1,307 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# author: Dominik Richter
|
3
|
+
# author: Christoph Hartmann
|
4
|
+
|
5
|
+
require 'logger'
|
6
|
+
require 'fileutils'
|
7
|
+
require 'molinillo'
|
8
|
+
require 'inspec/errors'
|
9
|
+
|
10
|
+
module Inspec
|
11
|
+
class Resolver
|
12
|
+
def self.resolve(requirements, vendor_index, cwd, opts = {})
|
13
|
+
reqs = requirements.map do |req|
|
14
|
+
Requirement.from_metadata(req, cwd: cwd) ||
|
15
|
+
fail("Cannot initialize dependency: #{req}")
|
16
|
+
end
|
17
|
+
|
18
|
+
new(vendor_index, opts).resolve(reqs)
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(vendor_index, opts = {})
|
22
|
+
@logger = opts[:logger] || Logger.new(nil)
|
23
|
+
@debug_mode = false # TODO: hardcoded for now, grab from options
|
24
|
+
|
25
|
+
@vendor_index = vendor_index
|
26
|
+
@resolver = Molinillo::Resolver.new(self, self)
|
27
|
+
@search_cache = {}
|
28
|
+
end
|
29
|
+
|
30
|
+
# Resolve requirements.
|
31
|
+
#
|
32
|
+
# @param requirements [Array(Inspec::requirement)] Array of requirements
|
33
|
+
# @return [Array(String)] list of resolved dependency paths
|
34
|
+
def resolve(requirements)
|
35
|
+
requirements.each(&:pull)
|
36
|
+
@base_dep_graph = Molinillo::DependencyGraph.new
|
37
|
+
@dep_graph = @resolver.resolve(requirements, @base_dep_graph)
|
38
|
+
arr = @dep_graph.map(&:payload)
|
39
|
+
Hash[arr.map { |e| [e.name, e] }]
|
40
|
+
rescue Molinillo::VersionConflict => e
|
41
|
+
raise VersionConflict.new(e.conflicts.keys.uniq, e.message)
|
42
|
+
rescue Molinillo::CircularDependencyError => e
|
43
|
+
names = e.dependencies.sort_by(&:name).map { |d| "profile '#{d.name}'" }
|
44
|
+
raise CyclicDependencyError,
|
45
|
+
'Your profile has requirements that depend on each other, creating '\
|
46
|
+
"an infinite loop. Please remove #{names.count > 1 ? 'either ' : ''} "\
|
47
|
+
"#{names.join(' or ')} and try again."
|
48
|
+
end
|
49
|
+
|
50
|
+
# --------------------------------------------------------------------------
|
51
|
+
# SpecificationProvider
|
52
|
+
|
53
|
+
# Search for the specifications that match the given dependency.
|
54
|
+
# The specifications in the returned array will be considered in reverse
|
55
|
+
# order, so the latest version ought to be last.
|
56
|
+
# @note This method should be 'pure', i.e. the return value should depend
|
57
|
+
# only on the `dependency` parameter.
|
58
|
+
#
|
59
|
+
# @param [Object] dependency
|
60
|
+
# @return [Array<Object>] the specifications that satisfy the given
|
61
|
+
# `dependency`.
|
62
|
+
def search_for(dep)
|
63
|
+
unless dep.is_a?(Inspec::Requirement)
|
64
|
+
fail 'Internal error: Dependency resolver requires an Inspec::Requirement object for #search_for(dependency)'
|
65
|
+
end
|
66
|
+
@search_cache[dep] ||= uncached_search_for(dep)
|
67
|
+
end
|
68
|
+
|
69
|
+
def uncached_search_for(dep)
|
70
|
+
# pre-cached and specified dependencies
|
71
|
+
return [dep] unless dep.profile.nil?
|
72
|
+
|
73
|
+
results = @vendor_index.find(dep)
|
74
|
+
return [] unless results.any?
|
75
|
+
|
76
|
+
# TODO: load dep from vendor index
|
77
|
+
# vertex = @dep_graph.vertex_named(dep.name)
|
78
|
+
# locked_requirement = vertex.payload.requirement if vertex
|
79
|
+
fail NotImplementedError, "load dependency #{dep} from vendor index"
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns the dependencies of `specification`.
|
83
|
+
# @note This method should be 'pure', i.e. the return value should depend
|
84
|
+
# only on the `specification` parameter.
|
85
|
+
#
|
86
|
+
# @param [Object] specification
|
87
|
+
# @return [Array<Object>] the dependencies that are required by the given
|
88
|
+
# `specification`.
|
89
|
+
def dependencies_for(specification)
|
90
|
+
specification.profile.metadata.dependencies
|
91
|
+
end
|
92
|
+
|
93
|
+
# Determines whether the given `requirement` is satisfied by the given
|
94
|
+
# `spec`, in the context of the current `activated` dependency graph.
|
95
|
+
#
|
96
|
+
# @param [Object] requirement
|
97
|
+
# @param [DependencyGraph] activated the current dependency graph in the
|
98
|
+
# resolution process.
|
99
|
+
# @param [Object] spec
|
100
|
+
# @return [Boolean] whether `requirement` is satisfied by `spec` in the
|
101
|
+
# context of the current `activated` dependency graph.
|
102
|
+
def requirement_satisfied_by?(requirement, _activated, spec)
|
103
|
+
requirement.matches_spec?(spec) || spec.is_a?(Inspec::Profile)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns the name for the given `dependency`.
|
107
|
+
# @note This method should be 'pure', i.e. the return value should depend
|
108
|
+
# only on the `dependency` parameter.
|
109
|
+
#
|
110
|
+
# @param [Object] dependency
|
111
|
+
# @return [String] the name for the given `dependency`.
|
112
|
+
def name_for(dependency)
|
113
|
+
unless dependency.is_a?(Inspec::Requirement)
|
114
|
+
fail 'Internal error: Dependency resolver requires an Inspec::Requirement object for #name_for(dependency)'
|
115
|
+
end
|
116
|
+
dependency.name
|
117
|
+
end
|
118
|
+
|
119
|
+
# @return [String] the name of the source of explicit dependencies, i.e.
|
120
|
+
# those passed to {Resolver#resolve} directly.
|
121
|
+
def name_for_explicit_dependency_source
|
122
|
+
'inspec.yml'
|
123
|
+
end
|
124
|
+
|
125
|
+
# @return [String] the name of the source of 'locked' dependencies, i.e.
|
126
|
+
# those passed to {Resolver#resolve} directly as the `base`
|
127
|
+
def name_for_locking_dependency_source
|
128
|
+
'inspec.lock'
|
129
|
+
end
|
130
|
+
|
131
|
+
# Sort dependencies so that the ones that are easiest to resolve are first.
|
132
|
+
# Easiest to resolve is (usually) defined by:
|
133
|
+
# 1) Is this dependency already activated?
|
134
|
+
# 2) How relaxed are the requirements?
|
135
|
+
# 3) Are there any conflicts for this dependency?
|
136
|
+
# 4) How many possibilities are there to satisfy this dependency?
|
137
|
+
#
|
138
|
+
# @param [Array<Object>] dependencies
|
139
|
+
# @param [DependencyGraph] activated the current dependency graph in the
|
140
|
+
# resolution process.
|
141
|
+
# @param [{String => Array<Conflict>}] conflicts
|
142
|
+
# @return [Array<Object>] a sorted copy of `dependencies`.
|
143
|
+
def sort_dependencies(dependencies, activated, conflicts)
|
144
|
+
dependencies.sort_by do |dependency|
|
145
|
+
name = name_for(dependency)
|
146
|
+
[
|
147
|
+
activated.vertex_named(name).payload ? 0 : 1,
|
148
|
+
# amount_constrained(dependency), # TODO
|
149
|
+
conflicts[name] ? 0 : 1,
|
150
|
+
# activated.vertex_named(name).payload ? 0 : search_for(dependency).count, # TODO
|
151
|
+
]
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Returns whether this dependency, which has no possible matching
|
156
|
+
# specifications, can safely be ignored.
|
157
|
+
#
|
158
|
+
# @param [Object] dependency
|
159
|
+
# @return [Boolean] whether this dependency can safely be skipped.
|
160
|
+
def allow_missing?(_dependency)
|
161
|
+
# TODO
|
162
|
+
false
|
163
|
+
end
|
164
|
+
|
165
|
+
# --------------------------------------------------------------------------
|
166
|
+
# UI
|
167
|
+
|
168
|
+
include Molinillo::UI
|
169
|
+
|
170
|
+
# The {IO} object that should be used to print output. `STDOUT`, by default.
|
171
|
+
#
|
172
|
+
# @return [IO]
|
173
|
+
def output
|
174
|
+
self
|
175
|
+
end
|
176
|
+
|
177
|
+
def print(what = '')
|
178
|
+
@logger.info(what)
|
179
|
+
end
|
180
|
+
alias puts print
|
181
|
+
end
|
182
|
+
|
183
|
+
class Package
|
184
|
+
def initialize(path, version)
|
185
|
+
@path = path
|
186
|
+
@version = version
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
class VendorIndex
|
191
|
+
attr_reader :list, :path
|
192
|
+
def initialize(path)
|
193
|
+
@path = path
|
194
|
+
FileUtils.mkdir_p(path) unless File.directory?(path)
|
195
|
+
@list = Dir[File.join(path, '*')].map { |x| load_path(x) }
|
196
|
+
end
|
197
|
+
|
198
|
+
def find(_dependency)
|
199
|
+
# TODO
|
200
|
+
fail NotImplementedError, '#find(dependency) on VendorIndex seeks implementation.'
|
201
|
+
end
|
202
|
+
|
203
|
+
private
|
204
|
+
|
205
|
+
def load_path(_path)
|
206
|
+
# TODO
|
207
|
+
fail NotImplementedError, '#load_path(path) on VendorIndex wants to be implemented.'
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
class Requirement
|
212
|
+
attr_reader :name, :dep, :cwd, :opts
|
213
|
+
def initialize(name, dep, cwd, opts)
|
214
|
+
@name = name
|
215
|
+
@dep = Gem::Dependency.new(name, Gem::Requirement.new(Array(dep)), :runtime)
|
216
|
+
@opts = opts
|
217
|
+
@cwd = cwd
|
218
|
+
end
|
219
|
+
|
220
|
+
def matches_spec?(spec)
|
221
|
+
params = spec.profile.metadata.params
|
222
|
+
@dep.match?(params[:name], params[:version])
|
223
|
+
end
|
224
|
+
|
225
|
+
def pull
|
226
|
+
case
|
227
|
+
when @opts[:path] then pull_path(@opts[:path])
|
228
|
+
else
|
229
|
+
# TODO: should default to supermarket
|
230
|
+
fail 'You must specify the source of the dependency (for now...)'
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def path
|
235
|
+
@path || pull
|
236
|
+
end
|
237
|
+
|
238
|
+
def profile
|
239
|
+
return nil if path.nil?
|
240
|
+
@profile ||= Inspec::Profile.for_target(path, {})
|
241
|
+
end
|
242
|
+
|
243
|
+
def self.from_metadata(dep, opts)
|
244
|
+
fail 'Cannot load empty dependency.' if dep.nil? || dep.empty?
|
245
|
+
name = dep[:name] || fail('You must provide a name for all dependencies')
|
246
|
+
version = dep[:version]
|
247
|
+
new(name, version, opts[:cwd], dep)
|
248
|
+
end
|
249
|
+
|
250
|
+
def to_s
|
251
|
+
@dep.to_s
|
252
|
+
end
|
253
|
+
|
254
|
+
private
|
255
|
+
|
256
|
+
def pull_path(path)
|
257
|
+
abspath = File.absolute_path(path, @cwd)
|
258
|
+
fail "Dependency path doesn't exist: #{path}" unless File.exist?(abspath)
|
259
|
+
fail "Dependency path isn't a folder: #{path}" unless File.directory?(abspath)
|
260
|
+
@path = abspath
|
261
|
+
true
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
class SupermarketDependency
|
266
|
+
def initialize(url, requirement)
|
267
|
+
@url = url
|
268
|
+
@requirement = requirement
|
269
|
+
end
|
270
|
+
|
271
|
+
def self.load(dep)
|
272
|
+
return nil if dep.nil?
|
273
|
+
sname = dep[:supermarket]
|
274
|
+
return nil if sname.nil?
|
275
|
+
surl = dep[:supermarket_url] || 'default_url...'
|
276
|
+
requirement = dep[:version]
|
277
|
+
url = surl + '/' + sname
|
278
|
+
new(url, requirement)
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
class Dependencies
|
283
|
+
attr_reader :list, :vendor_path
|
284
|
+
|
285
|
+
# initialize
|
286
|
+
#
|
287
|
+
# @param cwd [String] current working directory for relative path includes
|
288
|
+
# @param vendor_path [String] path which contains vendored dependencies
|
289
|
+
# @return [dependencies] this
|
290
|
+
def initialize(cwd, vendor_path)
|
291
|
+
@cwd = cwd
|
292
|
+
@vendor_path = vendor_path || File.join(Dir.home, '.inspec', 'cache')
|
293
|
+
@list = nil
|
294
|
+
end
|
295
|
+
|
296
|
+
# 1. Get dependencies, pull things to a local cache if necessary
|
297
|
+
# 2. Resolve dependencies
|
298
|
+
#
|
299
|
+
# @param dependencies [Gem::Dependency] list of dependencies
|
300
|
+
# @return [nil]
|
301
|
+
def vendor(dependencies)
|
302
|
+
return if dependencies.nil? || dependencies.empty?
|
303
|
+
@vendor_index ||= VendorIndex.new(@vendor_path)
|
304
|
+
@list = Resolver.resolve(dependencies, @vendor_index, @cwd)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|