inspec 0.34.1 → 1.0.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.
Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +323 -6
  3. data/Gemfile +10 -0
  4. data/README.md +1 -1
  5. data/Rakefile +4 -55
  6. data/docs/.gitignore +2 -0
  7. data/docs/README.md +40 -0
  8. data/docs/cli.md +342 -0
  9. data/docs/dsl_inspec.md +245 -0
  10. data/docs/dsl_resource.md +93 -0
  11. data/docs/inspec_and_friends.md +105 -0
  12. data/docs/matchers.md +136 -0
  13. data/docs/plugin_kitchen_inspec.html.md +55 -0
  14. data/docs/profiles.md +285 -0
  15. data/docs/resources/apache_conf.md.erb +75 -0
  16. data/docs/resources/apt.md.erb +84 -0
  17. data/docs/resources/audit_policy.md.erb +61 -0
  18. data/docs/resources/auditd_conf.md.erb +79 -0
  19. data/docs/resources/auditd_rules.md.erb +132 -0
  20. data/docs/resources/bash.md.erb +84 -0
  21. data/docs/resources/bond.md.erb +97 -0
  22. data/docs/resources/bridge.md.erb +67 -0
  23. data/docs/resources/bsd_service.md.erb +76 -0
  24. data/docs/resources/command.md.erb +151 -0
  25. data/docs/resources/csv.md.erb +62 -0
  26. data/docs/resources/directory.md.erb +43 -0
  27. data/docs/resources/etc_group.md.erb +116 -0
  28. data/docs/resources/etc_passwd.md.erb +155 -0
  29. data/docs/resources/etc_shadow.md.erb +149 -0
  30. data/docs/resources/file.md.erb +460 -0
  31. data/docs/resources/gem.md.erb +73 -0
  32. data/docs/resources/group.md.erb +74 -0
  33. data/docs/resources/grub_conf.md.erb +115 -0
  34. data/docs/resources/host.md.erb +85 -0
  35. data/docs/resources/iis_site.md.erb +142 -0
  36. data/docs/resources/inetd_conf.md.erb +99 -0
  37. data/docs/resources/ini.md.erb +69 -0
  38. data/docs/resources/interface.md.erb +66 -0
  39. data/docs/resources/iptables.md.erb +70 -0
  40. data/docs/resources/json.md.erb +76 -0
  41. data/docs/resources/kernel_module.md.erb +60 -0
  42. data/docs/resources/kernel_parameter.md.erb +72 -0
  43. data/docs/resources/launchd_service.md.erb +76 -0
  44. data/docs/resources/limits_conf.md.erb +80 -0
  45. data/docs/resources/login_def.md.erb +77 -0
  46. data/docs/resources/mount.md.erb +83 -0
  47. data/docs/resources/mysql_conf.md.erb +102 -0
  48. data/docs/resources/mysql_session.md.erb +63 -0
  49. data/docs/resources/npm.md.erb +75 -0
  50. data/docs/resources/ntp_conf.md.erb +76 -0
  51. data/docs/resources/oneget.md.erb +67 -0
  52. data/docs/resources/os.md.erb +154 -0
  53. data/docs/resources/os_env.md.erb +98 -0
  54. data/docs/resources/package.md.erb +115 -0
  55. data/docs/resources/parse_config.md.erb +122 -0
  56. data/docs/resources/parse_config_file.md.erb +143 -0
  57. data/docs/resources/pip.md.erb +74 -0
  58. data/docs/resources/port.md.erb +150 -0
  59. data/docs/resources/postgres_conf.md.erb +90 -0
  60. data/docs/resources/postgres_session.md.erb +75 -0
  61. data/docs/resources/powershell.md.erb +116 -0
  62. data/docs/resources/process.md.erb +73 -0
  63. data/docs/resources/registry_key.md.erb +149 -0
  64. data/docs/resources/runit_service.md.erb +76 -0
  65. data/docs/resources/security_policy.md.erb +61 -0
  66. data/docs/resources/service.md.erb +135 -0
  67. data/docs/resources/ssh_config.md.erb +94 -0
  68. data/docs/resources/sshd_config.md.erb +97 -0
  69. data/docs/resources/ssl.md.erb +133 -0
  70. data/docs/resources/sys_info.md.erb +55 -0
  71. data/docs/resources/systemd_service.md.erb +76 -0
  72. data/docs/resources/sysv_service.md.erb +76 -0
  73. data/docs/resources/upstart_service.md.erb +76 -0
  74. data/docs/resources/user.md.erb +154 -0
  75. data/docs/resources/users.md.erb +140 -0
  76. data/docs/resources/vbscript.md.erb +69 -0
  77. data/docs/resources/windows_feature.md.erb +61 -0
  78. data/docs/resources/wmi.md.erb +95 -0
  79. data/docs/resources/xinetd_conf.md.erb +170 -0
  80. data/docs/resources/yaml.md.erb +69 -0
  81. data/docs/resources/yum.md.erb +103 -0
  82. data/docs/ruby_usage.md +154 -0
  83. data/docs/shared/matcher_be.md.erb +1 -0
  84. data/docs/shared/matcher_cmp.md.erb +45 -0
  85. data/docs/shared/matcher_eq.md.erb +3 -0
  86. data/docs/shared/matcher_include.md.erb +1 -0
  87. data/docs/shared/matcher_match.md.erb +1 -0
  88. data/docs/shell.md +150 -0
  89. data/examples/meta-profile/inspec.yml +2 -4
  90. data/inspec.gemspec +1 -1
  91. data/lib/bundles/inspec-compliance/api.rb +28 -18
  92. data/lib/bundles/inspec-compliance/cli.rb +19 -27
  93. data/lib/bundles/inspec-compliance/target.rb +15 -2
  94. data/lib/fetchers/git.rb +4 -0
  95. data/lib/fetchers/local.rb +16 -1
  96. data/lib/fetchers/mock.rb +4 -0
  97. data/lib/fetchers/url.rb +46 -20
  98. data/lib/inspec/base_cli.rb +14 -0
  99. data/lib/inspec/cached_fetcher.rb +67 -0
  100. data/lib/inspec/cli.rb +22 -8
  101. data/lib/inspec/control_eval_context.rb +12 -2
  102. data/lib/inspec/dependencies/{vendor_index.rb → cache.rb} +5 -4
  103. data/lib/inspec/dependencies/dependency_set.rb +8 -14
  104. data/lib/inspec/dependencies/lockfile.rb +5 -13
  105. data/lib/inspec/dependencies/requirement.rb +31 -41
  106. data/lib/inspec/dependencies/resolver.rb +3 -3
  107. data/lib/inspec/dsl.rb +16 -4
  108. data/lib/inspec/errors.rb +1 -0
  109. data/lib/inspec/fetcher.rb +25 -1
  110. data/lib/inspec/objects/each_loop.rb +5 -2
  111. data/lib/inspec/objects/test.rb +8 -2
  112. data/lib/inspec/plugins/fetcher.rb +13 -12
  113. data/lib/inspec/plugins/resource.rb +3 -0
  114. data/lib/inspec/profile.rb +62 -26
  115. data/lib/inspec/profile_context.rb +44 -8
  116. data/lib/inspec/resource.rb +18 -3
  117. data/lib/inspec/rspec_json_formatter.rb +91 -40
  118. data/lib/inspec/rule.rb +45 -2
  119. data/lib/inspec/runner.rb +51 -39
  120. data/lib/inspec/shell.rb +5 -16
  121. data/lib/inspec/version.rb +1 -1
  122. data/lib/resources/apache_conf.rb +1 -1
  123. data/lib/resources/gem.rb +1 -0
  124. data/lib/resources/groups.rb +190 -0
  125. data/lib/resources/oneget.rb +1 -0
  126. data/lib/resources/os.rb +1 -1
  127. data/lib/resources/package.rb +3 -1
  128. data/lib/resources/parse_config.rb +18 -1
  129. data/lib/resources/pip.rb +1 -1
  130. data/lib/resources/security_policy.rb +54 -0
  131. data/lib/resources/ssl.rb +9 -11
  132. data/lib/resources/sys_info.rb +1 -1
  133. data/lib/resources/users.rb +81 -64
  134. metadata +91 -17
  135. data/docs/cli.rst +0 -442
  136. data/docs/dsl_inspec.rst +0 -259
  137. data/docs/dsl_resource.rst +0 -90
  138. data/docs/inspec_and_friends.rst +0 -85
  139. data/docs/matchers.rst +0 -137
  140. data/docs/profiles.rst +0 -169
  141. data/docs/readme.rst +0 -105
  142. data/docs/resources.rst +0 -4836
  143. data/docs/ruby_usage.rst +0 -145
  144. data/docs/shell.rst +0 -130
  145. data/docs/template.rst +0 -51
  146. data/lib/resources/group.rb +0 -137
