inspec 0.31.0 → 0.32.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 +56 -2
- data/Gemfile +6 -2
- data/MAINTAINERS.md +3 -1
- data/MAINTAINERS.toml +1 -1
- data/README.md +20 -1
- data/Rakefile +8 -0
- data/docs/cli.rst +18 -2
- data/docs/resources.rst +55 -3
- data/inspec.gemspec +2 -2
- data/lib/bundles/inspec-supermarket/api.rb +1 -0
- data/lib/fetchers/local.rb +12 -1
- data/lib/fetchers/tar.rb +4 -0
- data/lib/fetchers/url.rb +4 -0
- data/lib/inspec/base_cli.rb +17 -0
- data/lib/inspec/cli.rb +33 -12
- data/lib/inspec/dependencies/dependency_set.rb +50 -5
- data/lib/inspec/dependencies/lockfile.rb +94 -0
- data/lib/inspec/dependencies/requirement.rb +93 -53
- data/lib/inspec/dependencies/resolver.rb +53 -170
- data/lib/inspec/dependencies/vendor_index.rb +11 -4
- data/lib/inspec/dsl.rb +23 -15
- data/lib/inspec/errors.rb +1 -7
- data/lib/inspec/log.rb +2 -25
- data/lib/inspec/profile.rb +68 -28
- data/lib/inspec/profile_context.rb +28 -5
- data/lib/inspec/rspec_json_formatter.rb +48 -25
- data/lib/inspec/rule.rb +7 -0
- data/lib/inspec/runner.rb +26 -15
- data/lib/inspec/runner_rspec.rb +2 -6
- data/lib/inspec/shell.rb +35 -26
- data/lib/inspec/version.rb +2 -1
- data/lib/resources/host.rb +13 -6
- data/lib/resources/iis_site.rb +1 -0
- data/lib/resources/os.rb +1 -1
- data/lib/resources/package.rb +22 -6
- data/lib/resources/port.rb +1 -11
- data/lib/resources/service.rb +9 -0
- data/lib/resources/user.rb +8 -8
- metadata +14 -7
@@ -1,5 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'inspec/dependencies/vendor_index'
|
3
|
+
require 'inspec/dependencies/requirement'
|
3
4
|
require 'inspec/dependencies/resolver'
|
4
5
|
|
5
6
|
module Inspec
|
@@ -10,17 +11,60 @@ module Inspec
|
|
10
11
|
# VendorIndex and the Resolver.
|
11
12
|
#
|
12
13
|
class DependencySet
|
13
|
-
|
14
|
+
#
|
15
|
+
# Return a dependency set given a lockfile.
|
16
|
+
#
|
17
|
+
# @param lockfile [Inspec::Lockfile] A lockfile to generate the dependency set from
|
18
|
+
# @param cwd [String] Current working directory for relative path includes
|
19
|
+
# @param vendor_path [String] Path to the vendor directory
|
20
|
+
#
|
21
|
+
def self.from_lockfile(lockfile, cwd, vendor_path)
|
22
|
+
vendor_index = VendorIndex.new(vendor_path)
|
23
|
+
dep_tree = lockfile.deps.map do |dep|
|
24
|
+
Inspec::Requirement.from_lock_entry(dep, cwd, vendor_index)
|
25
|
+
end
|
14
26
|
|
27
|
+
dep_list = flatten_dep_tree(dep_tree)
|
28
|
+
new(cwd, vendor_path, dep_list)
|
29
|
+
end
|
30
|
+
|
31
|
+
# This is experimental code to test the working of the
|
32
|
+
# dependency loader - perform a proper dependency related search
|
33
|
+
# in the future.
|
34
|
+
#
|
35
|
+
# Flatten tree because that is all we know how to deal with for
|
36
|
+
# right now. Last dep seen for a given name wins right now.
|
37
|
+
def self.flatten_dep_tree(dep_tree)
|
38
|
+
dep_list = {}
|
39
|
+
dep_tree.each do |d|
|
40
|
+
dep_list[d.name] = d
|
41
|
+
dep_list.merge!(flatten_dep_tree(d.dependencies))
|
42
|
+
end
|
43
|
+
dep_list
|
44
|
+
end
|
45
|
+
|
46
|
+
attr_reader :vendor_path
|
47
|
+
attr_writer :dep_list
|
15
48
|
# initialize
|
16
49
|
#
|
17
50
|
# @param cwd [String] current working directory for relative path includes
|
18
51
|
# @param vendor_path [String] path which contains vendored dependencies
|
19
52
|
# @return [dependencies] this
|
20
|
-
def initialize(cwd, vendor_path)
|
53
|
+
def initialize(cwd, vendor_path, dep_list = nil)
|
21
54
|
@cwd = cwd
|
22
|
-
@vendor_path = vendor_path
|
23
|
-
@
|
55
|
+
@vendor_path = vendor_path
|
56
|
+
@dep_list = dep_list
|
57
|
+
end
|
58
|
+
|
59
|
+
def list
|
60
|
+
@dep_list
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_array
|
64
|
+
return [] if @dep_list.nil?
|
65
|
+
@dep_list.map do |_k, v|
|
66
|
+
v.to_hash
|
67
|
+
end.compact
|
24
68
|
end
|
25
69
|
|
26
70
|
#
|
@@ -29,10 +73,11 @@ module Inspec
|
|
29
73
|
#
|
30
74
|
# @param dependencies [Gem::Dependency] list of dependencies
|
31
75
|
# @return [nil]
|
76
|
+
#
|
32
77
|
def vendor(dependencies)
|
33
78
|
return nil if dependencies.nil? || dependencies.empty?
|
34
79
|
@vendor_index ||= VendorIndex.new(@vendor_path)
|
35
|
-
@
|
80
|
+
@dep_list = Resolver.resolve(dependencies, @vendor_index, @cwd)
|
36
81
|
end
|
37
82
|
end
|
38
83
|
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Inspec
|
5
|
+
class Lockfile
|
6
|
+
# When we finalize this feature, we should set these to 1
|
7
|
+
MINIMUM_SUPPORTED_VERSION = 0
|
8
|
+
CURRENT_LOCKFILE_VERSION = 0
|
9
|
+
|
10
|
+
def self.from_dependency_set(dep_set)
|
11
|
+
lockfile_content = {
|
12
|
+
'lockfile_version' => CURRENT_LOCKFILE_VERSION,
|
13
|
+
'depends' => dep_set.to_array,
|
14
|
+
}
|
15
|
+
new(lockfile_content)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.from_file(path)
|
19
|
+
parsed_content = YAML.load(File.read(path))
|
20
|
+
version = parsed_content['lockfile_version']
|
21
|
+
fail "No lockfile_version set in #{path}!" if version.nil?
|
22
|
+
validate_lockfile_version!(version.to_i)
|
23
|
+
new(parsed_content)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.validate_lockfile_version!(version)
|
27
|
+
if version < MINIMUM_SUPPORTED_VERSION
|
28
|
+
fail <<EOF
|
29
|
+
This lockfile specifies a lockfile_version of #{version} which is
|
30
|
+
lower than the minimum supported version #{MINIMUM_SUPPORTED_VERSION}.
|
31
|
+
|
32
|
+
Please create a new lockfile for this project by running:
|
33
|
+
|
34
|
+
inspec vendor
|
35
|
+
EOF
|
36
|
+
elsif version == 0
|
37
|
+
# Remove this case once this feature stablizes
|
38
|
+
$stderr.puts <<EOF
|
39
|
+
WARNING: This is a version 0 lockfile. Thank you for trying the
|
40
|
+
experimental dependency management feature. Please be aware you may
|
41
|
+
need to regenerate this lockfile in future versions as the feature is
|
42
|
+
currently in development.
|
43
|
+
EOF
|
44
|
+
elsif version > CURRENT_LOCKFILE_VERSION
|
45
|
+
fail <<EOF
|
46
|
+
This lockfile claims to be version #{version} which is greater than
|
47
|
+
the most recent lockfile version(#{CURRENT_LOCKFILE_VERSION}).
|
48
|
+
|
49
|
+
This may happen if you are using an older version of inspec than was
|
50
|
+
used to create the lockfile.
|
51
|
+
EOF
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
attr_reader :version, :deps
|
56
|
+
def initialize(lockfile_content_hash)
|
57
|
+
version = lockfile_content_hash['lockfile_version']
|
58
|
+
@version = version.to_i
|
59
|
+
parse_content_hash(lockfile_content_hash)
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_yaml
|
63
|
+
{
|
64
|
+
'lockfile_version' => CURRENT_LOCKFILE_VERSION,
|
65
|
+
'depends' => @deps,
|
66
|
+
}.to_yaml
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
# Refactor this to be "version-wise" - i.e. make one dispatch
|
72
|
+
# function for each version so that even if it duplicates code,
|
73
|
+
# it can describe the part of the code that it expects to be
|
74
|
+
# different. Then that dispatch routine can call more well
|
75
|
+
# defined methods like "parse_v0_dependencies" or
|
76
|
+
# "parse_flat_dependencies" or what not as things generally
|
77
|
+
# develop. It does help people easily set breakpoints/track
|
78
|
+
# different entry points of the API.
|
79
|
+
def parse_content_hash(lockfile_content_hash)
|
80
|
+
case version
|
81
|
+
when 0
|
82
|
+
parse_content_hash_0(lockfile_content_hash)
|
83
|
+
else
|
84
|
+
# If we've gotten here, there is likely a mistake in the
|
85
|
+
# lockfile version validation in the constructor.
|
86
|
+
fail "No lockfile parser for version #{version}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def parse_content_hash_0(lockfile_content_hash)
|
91
|
+
@deps = lockfile_content_hash['depends']
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -1,87 +1,134 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'inspec/fetcher'
|
3
|
+
require 'digest'
|
3
4
|
|
4
5
|
module Inspec
|
5
6
|
#
|
6
7
|
# Inspec::Requirement represents a given profile dependency, where
|
7
8
|
# appropriate we delegate to Inspec::Profile directly.
|
8
9
|
#
|
9
|
-
class Requirement
|
10
|
+
class Requirement # rubocop:disable Metrics/ClassLength
|
10
11
|
attr_reader :name, :dep, :cwd, :opts
|
12
|
+
attr_writer :dependencies
|
13
|
+
|
14
|
+
def self.from_metadata(dep, vendor_index, opts)
|
15
|
+
fail 'Cannot load empty dependency.' if dep.nil? || dep.empty?
|
16
|
+
name = dep[:name] || fail('You must provide a name for all dependencies')
|
17
|
+
version = dep[:version]
|
18
|
+
new(name, version, vendor_index, opts[:cwd], opts.merge(dep))
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.from_lock_entry(entry, cwd, vendor_index)
|
22
|
+
req = new(entry['name'],
|
23
|
+
entry['version_constraints'],
|
24
|
+
vendor_index,
|
25
|
+
cwd, { url: entry['resolved_source'] })
|
26
|
+
|
27
|
+
locked_deps = []
|
28
|
+
Array(entry['dependencies']).each do |dep_entry|
|
29
|
+
locked_deps << Inspec::Requirement.from_lock_entry(dep_entry, cwd, vendor_index)
|
30
|
+
end
|
31
|
+
|
32
|
+
req.lock_deps(locked_deps)
|
33
|
+
req
|
34
|
+
end
|
11
35
|
|
12
36
|
def initialize(name, version_constraints, vendor_index, cwd, opts)
|
13
37
|
@name = name
|
14
|
-
@
|
15
|
-
|
16
|
-
:runtime)
|
38
|
+
@version_requirement = Gem::Requirement.new(Array(version_constraints))
|
39
|
+
@dep = Gem::Dependency.new(name, @version_requirement, :runtime)
|
17
40
|
@vendor_index = vendor_index
|
18
41
|
@opts = opts
|
19
42
|
@cwd = cwd
|
20
43
|
end
|
21
44
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
45
|
+
def required_version
|
46
|
+
@version_requirement
|
47
|
+
end
|
48
|
+
|
49
|
+
def source_version
|
50
|
+
profile.metadata.params[:version]
|
51
|
+
end
|
52
|
+
|
53
|
+
def source_satisfies_spec?
|
54
|
+
name = profile.metadata.params[:name]
|
55
|
+
version = profile.metadata.params[:version]
|
56
|
+
@dep.match?(name, version)
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_hash
|
60
|
+
h = {
|
61
|
+
'name' => name,
|
62
|
+
'resolved_source' => source_url,
|
63
|
+
'version_constraints' => @version_requirement.to_s,
|
64
|
+
}
|
65
|
+
|
66
|
+
if !dependencies.empty?
|
67
|
+
h['dependencies'] = dependencies.map(&:to_hash)
|
68
|
+
end
|
69
|
+
|
70
|
+
if is_vendored?
|
71
|
+
h['content_hash'] = content_hash
|
72
|
+
end
|
73
|
+
h
|
74
|
+
end
|
75
|
+
|
76
|
+
def lock_deps(dep_array)
|
77
|
+
@dependencies = dep_array
|
78
|
+
end
|
79
|
+
|
80
|
+
def is_vendored?
|
81
|
+
@vendor_index.exists?(@name, source_url)
|
82
|
+
end
|
83
|
+
|
84
|
+
def content_hash
|
85
|
+
@content_hash ||= begin
|
86
|
+
archive_path = @vendor_index.archive_entry_for(@name, source_url)
|
87
|
+
fail "No vendored archive path for #{self}, cannot take content hash" if archive_path.nil?
|
88
|
+
Digest::SHA256.hexdigest File.read(archive_path)
|
89
|
+
end
|
25
90
|
end
|
26
91
|
|
27
92
|
def source_url
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
@opts[:url]
|
93
|
+
if opts[:path]
|
94
|
+
"file://#{File.expand_path(opts[:path], @cwd)}"
|
95
|
+
elsif opts[:url]
|
96
|
+
opts[:url]
|
33
97
|
end
|
34
98
|
end
|
35
99
|
|
36
100
|
def local_path
|
37
|
-
@local_path ||=
|
38
|
-
|
39
|
-
File.expand_path(opts[:path], @cwd)
|
101
|
+
@local_path ||= if fetcher.class == Fetchers::Local
|
102
|
+
File.expand_path(fetcher.target, @cwd)
|
40
103
|
else
|
41
104
|
@vendor_index.prefered_entry_for(@name, source_url)
|
42
105
|
end
|
43
106
|
end
|
44
107
|
|
45
|
-
def
|
46
|
-
@
|
47
|
-
|
48
|
-
|
49
|
-
:url
|
50
|
-
else
|
51
|
-
fail "Cannot determine source type from #{opts}"
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def fetcher_class
|
56
|
-
@fetcher_class ||= case source_type
|
57
|
-
when :local_path
|
58
|
-
Fetchers::Local
|
59
|
-
when :url
|
60
|
-
Fetchers::Url
|
61
|
-
else
|
62
|
-
fail "No known fetcher for dependency #{name} with source_type #{source_type}"
|
63
|
-
end
|
64
|
-
|
65
|
-
fail "No fetcher for #{name} (options: #{opts})" if @fetcher_class.nil?
|
66
|
-
@fetcher_class
|
108
|
+
def fetcher
|
109
|
+
@fetcher ||= Inspec::Fetcher.resolve(source_url)
|
110
|
+
fail "No fetcher for #{name} (options: #{opts})" if @fetcher.nil?
|
111
|
+
@fetcher
|
67
112
|
end
|
68
113
|
|
69
114
|
def pull
|
70
|
-
|
71
|
-
|
115
|
+
# TODO(ssd): Dispatch on the class here is gross. Seems like
|
116
|
+
# Fetcher is missing an API we want.
|
117
|
+
if fetcher.class == Fetchers::Local || @vendor_index.exists?(@name, source_url)
|
72
118
|
local_path
|
73
119
|
else
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
120
|
+
@vendor_index.add(@name, source_url, fetcher.archive_path)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def dependencies
|
125
|
+
@dependencies ||= profile.metadata.dependencies.map do |r|
|
126
|
+
Inspec::Requirement.from_metadata(r, @vendor_index, cwd: @cwd)
|
80
127
|
end
|
81
128
|
end
|
82
129
|
|
83
130
|
def to_s
|
84
|
-
dep
|
131
|
+
"#{dep} (#{source_url})"
|
85
132
|
end
|
86
133
|
|
87
134
|
def path
|
@@ -92,12 +139,5 @@ module Inspec
|
|
92
139
|
return nil if path.nil?
|
93
140
|
@profile ||= Inspec::Profile.for_target(path, {})
|
94
141
|
end
|
95
|
-
|
96
|
-
def self.from_metadata(dep, vendor_index, opts)
|
97
|
-
fail 'Cannot load empty dependency.' if dep.nil? || dep.empty?
|
98
|
-
name = dep[:name] || fail('You must provide a name for all dependencies')
|
99
|
-
version = dep[:version]
|
100
|
-
new(name, version, vendor_index, opts[:cwd], opts.merge(dep))
|
101
|
-
end
|
102
142
|
end
|
103
143
|
end
|
@@ -1,188 +1,71 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
# author:
|
3
|
-
|
4
|
-
require 'logger'
|
5
|
-
require 'molinillo'
|
2
|
+
# author: Steven Danna <steve@chef.io>
|
3
|
+
require 'inspec/log'
|
6
4
|
require 'inspec/errors'
|
7
|
-
require 'inspec/dependencies/requirement'
|
8
5
|
|
9
6
|
module Inspec
|
10
7
|
#
|
11
|
-
# Inspec::Resolver is
|
12
|
-
#
|
8
|
+
# Inspec::Resolver is a simple dependency resolver. Unlike Bundler
|
9
|
+
# or Berkshelf, it does not attempt to resolve each named dependency
|
10
|
+
# to a single version. Rather, it traverses down the dependency tree
|
11
|
+
# and:
|
12
|
+
#
|
13
|
+
# - Fetches the dependency from the source
|
14
|
+
# - Checks the presence of cycles, and
|
15
|
+
# - Checks that the specified dependency source satisfies the
|
16
|
+
# specified version constraint
|
17
|
+
#
|
18
|
+
# The full dependency tree is then available for the loader, which
|
19
|
+
# will provide the isolation necessary to support multiple versions
|
20
|
+
# of the same profile being used at runtime.
|
21
|
+
#
|
22
|
+
# Currently the fetching happens somewhat lazily depending on the
|
23
|
+
# implementation of the fetcher being used.
|
13
24
|
#
|
14
25
|
class Resolver
|
15
|
-
def self.resolve(
|
16
|
-
reqs =
|
17
|
-
req = Inspec::Requirement.from_metadata(
|
26
|
+
def self.resolve(dependencies, vendor_index, working_dir)
|
27
|
+
reqs = dependencies.map do |dep|
|
28
|
+
req = Inspec::Requirement.from_metadata(dep, vendor_index, cwd: working_dir)
|
18
29
|
req || fail("Cannot initialize dependency: #{req}")
|
19
30
|
end
|
20
|
-
|
21
|
-
new(vendor_index, opts.merge(cwd: cwd)).resolve(reqs)
|
22
|
-
end
|
23
|
-
|
24
|
-
def initialize(vendor_index, opts = {})
|
25
|
-
@logger = opts[:logger] || Logger.new(nil)
|
26
|
-
@debug_mode = false
|
27
|
-
|
28
|
-
@vendor_index = vendor_index
|
29
|
-
@cwd = opts[:cwd] || './'
|
30
|
-
@resolver = Molinillo::Resolver.new(self, self)
|
31
|
-
@search_cache = {}
|
32
|
-
end
|
33
|
-
|
34
|
-
# Resolve requirements.
|
35
|
-
#
|
36
|
-
# @param requirements [Array(Inspec::requirement)] Array of requirements
|
37
|
-
# @return [Array(String)] list of resolved dependency paths
|
38
|
-
def resolve(requirements)
|
39
|
-
requirements.each(&:pull)
|
40
|
-
@base_dep_graph = Molinillo::DependencyGraph.new
|
41
|
-
@dep_graph = @resolver.resolve(requirements, @base_dep_graph)
|
42
|
-
arr = @dep_graph.map(&:payload)
|
43
|
-
Hash[arr.map { |e| [e.name, e] }]
|
44
|
-
rescue Molinillo::VersionConflict => e
|
45
|
-
raise VersionConflict.new(e.conflicts.keys.uniq, e.message)
|
46
|
-
rescue Molinillo::CircularDependencyError => e
|
47
|
-
names = e.dependencies.sort_by(&:name).map { |d| "profile '#{d.name}'" }
|
48
|
-
raise CyclicDependencyError,
|
49
|
-
'Your profile has requirements that depend on each other, creating '\
|
50
|
-
"an infinite loop. Please remove #{names.count > 1 ? 'either ' : ''} "\
|
51
|
-
"#{names.join(' or ')} and try again."
|
52
|
-
end
|
53
|
-
|
54
|
-
# --------------------------------------------------------------------------
|
55
|
-
# SpecificationProvider
|
56
|
-
|
57
|
-
# Search for the specifications that match the given dependency.
|
58
|
-
# The specifications in the returned array will be considered in reverse
|
59
|
-
# order, so the latest version ought to be last.
|
60
|
-
# @note This method should be 'pure', i.e. the return value should depend
|
61
|
-
# only on the `dependency` parameter.
|
62
|
-
#
|
63
|
-
# @param [Object] dependency
|
64
|
-
# @return [Array<Object>] the specifications that satisfy the given
|
65
|
-
# `dependency`.
|
66
|
-
def search_for(dep)
|
67
|
-
unless dep.is_a?(Inspec::Requirement)
|
68
|
-
fail 'Internal error: Dependency resolver requires an Inspec::Requirement object for #search_for(dependency)'
|
69
|
-
end
|
70
|
-
@search_cache[dep] ||= uncached_search_for(dep)
|
71
|
-
end
|
72
|
-
|
73
|
-
def uncached_search_for(dep)
|
74
|
-
# pre-cached and specified dependencies
|
75
|
-
return [dep] unless dep.profile.nil?
|
76
|
-
|
77
|
-
results = @vendor_index.find(dep)
|
78
|
-
return [] unless results.any?
|
79
|
-
|
80
|
-
# TODO: load dep from vendor index
|
81
|
-
# vertex = @dep_graph.vertex_named(dep.name)
|
82
|
-
# locked_requirement = vertex.payload.requirement if vertex
|
83
|
-
fail NotImplementedError, "load dependency #{dep} from vendor index"
|
84
|
-
end
|
85
|
-
|
86
|
-
# Returns the dependencies of `specification`.
|
87
|
-
# @note This method should be 'pure', i.e. the return value should depend
|
88
|
-
# only on the `specification` parameter.
|
89
|
-
#
|
90
|
-
# @param [Object] specification
|
91
|
-
# @return [Array<Object>] the dependencies that are required by the given
|
92
|
-
# `specification`.
|
93
|
-
def dependencies_for(specification)
|
94
|
-
specification.profile.metadata.dependencies.map do |r|
|
95
|
-
Inspec::Requirement.from_metadata(r, @vendor_index, cwd: @cwd)
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
# Determines whether the given `requirement` is satisfied by the given
|
100
|
-
# `spec`, in the context of the current `activated` dependency graph.
|
101
|
-
#
|
102
|
-
# @param [Object] requirement
|
103
|
-
# @param [DependencyGraph] activated the current dependency graph in the
|
104
|
-
# resolution process.
|
105
|
-
# @param [Object] spec
|
106
|
-
# @return [Boolean] whether `requirement` is satisfied by `spec` in the
|
107
|
-
# context of the current `activated` dependency graph.
|
108
|
-
def requirement_satisfied_by?(requirement, _activated, spec)
|
109
|
-
requirement.matches_spec?(spec) || spec.is_a?(Inspec::Profile)
|
31
|
+
new.resolve(reqs)
|
110
32
|
end
|
111
33
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
def name_for(dependency)
|
119
|
-
unless dependency.is_a?(Inspec::Requirement)
|
120
|
-
fail 'Internal error: Dependency resolver requires an Inspec::Requirement object for #name_for(dependency)'
|
34
|
+
def resolve(deps, top_level = true, seen_items = {}, path_string = '')
|
35
|
+
graph = {}
|
36
|
+
if top_level
|
37
|
+
Inspec::Log.debug("Starting traversal of dependencies #{deps.map(&:name)}")
|
38
|
+
else
|
39
|
+
Inspec::Log.debug("Traversing dependency tree of transitive dependency #{deps.map(&:name)}")
|
121
40
|
end
|
122
|
-
dependency.name
|
123
|
-
end
|
124
|
-
|
125
|
-
# @return [String] the name of the source of explicit dependencies, i.e.
|
126
|
-
# those passed to {Resolver#resolve} directly.
|
127
|
-
def name_for_explicit_dependency_source
|
128
|
-
'inspec.yml'
|
129
|
-
end
|
130
41
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
# amount_constrained(dependency), # TODO
|
155
|
-
conflicts[name] ? 0 : 1,
|
156
|
-
# activated.vertex_named(name).payload ? 0 : search_for(dependency).count, # TODO
|
157
|
-
]
|
42
|
+
deps.each do |dep|
|
43
|
+
path_string = if path_string.empty?
|
44
|
+
dep.name
|
45
|
+
else
|
46
|
+
path_string + " -> #{dep.name}"
|
47
|
+
end
|
48
|
+
|
49
|
+
if seen_items.key?(dep.source_url)
|
50
|
+
fail Inspec::CyclicDependencyError, "Dependency #{dep} would cause a dependency cycle (#{path_string})"
|
51
|
+
else
|
52
|
+
seen_items[dep.source_url] = true
|
53
|
+
end
|
54
|
+
|
55
|
+
if !dep.source_satisfies_spec?
|
56
|
+
fail Inspec::UnsatisfiedVersionSpecification, "The profile #{dep.name} from #{dep.source_url} has a version #{dep.source_version} which doesn't match #{dep.required_version}"
|
57
|
+
end
|
58
|
+
|
59
|
+
Inspec::Log.debug("Adding #{dep.source_url}")
|
60
|
+
graph[dep.name] = dep
|
61
|
+
if !dep.dependencies.empty?
|
62
|
+
# Recursively resolve any transitive dependencies.
|
63
|
+
resolve(dep.dependencies, false, seen_items.dup, path_string)
|
64
|
+
end
|
158
65
|
end
|
159
|
-
end
|
160
|
-
|
161
|
-
# Returns whether this dependency, which has no possible matching
|
162
|
-
# specifications, can safely be ignored.
|
163
|
-
#
|
164
|
-
# @param [Object] dependency
|
165
|
-
# @return [Boolean] whether this dependency can safely be skipped.
|
166
|
-
def allow_missing?(_dependency)
|
167
|
-
# TODO
|
168
|
-
false
|
169
|
-
end
|
170
|
-
|
171
|
-
# --------------------------------------------------------------------------
|
172
|
-
# UI
|
173
|
-
|
174
|
-
include Molinillo::UI
|
175
|
-
|
176
|
-
# The {IO} object that should be used to print output. `STDOUT`, by default.
|
177
|
-
#
|
178
|
-
# @return [IO]
|
179
|
-
def output
|
180
|
-
self
|
181
|
-
end
|
182
66
|
|
183
|
-
|
184
|
-
|
67
|
+
Inspec::Log.debug('Dependency traversal complete.') if top_level
|
68
|
+
graph
|
185
69
|
end
|
186
|
-
alias puts print
|
187
70
|
end
|
188
71
|
end
|