chefspec-chef 9.3.4
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 +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
|