data/lib/fetchers/url.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  # author: Christoph Hartmann
4
4
 
5
5
  require 'uri'
6
+ require 'digest'
6
7
  require 'tempfile'
7
8
  require 'open-uri'
8
9
 
@@ -81,43 +82,68 @@ module Fetchers
81
82
  end
82
83
 
83
84
  def fetch(path)
84
- Inspec::Log.debug("Fetching URL: #{@target}")
85
- @archive_path = download_archive(path)
85
+ @archive_path ||= download_archive(path)
86
86
  end
87
87
 
88
88
  def resolved_source
89
- { url: @target }
89
+ @resolved_source ||= { url: @target, sha256: sha256 }
90
90
  end
91
91
 
92
- private
92
+ def cache_key
93
+ @archive_shasum ||= sha256
94
+ end
93
95
 
94
- # download url into archive using opts,
95
- # returns File object and content-type from HTTP headers
96
- def download_archive(path)
97
- http_opts = {}
98
- http_opts['ssl_verify_mode'.to_sym] = OpenSSL::SSL::VERIFY_NONE if @insecure
99
- http_opts['Authorization'] = "Bearer #{@token}" if @token
96
+ def to_s
97
+ @target
98
+ end
100
99
 
101
- remote = open(@target, http_opts)
100
+ private
102
101
 
