chefspec-chef 9.3.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +30 -0
- data/LICENSE +22 -0
- data/Rakefile +85 -0
- data/chefspec-chef.gemspec +30 -0
- data/lib/chefspec/api/core.rb +217 -0
- data/lib/chefspec/api/described.rb +53 -0
- data/lib/chefspec/api/do_nothing.rb +26 -0
- data/lib/chefspec/api/include_any_recipe.rb +24 -0
- data/lib/chefspec/api/include_recipe.rb +28 -0
- data/lib/chefspec/api/link.rb +28 -0
- data/lib/chefspec/api/notifications.rb +40 -0
- data/lib/chefspec/api/reboot.rb +14 -0
- data/lib/chefspec/api/render_file.rb +37 -0
- data/lib/chefspec/api/state_attrs.rb +30 -0
- data/lib/chefspec/api/stubs.rb +183 -0
- data/lib/chefspec/api/stubs_for.rb +139 -0
- data/lib/chefspec/api/subscriptions.rb +37 -0
- data/lib/chefspec/api/user.rb +230 -0
- data/lib/chefspec/api.rb +39 -0
- data/lib/chefspec/berkshelf.rb +63 -0
- data/lib/chefspec/cacher.rb +64 -0
- data/lib/chefspec/coverage/filters.rb +82 -0
- data/lib/chefspec/coverage.rb +247 -0
- data/lib/chefspec/deprecations.rb +46 -0
- data/lib/chefspec/errors.rb +48 -0
- data/lib/chefspec/expect_exception.rb +51 -0
- data/lib/chefspec/extensions/chef/client.rb +21 -0
- data/lib/chefspec/extensions/chef/conditional.rb +16 -0
- data/lib/chefspec/extensions/chef/cookbook/gem_installer.rb +33 -0
- data/lib/chefspec/extensions/chef/cookbook_loader.rb +14 -0
- data/lib/chefspec/extensions/chef/cookbook_uploader.rb +12 -0
- data/lib/chefspec/extensions/chef/data_query.rb +49 -0
- data/lib/chefspec/extensions/chef/lwrp_base.rb +29 -0
- data/lib/chefspec/extensions/chef/provider.rb +39 -0
- data/lib/chefspec/extensions/chef/resource/freebsd_package.rb +17 -0
- data/lib/chefspec/extensions/chef/resource.rb +188 -0
- data/lib/chefspec/extensions/chef/run_context/cookbook_compiler.rb +84 -0
- data/lib/chefspec/extensions/chef/securable.rb +19 -0
- data/lib/chefspec/extensions/ohai/system.rb +11 -0
- data/lib/chefspec/extensions.rb +21 -0
- data/lib/chefspec/file_cache_path_proxy.rb +15 -0
- data/lib/chefspec/formatter.rb +282 -0
- data/lib/chefspec/librarian.rb +51 -0
- data/lib/chefspec/matchers/do_nothing_matcher.rb +52 -0
- data/lib/chefspec/matchers/include_any_recipe_matcher.rb +51 -0
- data/lib/chefspec/matchers/include_recipe_matcher.rb +46 -0
- data/lib/chefspec/matchers/link_to_matcher.rb +37 -0
- data/lib/chefspec/matchers/notifications_matcher.rb +143 -0
- data/lib/chefspec/matchers/render_file_matcher.rb +140 -0
- data/lib/chefspec/matchers/resource_matcher.rb +175 -0
- data/lib/chefspec/matchers/state_attrs_matcher.rb +71 -0
- data/lib/chefspec/matchers/subscribes_matcher.rb +72 -0
- data/lib/chefspec/matchers.rb +13 -0
- data/lib/chefspec/mixins/normalize.rb +22 -0
- data/lib/chefspec/policyfile.rb +69 -0
- data/lib/chefspec/renderer.rb +145 -0
- data/lib/chefspec/rspec.rb +21 -0
- data/lib/chefspec/runner.rb +8 -0
- data/lib/chefspec/server.rb +4 -0
- data/lib/chefspec/server_methods.rb +173 -0
- data/lib/chefspec/server_runner.rb +76 -0
- data/lib/chefspec/solo_runner.rb +516 -0
- data/lib/chefspec/stubs/command_registry.rb +11 -0
- data/lib/chefspec/stubs/command_stub.rb +37 -0
- data/lib/chefspec/stubs/data_bag_item_registry.rb +13 -0
- data/lib/chefspec/stubs/data_bag_item_stub.rb +25 -0
- data/lib/chefspec/stubs/data_bag_registry.rb +13 -0
- data/lib/chefspec/stubs/data_bag_stub.rb +23 -0
- data/lib/chefspec/stubs/registry.rb +32 -0
- data/lib/chefspec/stubs/search_registry.rb +13 -0
- data/lib/chefspec/stubs/search_stub.rb +25 -0
- data/lib/chefspec/stubs/stub.rb +38 -0
- data/lib/chefspec/util.rb +58 -0
- data/lib/chefspec/version.rb +3 -0
- data/lib/chefspec/zero_server.rb +142 -0
- data/lib/chefspec.rb +75 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/support/hash.rb +35 -0
- data/spec/unit/cacher_spec.rb +70 -0
- data/spec/unit/coverage/filters_spec.rb +60 -0
- data/spec/unit/deprecations_spec.rb +52 -0
- data/spec/unit/errors_spec.rb +57 -0
- data/spec/unit/expect_exception_spec.rb +32 -0
- data/spec/unit/macros_spec.rb +119 -0
- data/spec/unit/matchers/do_nothing_matcher.rb +5 -0
- data/spec/unit/matchers/include_any_recipe_matcher_spec.rb +52 -0
- data/spec/unit/matchers/include_recipe_matcher_spec.rb +38 -0
- data/spec/unit/matchers/link_to_matcher_spec.rb +55 -0
- data/spec/unit/matchers/notifications_matcher_spec.rb +39 -0
- data/spec/unit/matchers/render_file_matcher_spec.rb +68 -0
- data/spec/unit/matchers/resource_matcher_spec.rb +5 -0
- data/spec/unit/matchers/state_attrs_matcher_spec.rb +68 -0
- data/spec/unit/matchers/subscribes_matcher_spec.rb +63 -0
- data/spec/unit/renderer_spec.rb +69 -0
- data/spec/unit/server_runner_spec.rb +28 -0
- data/spec/unit/solo_runner_spec.rb +171 -0
- data/spec/unit/stubs/command_registry_spec.rb +27 -0
- data/spec/unit/stubs/command_stub_spec.rb +61 -0
- data/spec/unit/stubs/data_bag_item_registry_spec.rb +39 -0
- data/spec/unit/stubs/data_bag_item_stub_spec.rb +36 -0
- data/spec/unit/stubs/data_bag_registry_spec.rb +39 -0
- data/spec/unit/stubs/data_bag_stub_spec.rb +35 -0
- data/spec/unit/stubs/registry_spec.rb +29 -0
- data/spec/unit/stubs/search_registry_spec.rb +39 -0
- data/spec/unit/stubs/search_stub_spec.rb +36 -0
- data/spec/unit/stubs/stub_spec.rb +64 -0
- data/templates/coverage/human.erb +22 -0
- data/templates/coverage/json.erb +8 -0
- data/templates/coverage/table.erb +14 -0
- data/templates/errors/cookbook_path_not_found.erb +3 -0
- data/templates/errors/erb_template_parse_error.erb +5 -0
- data/templates/errors/gem_load_error.erb +7 -0
- data/templates/errors/invalid_berkshelf_options.erb +4 -0
- data/templates/errors/may_need_to_specify_platform.erb +25 -0
- data/templates/errors/no_conversion_error.erb +1 -0
- data/templates/errors/not_stubbed.erb +7 -0
- data/templates/errors/shell_out_not_stubbed.erb +10 -0
- data/templates/errors/template_not_found.erb +9 -0
- metadata +221 -0
@@ -0,0 +1,516 @@
|
|
1
|
+
require "fauxhai"
|
2
|
+
require "chef/client"
|
3
|
+
require "chef/cookbook/metadata"
|
4
|
+
require "chef/mash"
|
5
|
+
require "chef/providers"
|
6
|
+
require "chef/resources"
|
7
|
+
|
8
|
+
module ChefSpec
|
9
|
+
class SoloRunner
|
10
|
+
#
|
11
|
+
# Handy class method for just converging a runner if you do not care about
|
12
|
+
# initializing the runner with custom options.
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# ChefSpec::SoloRunner.converge('cookbook::recipe')
|
16
|
+
#
|
17
|
+
def self.converge(*recipe_names)
|
18
|
+
new.tap do |instance|
|
19
|
+
instance.converge(*recipe_names)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
include ChefSpec::Normalize
|
24
|
+
|
25
|
+
# @return [Hash]
|
26
|
+
attr_reader :options
|
27
|
+
|
28
|
+
# @return [Chef::RunContext]
|
29
|
+
attr_reader :run_context
|
30
|
+
|
31
|
+
#
|
32
|
+
# Instantiate a new SoloRunner to run examples with.
|
33
|
+
#
|
34
|
+
# @example Instantiate a new Runner
|
35
|
+
# ChefSpec::SoloRunner.new
|
36
|
+
#
|
37
|
+
# @example Specifying the platform and version
|
38
|
+
# ChefSpec::SoloRunner.new(platform: 'ubuntu', version: '18.04')
|
39
|
+
#
|
40
|
+
# @example Specifying the cookbook path
|
41
|
+
# ChefSpec::SoloRunner.new(cookbook_path: ['/cookbooks'])
|
42
|
+
#
|
43
|
+
# @example Specifying the log level
|
44
|
+
# ChefSpec::SoloRunner.new(log_level: :info)
|
45
|
+
#
|
46
|
+
#
|
47
|
+
# @param [Hash] options
|
48
|
+
# The options for the new runner
|
49
|
+
#
|
50
|
+
# @option options [Symbol] :log_level
|
51
|
+
# The log level to use (default is :warn)
|
52
|
+
# @option options [String] :platform
|
53
|
+
# The platform to load Ohai attributes from (must be present in fauxhai)
|
54
|
+
# @option options [String] :version
|
55
|
+
# The version of the platform to load Ohai attributes from (must be present in fauxhai)
|
56
|
+
# @option options [String] :path
|
57
|
+
# Path of a json file that will be passed to fauxhai as :path option
|
58
|
+
# @option options [Array<String>] :step_into
|
59
|
+
# The list of LWRPs to evaluate
|
60
|
+
# @option options String] :file_cache_path
|
61
|
+
# File caching path, if absent ChefSpec will use a temporary directory generated on the fly
|
62
|
+
#
|
63
|
+
# @yield [node] Configuration block for Chef::Node
|
64
|
+
#
|
65
|
+
def initialize(options = {})
|
66
|
+
@options = with_default_options(options)
|
67
|
+
apply_chef_config!
|
68
|
+
yield node if block_given?
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# Execute the given `run_list` on the node, without actually converging
|
73
|
+
# the node. Each time {#converge} is called, the `run_list` is reset to the
|
74
|
+
# new value (it is **not** additive).
|
75
|
+
#
|
76
|
+
# @example Converging a single recipe
|
77
|
+
# chef_run.converge('example::default')
|
78
|
+
#
|
79
|
+
# @example Converging multiple recipes
|
80
|
+
# chef_run.converge('example::default', 'example::secondary')
|
81
|
+
#
|
82
|
+
#
|
83
|
+
# @param [Array] recipe_names
|
84
|
+
# The names of the recipe or recipes to converge
|
85
|
+
#
|
86
|
+
# @return [ChefSpec::SoloRunner]
|
87
|
+
# A reference to the calling Runner (for chaining purposes)
|
88
|
+
#
|
89
|
+
def converge(*recipe_names)
|
90
|
+
# Re-apply the Chef config before converging in case something else
|
91
|
+
# called Config.reset too.
|
92
|
+
apply_chef_config!
|
93
|
+
@converging = false
|
94
|
+
node.run_list.reset!
|
95
|
+
recipe_names.each { |recipe_name| node.run_list.add(recipe_name) }
|
96
|
+
|
97
|
+
return self if dry_run?
|
98
|
+
|
99
|
+
# Expand the run_list
|
100
|
+
expand_run_list!
|
101
|
+
|
102
|
+
# Merge in provided node attributes. Default and override use the role_
|
103
|
+
# levels so they win over the relevant bits from cookbooks since otherwise
|
104
|
+
# they would not and that would be confusing.
|
105
|
+
node.attributes.role_default = Chef::Mixin::DeepMerge.merge(node.attributes.role_default, options[:default_attributes]) if options[:default_attributes]
|
106
|
+
node.attributes.normal = Chef::Mixin::DeepMerge.merge(node.attributes.normal, options[:normal_attributes]) if options[:normal_attributes]
|
107
|
+
node.attributes.role_override = Chef::Mixin::DeepMerge.merge(node.attributes.role_override, options[:override_attributes]) if options[:override_attributes]
|
108
|
+
node.attributes.automatic = Chef::Mixin::DeepMerge.merge(node.attributes.automatic, options[:automatic_attributes]) if options[:automatic_attributes]
|
109
|
+
|
110
|
+
# Setup the run_context, rescuing the exception that happens when a
|
111
|
+
# resource is not defined on a particular platform
|
112
|
+
begin
|
113
|
+
@run_context = client.setup_run_context
|
114
|
+
rescue Chef::Exceptions::NoSuchResourceType => e
|
115
|
+
raise Error::MayNeedToSpecifyPlatform.new(original_error: e.message)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Allow stubbing/mocking after the cookbook has been compiled but before the converge
|
119
|
+
yield node if block_given?
|
120
|
+
|
121
|
+
@converging = true
|
122
|
+
converge_val = @client.converge(@run_context)
|
123
|
+
if converge_val.is_a?(Exception)
|
124
|
+
raise converge_val
|
125
|
+
end
|
126
|
+
|
127
|
+
self
|
128
|
+
end
|
129
|
+
|
130
|
+
#
|
131
|
+
# Execute a block of recipe code.
|
132
|
+
#
|
133
|
+
# @param [Proc] block
|
134
|
+
# A block containing Chef recipe code
|
135
|
+
#
|
136
|
+
# @return [ChefSpec::SoloRunner]
|
137
|
+
#
|
138
|
+
def converge_block(&block)
|
139
|
+
converge do
|
140
|
+
recipe = Chef::Recipe.new(cookbook_name, "_test", run_context)
|
141
|
+
recipe.instance_exec(&block)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
#
|
146
|
+
# Run a static preload of the cookbook under test. This will load libraries
|
147
|
+
# and resources, but not attributes or recipes.
|
148
|
+
#
|
149
|
+
# @return [void]
|
150
|
+
#
|
151
|
+
def preload!
|
152
|
+
# Flag to disable preloading for situations where it doesn't make sense.
|
153
|
+
return if ENV["CHEFSPEC_NO_PRELOAD"]
|
154
|
+
|
155
|
+
begin
|
156
|
+
old_preload = $CHEFSPEC_PRELOAD
|
157
|
+
$CHEFSPEC_PRELOAD = true
|
158
|
+
converge("recipe[#{cookbook_name}]")
|
159
|
+
node.run_list.reset!
|
160
|
+
ensure
|
161
|
+
$CHEFSPEC_PRELOAD = old_preload
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
#
|
166
|
+
# The +Chef::Node+ corresponding to this Runner.
|
167
|
+
#
|
168
|
+
# @return [Chef::Node]
|
169
|
+
#
|
170
|
+
def node
|
171
|
+
runner = self
|
172
|
+
@node ||= begin
|
173
|
+
apply_chef_config!
|
174
|
+
client.build_node.tap do |node|
|
175
|
+
node.define_singleton_method(:runner) { runner }
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
#
|
181
|
+
# The full collection of resources for this Runner.
|
182
|
+
#
|
183
|
+
# @return [Hash<String, Chef::Resource>]
|
184
|
+
#
|
185
|
+
def resource_collection
|
186
|
+
@resource_collection ||= @run_context.resource_collection
|
187
|
+
end
|
188
|
+
|
189
|
+
#
|
190
|
+
# Find the resource with the declared type and resource name, and optionally match a performed action.
|
191
|
+
#
|
192
|
+
# If multiples match it returns the last (which more or less matches the chef last-inserter-wins semantics)
|
193
|
+
#
|
194
|
+
# @example Find a template at `/etc/foo`
|
195
|
+
# chef_run.find_resource(:template, '/etc/foo') #=> #<Chef::Resource::Template>
|
196
|
+
#
|
197
|
+
#
|
198
|
+
# @param [Symbol] type
|
199
|
+
# The type of resource (sometimes called `resource_name`) such as `file`
|
200
|
+
# or `directory`.
|
201
|
+
# @param [String, Regexp] name
|
202
|
+
# The value of the name attribute or identity attribute for the resource.
|
203
|
+
# @param [Symbol] action
|
204
|
+
# (optional) match only resources that performed the action.
|
205
|
+
#
|
206
|
+
# @return [Chef::Resource, nil]
|
207
|
+
# The matching resource, or nil if one is not found
|
208
|
+
#
|
209
|
+
def find_resource(type, name, action = nil)
|
210
|
+
resource_collection.all_resources.reverse_each.find do |resource|
|
211
|
+
resource.declared_type == type.to_sym && (name === resource.name || name === resource.identity) && (action.nil? || resource.performed_action?(action))
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
#
|
216
|
+
# Find the resource with the declared type.
|
217
|
+
#
|
218
|
+
# @example Find all template resources
|
219
|
+
# chef_run.find_resources(:template) #=> [#<Chef::Resource::Template>, #...]
|
220
|
+
#
|
221
|
+
#
|
222
|
+
# @param [Symbol] type
|
223
|
+
# The type of resource such as `:file` or `:directory`.
|
224
|
+
#
|
225
|
+
# @return [Array<Chef::Resource>]
|
226
|
+
# The matching resources
|
227
|
+
#
|
228
|
+
def find_resources(type)
|
229
|
+
resource_collection.all_resources.select do |resource|
|
230
|
+
resource_name(resource) == type.to_sym
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
#
|
235
|
+
# Boolean method to determine the current phase of the Chef run (compiling
|
236
|
+
# or converging)
|
237
|
+
#
|
238
|
+
# @return [true, false]
|
239
|
+
#
|
240
|
+
def compiling?
|
241
|
+
!@converging
|
242
|
+
end
|
243
|
+
|
244
|
+
#
|
245
|
+
# Determines if the runner should step into the given resource. The
|
246
|
+
# +step_into+ option takes a string, but this method coerces everything
|
247
|
+
# to symbols for safety.
|
248
|
+
#
|
249
|
+
# This method also substitutes any dashes (+-+) with underscores (+_+),
|
250
|
+
# because that's what Chef does under the hood. (See GitHub issue #254
|
251
|
+
# for more background)
|
252
|
+
#
|
253
|
+
# @param [Chef::Resource] resource
|
254
|
+
# the Chef resource to try and step in to
|
255
|
+
#
|
256
|
+
# @return [true, false]
|
257
|
+
#
|
258
|
+
def step_into?(resource)
|
259
|
+
key = resource_name(resource)
|
260
|
+
Array(options[:step_into]).map(&method(:resource_name)).include?(key)
|
261
|
+
end
|
262
|
+
|
263
|
+
#
|
264
|
+
# Boolean method to determine if this Runner is in `dry_run` mode.
|
265
|
+
#
|
266
|
+
# @return [true, false]
|
267
|
+
#
|
268
|
+
def dry_run?
|
269
|
+
!!options[:dry_run]
|
270
|
+
end
|
271
|
+
|
272
|
+
#
|
273
|
+
# This runner as a string.
|
274
|
+
#
|
275
|
+
# @return [String] Currently includes the run_list. Format of the string
|
276
|
+
# may change between versions of this gem.
|
277
|
+
#
|
278
|
+
def to_s
|
279
|
+
"#<#{self.class.name} run_list: [#{node.run_list}]>"
|
280
|
+
end
|
281
|
+
|
282
|
+
#
|
283
|
+
# The runner as a String with helpful output.
|
284
|
+
#
|
285
|
+
# @return [String]
|
286
|
+
#
|
287
|
+
def inspect
|
288
|
+
"#<#{self.class.name}" \
|
289
|
+
" options: #{options.inspect}," \
|
290
|
+
" run_list: [#{node.run_list}]>"
|
291
|
+
end
|
292
|
+
|
293
|
+
#
|
294
|
+
# Respond to custom matchers defined by the user.
|
295
|
+
#
|
296
|
+
def method_missing(m, *args, &block)
|
297
|
+
block = ChefSpec.matchers[resource_name(m.to_sym)]
|
298
|
+
if block
|
299
|
+
instance_exec(args.first, &block)
|
300
|
+
else
|
301
|
+
super
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
#
|
306
|
+
# Inform Ruby that we respond to methods that are defined as custom
|
307
|
+
# matchers.
|
308
|
+
#
|
309
|
+
def respond_to_missing?(m, include_private = false)
|
310
|
+
ChefSpec.matchers.key?(m.to_sym) || super
|
311
|
+
end
|
312
|
+
|
313
|
+
private
|
314
|
+
|
315
|
+
#
|
316
|
+
# The path to cache files on disk. This value is created using
|
317
|
+
# {Dir.mktmpdir}. The method adds a {Kernel.at_exit} handler to ensure the
|
318
|
+
# temporary directory is deleted when the system exits.
|
319
|
+
#
|
320
|
+
# **This method creates a new temporary directory on each call!** As such,
|
321
|
+
# you should cache the result to a variable inside you system.
|
322
|
+
#
|
323
|
+
def file_cache_path
|
324
|
+
path = Dir.mktmpdir
|
325
|
+
at_exit { FileUtils.rm_rf(path) }
|
326
|
+
path
|
327
|
+
end
|
328
|
+
|
329
|
+
#
|
330
|
+
# Set the default options, with the given options taking precedence.
|
331
|
+
#
|
332
|
+
# @param [Hash] options
|
333
|
+
# the list of options to take precedence
|
334
|
+
#
|
335
|
+
# @return [Hash] options
|
336
|
+
#
|
337
|
+
def with_default_options(options)
|
338
|
+
config = RSpec.configuration
|
339
|
+
|
340
|
+
{
|
341
|
+
cookbook_root: config.cookbook_root || calling_cookbook_root(options, caller),
|
342
|
+
cookbook_path: config.cookbook_path || calling_cookbook_path(options, caller),
|
343
|
+
role_path: config.role_path || default_role_path,
|
344
|
+
environment_path: config.environment_path || default_environment_path,
|
345
|
+
file_cache_path: config.file_cache_path,
|
346
|
+
log_level: config.log_level,
|
347
|
+
path: config.path,
|
348
|
+
platform: config.platform,
|
349
|
+
version: config.version,
|
350
|
+
}.merge(options)
|
351
|
+
end
|
352
|
+
|
353
|
+
#
|
354
|
+
# The inferred cookbook root from the calling spec.
|
355
|
+
#
|
356
|
+
# @param [Hash<Symbol, Object>] options
|
357
|
+
# initial runner options
|
358
|
+
# @param [Array<String>] kaller
|
359
|
+
# the calling trace
|
360
|
+
#
|
361
|
+
# @return [String]
|
362
|
+
#
|
363
|
+
def calling_cookbook_root(options, kaller)
|
364
|
+
calling_spec = options[:spec_declaration_locations] || kaller.find { |line| line =~ %r{/spec} }
|
365
|
+
raise Error::CookbookPathNotFound if calling_spec.nil?
|
366
|
+
|
367
|
+
bits = calling_spec.split(/:[0-9]/, 2).first.split(File::SEPARATOR)
|
368
|
+
spec_dir = bits.index("spec") || 0
|
369
|
+
|
370
|
+
File.join(bits.slice(0, spec_dir))
|
371
|
+
end
|
372
|
+
|
373
|
+
#
|
374
|
+
# The inferred path from the calling spec.
|
375
|
+
#
|
376
|
+
# @param [Hash<Symbol, Object>] options
|
377
|
+
# initial runner options
|
378
|
+
# @param [Array<String>] kaller
|
379
|
+
# the calling trace
|
380
|
+
#
|
381
|
+
# @return [String]
|
382
|
+
#
|
383
|
+
def calling_cookbook_path(options, kaller)
|
384
|
+
File.expand_path(File.join(calling_cookbook_root(options, kaller), ".."))
|
385
|
+
end
|
386
|
+
|
387
|
+
#
|
388
|
+
# The inferred path to roles.
|
389
|
+
#
|
390
|
+
# @return [String, nil]
|
391
|
+
#
|
392
|
+
def default_role_path
|
393
|
+
Pathname.new(Dir.pwd).ascend do |path|
|
394
|
+
possible = File.join(path, "roles")
|
395
|
+
return possible if File.exist?(possible)
|
396
|
+
end
|
397
|
+
|
398
|
+
nil
|
399
|
+
end
|
400
|
+
|
401
|
+
#
|
402
|
+
# The inferred path to environments.
|
403
|
+
#
|
404
|
+
# @return [String, nil]
|
405
|
+
#
|
406
|
+
def default_environment_path
|
407
|
+
Pathname.new(Dir.pwd).ascend do |path|
|
408
|
+
possible = File.join(path, "environments")
|
409
|
+
return possible if File.exist?(possible)
|
410
|
+
end
|
411
|
+
|
412
|
+
nil
|
413
|
+
end
|
414
|
+
|
415
|
+
#
|
416
|
+
# The +Chef::Client+ for this runner.
|
417
|
+
#
|
418
|
+
# @return [Chef::Runner]
|
419
|
+
#
|
420
|
+
def client
|
421
|
+
return @client if @client
|
422
|
+
|
423
|
+
@client = Chef::Client.new
|
424
|
+
@client.ohai.data = Mash.from_hash(Fauxhai.mock(options).data)
|
425
|
+
@client.load_node
|
426
|
+
@client.build_node
|
427
|
+
@client.save_updated_node
|
428
|
+
@client
|
429
|
+
end
|
430
|
+
|
431
|
+
#
|
432
|
+
# We really need a way to just expand the run_list, but that's done by
|
433
|
+
# +Chef::Client#build_node+. However, that same method also resets the
|
434
|
+
# automatic attributes, making it impossible to mock them. So we are
|
435
|
+
# stuck +instance_eval+ing against the client and manually expanding
|
436
|
+
# the mode object.
|
437
|
+
#
|
438
|
+
# @todo Remove in Chef 13
|
439
|
+
#
|
440
|
+
def expand_run_list!
|
441
|
+
# Recent versions of Chef include a method to expand the +run_list+,
|
442
|
+
# setting the correct instance variables on the policy builder. We use
|
443
|
+
# that, unless the user is running an older version of Chef which
|
444
|
+
# doesn't include this method.
|
445
|
+
if client.respond_to?(:expanded_run_list)
|
446
|
+
client.expanded_run_list
|
447
|
+
else
|
448
|
+
# Sadly, if we got this far, it means that the current Chef version
|
449
|
+
# does not include the +expanded_run_list+ method, so we need to
|
450
|
+
# manually expand the +run_list+. The following code has been known
|
451
|
+
# to make kittens cry, so please read with extreme caution.
|
452
|
+
client.instance_eval do
|
453
|
+
@run_list_expansion = expand_run_list
|
454
|
+
@expanded_run_list_with_versions = @run_list_expansion.recipes.with_version_constraints_strings
|
455
|
+
end
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
#
|
460
|
+
# Try to load the cookbook metadata for the cookbook under test.
|
461
|
+
#
|
462
|
+
# @return [Chef::Cookbook::Metadata]
|
463
|
+
#
|
464
|
+
def cookbook
|
465
|
+
@cookbook ||= Chef::Cookbook::Metadata.new.tap { |m| m.from_file("#{options[:cookbook_root]}/metadata.rb") }
|
466
|
+
end
|
467
|
+
|
468
|
+
#
|
469
|
+
# Try to figure out the name for the cookbook under test.
|
470
|
+
#
|
471
|
+
# @return [String]
|
472
|
+
#
|
473
|
+
def cookbook_name
|
474
|
+
# Try to figure out the name of this cookbook, pretending this block
|
475
|
+
# is in the name context as the cookbook under test.
|
476
|
+
|
477
|
+
cookbook.name
|
478
|
+
rescue IOError
|
479
|
+
# Old cookbook, has no metadata, use the folder name I guess.
|
480
|
+
File.basename(options[:cookbook_root])
|
481
|
+
end
|
482
|
+
|
483
|
+
#
|
484
|
+
# Apply the required options to {Chef::Config}.
|
485
|
+
#
|
486
|
+
# @api private
|
487
|
+
# @return [void]
|
488
|
+
#
|
489
|
+
def apply_chef_config!
|
490
|
+
Chef::Log.level = @options[:log_level]
|
491
|
+
|
492
|
+
Chef::Config.reset!
|
493
|
+
Chef::Config.formatters.clear
|
494
|
+
Chef::Config.add_formatter("chefspec")
|
495
|
+
Chef::Config[:cache_type] = "Memory"
|
496
|
+
Chef::Config[:client_key] = nil
|
497
|
+
Chef::Config[:client_name] = nil
|
498
|
+
Chef::Config[:node_name] = nil
|
499
|
+
Chef::Config[:file_cache_path] = @options[:file_cache_path] || file_cache_path
|
500
|
+
Chef::Config[:cookbook_path] = Array(@options[:cookbook_path])
|
501
|
+
# If the word cookbook is in the folder name, treat it as the path. Otherwise
|
502
|
+
# it's probably not a cookbook path and so we activate the gross hack mode.
|
503
|
+
if Chef::Config[:cookbook_path].size == 1 && Chef::Config[:cookbook_path].first !~ /cookbook/
|
504
|
+
Chef::Config[:chefspec_cookbook_root] = @options[:cookbook_root]
|
505
|
+
end
|
506
|
+
Chef::Config[:no_lazy_load] = true
|
507
|
+
Chef::Config[:role_path] = Array(@options[:role_path])
|
508
|
+
Chef::Config[:force_logger] = true
|
509
|
+
Chef::Config[:solo] = true
|
510
|
+
Chef::Config[:solo_legacy_mode] = true
|
511
|
+
Chef::Config[:use_policyfile] = false
|
512
|
+
Chef::Config[:environment_path] = @options[:environment_path]
|
513
|
+
end
|
514
|
+
|
515
|
+
end
|
516
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require_relative "stub"
|
2
|
+
|
3
|
+
module ChefSpec
|
4
|
+
module Stubs
|
5
|
+
class CommandStub < Stub
|
6
|
+
attr_reader :block
|
7
|
+
attr_reader :command
|
8
|
+
attr_reader :value
|
9
|
+
|
10
|
+
def initialize(command, &block)
|
11
|
+
@command = command
|
12
|
+
@block = block
|
13
|
+
end
|
14
|
+
|
15
|
+
def and_return(value)
|
16
|
+
@value = value
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def result
|
21
|
+
if @block
|
22
|
+
@block.call
|
23
|
+
else
|
24
|
+
@value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def signature
|
29
|
+
if @block
|
30
|
+
"stub_command(#{@command.inspect}) { # Ruby code }"
|
31
|
+
else
|
32
|
+
"stub_command(#{@command.inspect}).and_return(#{@value})"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative "stub"
|
2
|
+
|
3
|
+
module ChefSpec
|
4
|
+
module Stubs
|
5
|
+
class DataBagItemStub < Stub
|
6
|
+
attr_reader :block
|
7
|
+
attr_reader :id
|
8
|
+
attr_reader :bag
|
9
|
+
|
10
|
+
def initialize(bag, id, &block)
|
11
|
+
@bag = bag.to_s
|
12
|
+
@id = id
|
13
|
+
@block = block
|
14
|
+
end
|
15
|
+
|
16
|
+
def signature
|
17
|
+
if @block
|
18
|
+
"stub_data_bag_item(#{@bag.inspect}, #{@id.inspect}) { # Ruby code }"
|
19
|
+
else
|
20
|
+
"stub_data_bag_item(#{@bag.inspect}, #{@id.inspect}).and_return(#{@value})"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative "stub"
|
2
|
+
|
3
|
+
module ChefSpec
|
4
|
+
module Stubs
|
5
|
+
class DataBagStub < Stub
|
6
|
+
attr_reader :block
|
7
|
+
attr_reader :bag
|
8
|
+
|
9
|
+
def initialize(bag, &block)
|
10
|
+
@bag = bag.to_s
|
11
|
+
@block = block
|
12
|
+
end
|
13
|
+
|
14
|
+
def signature
|
15
|
+
if @block
|
16
|
+
"stub_data_bag(#{@bag.inspect}) { # Ruby code }"
|
17
|
+
else
|
18
|
+
"stub_data_bag(#{@bag.inspect}).and_return(#{@value})"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module ChefSpec
|
2
|
+
module Stubs
|
3
|
+
class Registry
|
4
|
+
class << self
|
5
|
+
extend Forwardable
|
6
|
+
def_delegators :instance, :reset!, :register, :stubs, :stubs=, :stub_for
|
7
|
+
end
|
8
|
+
|
9
|
+
include Singleton
|
10
|
+
|
11
|
+
# @return [Hash<Symbol, Array<SearchStub>>]
|
12
|
+
attr_accessor :stubs
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
reset!
|
16
|
+
end
|
17
|
+
|
18
|
+
def reset!
|
19
|
+
@stubs = []
|
20
|
+
end
|
21
|
+
|
22
|
+
def register(stub)
|
23
|
+
@stubs.insert(0, stub)
|
24
|
+
stub
|
25
|
+
end
|
26
|
+
|
27
|
+
def stub_for(*args)
|
28
|
+
raise ArgumentError, "#stub_for is an abstract function"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|