102
+ def sha256
103
+ file = @archive_path || temp_archive_path
104
+ Digest::SHA256.hexdigest File.read(file)
105
+ end
106
+
107
+ def file_type_from_remote(remote)
103
108
  content_type = remote.meta['content-type']
104
- file_type = MIME_TYPES[content_type] ||
105
- throw(RuntimeError, 'Failed to resolve URL target, its '\
106
- "metadata did not match ZIP or TAR: #{content_type}")
109
+ file_type = MIME_TYPES[content_type]
107
110
 
108
- # fall back to tar
109
111
  if file_type.nil?
110
- fail "Could not determine file type for content type #{content_type}."
112
+ Inspec::Log.warn("Unrecognized content type: #{content_type}. Assuming tar.gz")
113
+ file_type = '.tar.gz'
111
114
  end
112
- final_path = "#{path}#{file_type}"
113
- # download content
114
- archive = Tempfile.new(['inspec-dl-', file_type])
115
+
116
+ file_type
117
+ end
118
+
119
+ def temp_archive_path
120
+ @temp_archive_path ||= download_archive_to_temp
121
+ end
122
+
123
+ # Downloads archive to temporary file with side effect :( of setting @archive_type
124
+ def download_archive_to_temp
125
+ return @temp_archive_path if ! @temp_archive_path.nil?
126
+ Inspec::Log.debug("Fetching URL: #{@target}")
127
+ http_opts = {}
128
+ http_opts['ssl_verify_mode'.to_sym] = OpenSSL::SSL::VERIFY_NONE if @insecure
129
+ http_opts['Authorization'] = "Bearer #{@token}" if @token
130
+ remote = open(@target, http_opts)
131
+ @archive_type = file_type_from_remote(remote) # side effect :(
132
+ archive = Tempfile.new(['inspec-dl-', @archive_type])
115
133
  archive.binmode
116
134
  archive.write(remote.read)
117
135
  archive.rewind
118
136
  archive.close
119
- FileUtils.mv(archive.path, final_path)
137
+ Inspec::Log.debug("Archive stored at temporary location: #{archive.path}")
138
+ @temp_archive_path = archive.path
139
+ end
140
+
141
+ def download_archive(path)
142
+ download_archive_to_temp
143
+ final_path = "#{path}#{@archive_type}"
144
+ FileUtils.mv(temp_archive_path, final_path)
120
145
  Inspec::Log.debug("Fetched archive moved to: #{final_path}")
146
+ @temp_archive_path = nil
121
147
  final_path
122
148
  end
123
149
  end
@@ -58,6 +58,10 @@ module Inspec
58
58
  desc: 'Use colors in output.'
59
59
  option :attrs, type: :array,
60
60
  desc: 'Load attributes file (experimental)'
61
+ option :cache, type: :string,
62
+ desc: 'Use the given path for caching dependencies. (default: ~/.inspec/cache)'
63
+ option :create_lockfile, type: :boolean, default: true,
64
+ desc: 'Write out a lockfile based on this execution (unless one already exists)'
61
65
  end
62
66
 
63
67
  private
@@ -128,6 +132,16 @@ module Inspec
128
132
  Logger.const_get(l.upcase)
129
133
  end
130
134
 
135
+ def pretty_handle_exception(exception)
136
+ case exception
137
+ when Inspec::Error
138
+ $stderr.puts exception.message
139
+ exit(1)
140
+ else
141
+ raise exception # rubocop:disable Style/SignalException
142
+ end
143
+ end
144
+
131
145
  def configure_logger(o)
132
146
  #
133
147
  # TODO(ssd): This is a big gross, but this configures the
@@ -0,0 +1,67 @@
1
+ # encoding: utf-8
2
+ require 'inspec/fetcher'
3
+ require 'forwardable'
4
+
5
+ module Inspec
6
+ class CachedFetcher
7
+ extend Forwardable
8
+
9
+ attr_reader :cache, :target, :fetcher
10
+ def initialize(target, cache)
11
+ @target = target
12
+ @fetcher = Inspec::Fetcher.resolve(target)
13
+
14
+ if @fetcher.nil?
15
+ fail("Could not fetch inspec profile in #{target.inspect}.")
16
+ end
17
+
18
+ @cache = cache
19
+ end
20
+
21
+ def resolved_source
22
+ fetch
23
+ @fetcher.resolved_source
24
+ end
25
+
26
+ def cache_key
27
+ k = if target.is_a?(Hash)
28
+ target[:sha256] || target[:ref]
29
+ end
30
+
31
+ if k.nil?
32
+ fetcher.cache_key
33
+ else
34
+ k
35
+ end
36
+ end
37
+
38
+ def fetch
39
+ if cache.exists?(cache_key)
40
+ Inspec::Log.debug "Using cached dependency for #{target}"
41
+ [cache.prefered_entry_for(cache_key), false]
42
+ else
43
+ Inspec::Log.debug "Dependency does not exist in the cache #{target}"
44
+ fetcher.fetch(cache.base_path_for(fetcher.cache_key))
45
+ assert_cache_sanity!
46
+ [fetcher.archive_path, fetcher.writable?]
47
+ end
48
+ end
49
+
50
+ def assert_cache_sanity!
51
+ if target.respond_to?(:key?) && target.key?(:sha256)
52
+ if fetcher.resolved_source[:sha256] != target[:sha256]
53
+ fail <<EOF
54
+ The remote source #{fetcher} no longer has the requested content:
55
+
56
+ Request Content Hash: #{target[:sha256]}
57
+ Actual Content Hash: #{fetcher.resolved_source[:sha256]}
58
+
59
+ For URL, supermarket, compliance, and other sources that do not
60
+ provide versioned artifacts, this likely means that the remote source
61
+ has changed since your lockfile was generated.
62
+ EOF
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
data/lib/inspec/cli.rb CHANGED
@@ -48,6 +48,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI # rubocop:disable Metrics/ClassLength
48
48
  fdst = File.expand_path(dst)
49
49
  File.write(fdst, JSON.dump(profile.info))
50
50
  end
51
+ rescue StandardError => e
52
+ pretty_handle_exception(e)
51
53
  end
52
54
 
53
55
  desc 'check PATH', 'verify all tests at the specified PATH'
@@ -97,14 +99,18 @@ class Inspec::InspecCLI < Inspec::BaseCLI # rubocop:disable Metrics/ClassLength
97
99
  end
98
100
  end
99
101
  exit 1 unless result[:summary][:valid]
102
+ rescue StandardError => e
103
+ pretty_handle_exception(e)
100
104
  end
101
105
 
102
106
  desc 'vendor', 'Download all dependencies and generate a lockfile'
103
107
  def vendor(path = nil)
104
108
  configure_logger(opts)
105
- profile = Inspec::Profile.for_target('./', opts)
106
- lockfile = profile.generate_lockfile(path)
109
+ profile = Inspec::Profile.for_target('./', opts.merge(cache: Inspec::Cache.new(path)))
110
+ lockfile = profile.generate_lockfile
107
111
  File.write('inspec.lock', lockfile.to_yaml)
112
+ rescue StandardError => e
113
+ pretty_handle_exception(e)
108
114
  end
109
115
 
110
116
  desc 'archive PATH', 'archive a profile to tar.gz (default) or zip'
@@ -136,6 +142,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI # rubocop:disable Metrics/ClassLength
136
142
 
137
143
  # generate archive
138
144
  exit 1 unless profile.archive(opts)
145
+ rescue StandardError => e
146
+ pretty_handle_exception(e)
139
147
  end
140
148
 
141
149
  desc 'exec PATHS', 'run all test files at the specified PATH.'
@@ -147,6 +155,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI # rubocop:disable Metrics/ClassLength
147
155
 
148
156
  # run tests
149
157
  run_tests(targets, o)
158
+ rescue StandardError => e
159
+ pretty_handle_exception(e)
150
160
  end
151
161
 
152
162
  desc 'detect', 'detect the target OS'
@@ -165,6 +175,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI # rubocop:disable Metrics/ClassLength
165
175
  mark_text(res[item.to_sym]))
166
176
  }
167
177
  end
178
+ rescue StandardError => e
179
+ pretty_handle_exception(e)
168
180
  end
169
181
 
170
182
  desc 'shell', 'open an interactive debugging shell'
@@ -196,12 +208,16 @@ class Inspec::InspecCLI < Inspec::BaseCLI # rubocop:disable Metrics/ClassLength
196
208
  exit 0
197
209
  rescue RuntimeError, Train::UserError => e
198
210
  $stderr.puts e.message
211
+ rescue StandardError => e
212
+ pretty_handle_exception(e)
199
213
  end
200
214
 
201
215
  desc 'env', 'Output shell-appropriate completion configuration'
202
216
  def env(shell = nil)
203
217
  p = Inspec::EnvPrinter.new(self.class, shell)
204
218
  p.print_and_exit!
219
+ rescue StandardError => e
220
+ pretty_handle_exception(e)
205
221
  end
206
222
 
207
223
  desc 'version', 'prints the version of this tool'
@@ -213,13 +229,11 @@ class Inspec::InspecCLI < Inspec::BaseCLI # rubocop:disable Metrics/ClassLength
213
229
 
214
230
  def run_command(opts)
215
231
  runner = Inspec::Runner.new(opts)
216
- ctx = runner.create_context(opts)
217
- res = ctx.load(opts[:command])
232
+ res = runner.eval_with_virtual_profile(opts[:command])
233
+ runner.load
218
234
 
219
- return :ruby_eval, res if ctx.rules.empty?
220
-
221
- runner.register_rules(ctx)
222
- return :rspec_run, runner.run # rubocop:disable Style/RedundantReturn
235
+ return :ruby_eval, res if runner.all_rules.empty?
236
+ return :rspec_run, runner.run_tests # rubocop:disable Style/RedundantReturn
223
237
  end
224
238
  end
225
239
 
@@ -23,7 +23,7 @@ module Inspec
23
23
  require 'rspec/core/dsl'
24
24
  Class.new(Inspec::Rule) do
25
25
  include RSpec::Core::DSL
26
- include resources_dsl
26
+ with_resource_dsl resources_dsl
27
27
  end
28
28
  end
29
29
 
@@ -92,6 +92,14 @@ module Inspec
92
92
  res
93
93
  end
94
94
 
95
+ define_method :add_resource do |name, new_res|
96
+ resources_dsl.module_exec do
97
+ define_method name.to_sym do |*args|
98
+ new_res.new(@backend, name.to_s, *args)
99
+ end
100
+ end
101
+ end
102
+
95
103
  define_method :add_resources do |context|
96
104
  self.class.class_eval do
97
105
  include context.to_resources_dsl
@@ -107,7 +115,9 @@ module Inspec
107
115
  end
108
116
 
109
117
  define_method :register_control do |control, &block|
110
- ::Inspec::Rule.set_skip_rule(control, true) if @skip_profile
118
+ if @skip_profile || !profile_context_owner.profile_supports_os?
119
+ ::Inspec::Rule.set_skip_rule(control, true)
120
+ end
111
121
 
112
122
  profile_context_owner.register_rule(control, &block) unless control.nil?
113
123
  end
@@ -4,7 +4,7 @@ require 'fileutils'
4
4
 
5
5
  module Inspec
6
6
  #
7
- # VendorIndex manages an on-disk cache of inspec profiles. The
7
+ # Inspec::Cache manages an on-disk cache of inspec profiles. The
8
8
  # cache can contain:
9
9
  #
10
10
  # - .tar.gz profile archives
@@ -16,7 +16,7 @@ module Inspec
16
16
  # sources.
17
17
  #
18
18
  #
19
- class VendorIndex
19
+ class Cache
20
20
  attr_reader :path
21
21
  def initialize(path = nil)
22
22
  @path = path || File.join(Dir.home, '.inspec', 'cache')
@@ -43,19 +43,20 @@ module Inspec
43
43
 
44
44
  #
45
45
  # For a given name and source_url, return true if the
46
- # profile exists in the VendorIndex.
46
+ # profile exists in the Cache.
47
47
  #
48
48
  # @param [String] name
49
49
  # @param [String] source_url
50
50
  # @return [Boolean]
51
51
  #
52
52
  def exists?(key)
53
+ return false if key.nil? || key.empty?
53
54
  path = base_path_for(key)
54
55
  File.directory?(path) || File.exist?("#{path}.tar.gz") || File.exist?("#{path}.zip")
55
56
  end
56
57
 
57
58
  #
58
- # Return the path to given profile in the vendor index.
59
+ # Return the path to given profile in the cache.
59
60
  #
60
61
  # The `source_url` parameter should be a URI-like string that
61
62
  # fully specifies the source of the exact version we want to pull
@@ -1,5 +1,4 @@
1
1
  # encoding: utf-8
2
- require 'inspec/dependencies/vendor_index'
3
2
  require 'inspec/dependencies/requirement'
4
3
  require 'inspec/dependencies/resolver'
5
4
 
@@ -7,9 +6,6 @@ module Inspec
7
6
  #
8
7
  # A DependencySet manages a list of dependencies for a profile.
9
8
  #
10
- # Currently this class is a thin wrapper interface to coordinate the
11
- # VendorIndex and the Resolver.
12
- #
13
9
  class DependencySet
14
10
  #
15
11
  # Return a dependency set given a lockfile.
@@ -18,22 +14,21 @@ module Inspec
18
14
  # @param cwd [String] Current working directory for relative path includes
19
15
  # @param vendor_path [String] Path to the vendor directory
20
16
  #
21
- def self.from_lockfile(lockfile, cwd, vendor_path, backend)
22
- vendor_index = VendorIndex.new(vendor_path)
17
+ def self.from_lockfile(lockfile, cwd, cache, backend)
23
18
  dep_tree = lockfile.deps.map do |dep|
24
- Inspec::Requirement.from_lock_entry(dep, cwd, vendor_index, backend)
19
+ Inspec::Requirement.from_lock_entry(dep, cwd, cache, backend)
25
20
  end
26
21
 
27
22
  dep_list = flatten_dep_tree(dep_tree)
28
- new(cwd, vendor_path, dep_list, backend)
23
+ new(cwd, cache, dep_list, backend)
29
24
  end
30
25
 
31
- def self.from_array(dependencies, cwd, vendor_path, backend)
26
+ def self.from_array(dependencies, cwd, cache, backend)
32
27
  dep_list = {}
33
28
  dependencies.each do |d|
34
29
  dep_list[d.name] = d
35
30
  end
36
- new(cwd, vendor_path, dep_list, backend)
31
+ new(cwd, cache, dep_list, backend)
37
32
  end
38
33
 
39
34
  # This is experimental code to test the working of the
@@ -58,9 +53,9 @@ module Inspec
58
53
  # @param cwd [String] current working directory for relative path includes
59
54
  # @param vendor_path [String] path which contains vendored dependencies
60
55
  # @return [dependencies] this
61
- def initialize(cwd, vendor_path, dep_list, backend)
56
+ def initialize(cwd, cache, dep_list, backend)
62
57
  @cwd = cwd
63
- @vendor_path = vendor_path
58
+ @cache = cache
64
59
  @dep_list = dep_list
65
60
  @backend = backend
66
61
  end
@@ -91,8 +86,7 @@ module Inspec
91
86
  #
92
87
  def vendor(dependencies)
93
88
  return nil if dependencies.nil? || dependencies.empty?
94
- @vendor_index ||= VendorIndex.new(@vendor_path)
95
- @dep_list = Resolver.resolve(dependencies, @vendor_index, @cwd, @backend)
89
+ @dep_list = Resolver.resolve(dependencies, @cache, @cwd, @backend)
96
90
  end
97
91
  end
98
92
  end
@@ -4,8 +4,8 @@ require 'yaml'
4
4
  module Inspec
5
5
  class Lockfile
6
6
  # When we finalize this feature, we should set these to 1
7
- MINIMUM_SUPPORTED_VERSION = 0
8
- CURRENT_LOCKFILE_VERSION = 0
7
+ MINIMUM_SUPPORTED_VERSION = 1
8
+ CURRENT_LOCKFILE_VERSION = 1
9
9
 
10
10
  def self.from_dependency_set(dep_set)
11
11
  lockfile_content = {
@@ -32,14 +32,6 @@ lower than the minimum supported version #{MINIMUM_SUPPORTED_VERSION}.
32
32
  Please create a new lockfile for this project by running:
33
33
 
34
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
35
  EOF
44
36
  elsif version > CURRENT_LOCKFILE_VERSION
45
37
  fail <<EOF
@@ -78,8 +70,8 @@ EOF
78
70
  # different entry points of the API.
79
71
  def parse_content_hash(lockfile_content_hash)
80
72
  case version
81
- when 0
82
- parse_content_hash_0(lockfile_content_hash)
73
+ when 1
74
+ parse_content_hash_1(lockfile_content_hash)
83
75
  else
84
76
  # If we've gotten here, there is likely a mistake in the
85
77
  # lockfile version validation in the constructor.
@@ -87,7 +79,7 @@ EOF
87
79
  end
88
80
  end
89
81
 
90
- def parse_content_hash_0(lockfile_content_hash)
82
+ def parse_content_hash_1(lockfile_content_hash)
91
83
  @deps = if lockfile_content_hash['depends']
92
84
  lockfile_content_hash['depends'].map { |i| symbolize_keys(i) }
93
85
  end
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
- require 'inspec/fetcher'
2
+ require 'inspec/cached_fetcher'
3
3
  require 'inspec/dependencies/dependency_set'
4
4
  require 'digest'
5
5
 
@@ -9,53 +9,54 @@ module Inspec
9
9
  # appropriate we delegate to Inspec::Profile directly.
10
10
  #
11
11
  class Requirement
12
- attr_reader :name, :dep, :cwd, :opts
13
- attr_writer :dependencies
14
-
15
- def self.from_metadata(dep, vendor_index, opts)
12
+ def self.from_metadata(dep, cache, opts)
16
13
  fail 'Cannot load empty dependency.' if dep.nil? || dep.empty?
17
- name = dep[:name] || fail('You must provide a name for all dependencies')
18
- version = dep[:version]
19
- new(name, version, vendor_index, opts[:cwd], opts.merge(dep))
14
+ new(dep[:name], dep[:version], cache, opts[:cwd], opts.merge(dep))
20
15
  end
21
16
 
22
- def self.from_lock_entry(entry, cwd, vendor_index, backend)
17
+ def self.from_lock_entry(entry, cwd, cache, backend)
23
18
  req = new(entry[:name],
24
19
  entry[:version_constraints],
25
- vendor_index,
20
+ cache,
26
21
  cwd,
27
22
  entry[:resolved_source].merge(backend: backend))
28
23
 
29
24
  locked_deps = []
30
- Array(entry['dependencies']).each do |dep_entry|
31
- locked_deps << Inspec::Requirement.from_lock_entry(dep_entry, cwd, vendor_index, backend)
25
+ Array(entry[:dependencies]).each do |dep_entry|
26
+ locked_deps << Inspec::Requirement.from_lock_entry(dep_entry, cwd, cache, backend)
32
27
  end
33
28
  req.lock_deps(locked_deps)
34
29
  req
35
30
  end
36
31
 
37
- def initialize(name, version_constraints, vendor_index, cwd, opts)
32
+ attr_reader :cwd, :opts, :required_version
33
+ def initialize(name, version_constraints, cache, cwd, opts)
38
34
  @name = name
39
- @version_requirement = Gem::Requirement.new(Array(version_constraints))
40
- @dep = Gem::Dependency.new(name, @version_requirement, :runtime)
41
- @vendor_index = vendor_index
35
+ @required_version = Gem::Requirement.new(Array(version_constraints))
36
+ @cache = cache
42
37
  @backend = opts[:backend]
43
38
  @opts = opts
44
39
  @cwd = cwd
45
40
  end
46
41
 
47
- def required_version
48
- @version_requirement
42
+ #
43
+ # A dependency can be renamed in inspec.yml/inspec.lock. Prefer
44
+ # the name the user gave this dependency over the profile name.
45
+ #
46
+ def name
47
+ @name || profile.name
49
48
  end
50
49
 
51
50
  def source_version
52
- profile.metadata.params[:version]
51
+ profile.version
53
52
  end
54
53
 
55
54
  def source_satisfies_spec?
56
- name = profile.metadata.params[:name]
57
- version = profile.metadata.params[:version]
58
- @dep.match?(name, version)
55
+ gem_dep.match?(profile.name, profile.version)
56
+ end
57
+
58
+ def gem_dep
59
+ @gem_dep ||= Gem::Dependency.new(profile.name, required_version, :runtime)
59
60
  end
60
61
 
61
62
  def resolved_source
@@ -66,14 +67,13 @@ module Inspec
66
67
  h = {
67
68
  'name' => name,
68
69
  'resolved_source' => resolved_source,
69
- 'version_constraints' => @version_requirement.to_s,
70
+ 'version_constraints' => required_version.to_s,
70
71
  }
71
72
 
72
73
  if !dependencies.empty?
73
74
  h['dependencies'] = dependencies.map(&:to_hash)
74
75
  end
75
76
 
76
- h['content_hash'] = content_hash if content_hash
77
77
  h
78
78
  end
79
79
 
@@ -81,39 +81,29 @@ module Inspec
81
81
  @dependencies = dep_array
82
82
  end
83
83
 
84
- def content_hash
85
- @content_hash ||= begin
86
- archive_path = @vendor_index.archive_entry_for(fetcher.cache_key) || fetcher.archive_path
87
- if archive_path && File.file?(archive_path)
88
- Digest::SHA256.hexdigest File.read(archive_path)
89
- end
90
- end
91
- end
92
-
93
84
  def fetcher
94
- @fetcher ||= Inspec::Fetcher.resolve(opts)
95
- fail "No fetcher for #{name} (options: #{opts})" if @fetcher.nil?
96
- @fetcher
85
+ @fetcher ||= Inspec::CachedFetcher.new(opts, @cache)
97
86
  end
98
87
 
99
88
  def dependencies
100
89
  @dependencies ||= profile.metadata.dependencies.map do |r|
101
- Inspec::Requirement.from_metadata(r, @vendor_index, cwd: @cwd, backend: @backend)
90
+ Inspec::Requirement.from_metadata(r, @cache, cwd: @cwd, backend: @backend)
102
91
  end
103
92
  end
104
93
 
105
94
  def to_s
106
- "#{dep} (#{resolved_source})"
95
+ name
107
96
  end
108
97
 
109
98
  def profile
99
+ return @profile if ! @profile.nil?
100
+
110
101
  opts = @opts.dup
111
- opts[:cache] = @vendor_index
112
102
  opts[:backend] = @backend
113
103
  if !@dependencies.nil?
114
- opts[:dependencies] = Inspec::DependencySet.from_array(@dependencies, @cwd, @vendor_index, @backend)
104
+ opts[:dependencies] = Inspec::DependencySet.from_array(@dependencies, @cwd, @cache, @backend)
115
105
  end
116
- @profile ||= Inspec::Profile.for_target(opts, opts)
106
+ @profile = Inspec::Profile.for_fetcher(fetcher, opts)
117
107
  end
118
108
  end
119
109
  end
@@ -23,9 +23,9 @@ module Inspec
23
23
  # implementation of the fetcher being used.
24
24
  #
25
25
  class Resolver
26
- def self.resolve(dependencies, vendor_index, working_dir, backend)
26
+ def self.resolve(dependencies, cache, working_dir, backend)
27
27
  reqs = dependencies.map do |dep|
28
- req = Inspec::Requirement.from_metadata(dep, vendor_index, cwd: working_dir, backend: backend)
28
+ req = Inspec::Requirement.from_metadata(dep, cache, cwd: working_dir, backend: backend)
29
29
  req || fail("Cannot initialize dependency: #{req}")
30
30
  end
31
31
  new.resolve(reqs)
@@ -50,7 +50,7 @@ module Inspec
50
50
  def resolve(deps, top_level = true, seen_items = {}, path_string = '') # rubocop:disable Metrics/AbcSize
51
51
  graph = {}
52
52
  if top_level
53
- Inspec::Log.debug("Starting traversal of dependencies #{deps.map(&:name)}")
53
+ Inspec::Log.debug("Starting traversal of dependencies #{deps.map(&:to_s)}")
54
54
  else
55
55
  Inspec::Log.debug("Traversing dependency tree of transitive dependency #{deps.map(&:name)}")
56
56
  end
data/lib/inspec/dsl.rb CHANGED
@@ -18,6 +18,15 @@ module Inspec::DSL
18
18
  alias require_rules require_controls
19
19
  alias include_rules include_controls
20
20
 
21
+ def require_resource(options = {})
22
+ fail 'You must specify a specific resource name when calling require_resource()' if options[:resource].nil?
23
+
24
+ from_profile = options[:profile] || profile_name
25
+ target_name = options[:as] || options[:resource]
26
+ res = resource_class(from_profile, options[:resource])
27
+ add_resource(target_name, res)
28
+ end
29
+
21
30
  def self.load_spec_files_for_profile(bind_context, opts, &block)
22
31
  dependencies = opts[:dependencies]
23
32
  profile_id = opts[:profile_id]
@@ -44,12 +53,15 @@ EOF
44
53
  end
45
54
 
46
55
  def self.filter_included_controls(context, profile, &block)
47
- include_ctx = profile.runner_context
56
+ mock = Inspec::Backend.create({ backend: 'mock' })
57
+ include_ctx = Inspec::ProfileContext.for_profile(profile, mock, {})
48
58
  include_ctx.load(block) if block_given?
49
59
  # remove all rules that were not registered
50
- context.rules.keys.each do |id|
51
- unless include_ctx.rules[id]
52
- context.rules[id] = nil
60
+ context.all_rules.each do |r|
61
+ id = Inspec::Rule.rule_id(r)
62
+ fid = Inspec::Rule.profile_id(r) + '/' + id
63
+ unless include_ctx.rules[id] || include_ctx.rules[fid]
64
+ context.remove_rule(fid)
53
65
  end
54
66
  end
55
67
  end
data/lib/inspec/errors.rb CHANGED
@@ -9,4 +9,5 @@ module Inspec
9
9
  class CyclicDependencyError < Error; end
10
10
  class UnsatisfiedVersionSpecification < Error; end
11
11
  class DuplicateDep < Error; end
12
+ class FetcherFailure < Error; end
12
13
  end