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,140 @@
|
|
1
|
+
module ChefSpec::Matchers
|
2
|
+
class RenderFileMatcher
|
3
|
+
attr_reader :expected_content
|
4
|
+
def initialize(path)
|
5
|
+
@path = path
|
6
|
+
@expected_content = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def matches?(runner)
|
10
|
+
@runner = runner
|
11
|
+
|
12
|
+
if resource
|
13
|
+
ChefSpec::Coverage.cover!(resource)
|
14
|
+
has_create_action? && matches_content?
|
15
|
+
else
|
16
|
+
false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def with_content(expected_content = nil, &block)
|
21
|
+
if expected_content && block
|
22
|
+
raise ArgumentError, "Cannot specify expected content and a block!"
|
23
|
+
elsif expected_content
|
24
|
+
@expected_content << expected_content
|
25
|
+
elsif block_given?
|
26
|
+
@expected_content << block
|
27
|
+
else
|
28
|
+
raise ArgumentError, "Must specify expected content or a block!"
|
29
|
+
end
|
30
|
+
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def description
|
35
|
+
message = %Q{render file "#{@path}"}
|
36
|
+
@expected_content.each do |expected|
|
37
|
+
if expected.to_s.include?("\n")
|
38
|
+
message << " with content <suppressed>"
|
39
|
+
else
|
40
|
+
message << " with content #{expected.inspect}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
message
|
44
|
+
end
|
45
|
+
|
46
|
+
def failure_message
|
47
|
+
message = %Q{expected Chef run to render "#{@path}"}
|
48
|
+
unless @expected_content.empty?
|
49
|
+
message << " matching:"
|
50
|
+
message << "\n\n"
|
51
|
+
message << expected_content_message
|
52
|
+
message << "\n\n"
|
53
|
+
message << "but got:"
|
54
|
+
message << "\n\n"
|
55
|
+
message << @actual_content.to_s
|
56
|
+
message << "\n "
|
57
|
+
end
|
58
|
+
message
|
59
|
+
end
|
60
|
+
|
61
|
+
def failure_message_when_negated
|
62
|
+
message = %Q{expected file "#{@path}"}
|
63
|
+
unless @expected_content.empty?
|
64
|
+
message << " matching:"
|
65
|
+
message << "\n\n"
|
66
|
+
message << expected_content_message
|
67
|
+
message << "\n\n"
|
68
|
+
end
|
69
|
+
message << " to not be in Chef run"
|
70
|
+
message
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def expected_content_message
|
76
|
+
messages = @expected_content.collect do |expected|
|
77
|
+
if RSpec::Matchers.is_a_matcher?(expected) && expected.respond_to?(:description)
|
78
|
+
expected.description
|
79
|
+
elsif expected.is_a?(Proc)
|
80
|
+
"(the result of a proc)"
|
81
|
+
else
|
82
|
+
expected.to_s
|
83
|
+
end
|
84
|
+
end
|
85
|
+
messages.join("\n\n")
|
86
|
+
end
|
87
|
+
|
88
|
+
def resource
|
89
|
+
@resource ||= @runner.find_resource(:cookbook_file, @path) ||
|
90
|
+
@runner.find_resource(:file, @path) ||
|
91
|
+
@runner.find_resource(:template, @path)
|
92
|
+
end
|
93
|
+
|
94
|
+
#
|
95
|
+
# Determines if the given resource has a create-like action.
|
96
|
+
#
|
97
|
+
# @param [Chef::Resource] resource
|
98
|
+
#
|
99
|
+
# @return [true, false]
|
100
|
+
#
|
101
|
+
def has_create_action?
|
102
|
+
%i{create create_if_missing}.any? { |action| resource.performed_action?(action) }
|
103
|
+
end
|
104
|
+
|
105
|
+
#
|
106
|
+
# Determines if the resources content matches the expected content.
|
107
|
+
#
|
108
|
+
# @param [Chef::Resource] resource
|
109
|
+
#
|
110
|
+
# @return [true, false]
|
111
|
+
#
|
112
|
+
def matches_content?
|
113
|
+
return true if @expected_content.empty?
|
114
|
+
|
115
|
+
@actual_content = ChefSpec::Renderer.new(@runner, resource).content
|
116
|
+
|
117
|
+
return false if @actual_content.nil?
|
118
|
+
|
119
|
+
# Knock out matches that pass. When we're done, we pass if the list is
|
120
|
+
# empty. Otherwise, @expected_content is the list of matchers that
|
121
|
+
# failed
|
122
|
+
@expected_content.delete_if do |expected|
|
123
|
+
if expected.is_a?(Regexp)
|
124
|
+
@actual_content =~ expected
|
125
|
+
elsif RSpec::Matchers.is_a_matcher?(expected)
|
126
|
+
expected.matches?(@actual_content)
|
127
|
+
elsif expected.is_a?(Proc)
|
128
|
+
expected.call(@actual_content)
|
129
|
+
# Weird RSpecish, but that block will return false for a negated check,
|
130
|
+
# so we always return true. The block will raise an exception if the
|
131
|
+
# assertion fails.
|
132
|
+
true
|
133
|
+
else
|
134
|
+
@actual_content.include?(expected)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
@expected_content.empty?
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
require "rspec/matchers/expecteds_for_multiple_diffs"
|
2
|
+
require "rspec/expectations/fail_with"
|
3
|
+
|
4
|
+
module ChefSpec::Matchers
|
5
|
+
class ResourceMatcher
|
6
|
+
def initialize(resource_name, expected_action, expected_identity)
|
7
|
+
@resource_name = resource_name
|
8
|
+
@expected_action = expected_action
|
9
|
+
@expected_identity = expected_identity
|
10
|
+
end
|
11
|
+
|
12
|
+
def with(parameters = {})
|
13
|
+
params.merge!(parameters)
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def at_compile_time
|
18
|
+
raise ArgumentError, "Cannot specify both .at_converge_time and .at_compile_time!" if @converge_time
|
19
|
+
|
20
|
+
@compile_time = true
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def at_converge_time
|
25
|
+
raise ArgumentError, "Cannot specify both .at_compile_time and .at_converge_time!" if @compile_time
|
26
|
+
|
27
|
+
@converge_time = true
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# Allow users to specify fancy #with matchers.
|
33
|
+
#
|
34
|
+
def method_missing(m, *args, &block)
|
35
|
+
if m.to_s =~ /^with_(.+)$/
|
36
|
+
with($1.to_sym => args.first)
|
37
|
+
self
|
38
|
+
else
|
39
|
+
super
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def description
|
44
|
+
%Q{#{@expected_action} #{@resource_name} "#{@expected_identity}"}
|
45
|
+
end
|
46
|
+
|
47
|
+
def matches?(runner)
|
48
|
+
@runner = runner
|
49
|
+
|
50
|
+
if resource
|
51
|
+
ChefSpec::Coverage.cover!(resource)
|
52
|
+
unmatched_parameters.empty? && correct_phase?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def failure_message
|
57
|
+
if resource
|
58
|
+
if unmatched_parameters.empty?
|
59
|
+
if @compile_time
|
60
|
+
%Q{expected "#{resource}" to be run at compile time}
|
61
|
+
else
|
62
|
+
%Q{expected "#{resource}" to be run at converge time}
|
63
|
+
end
|
64
|
+
else
|
65
|
+
message = %Q{expected "#{resource}" to have parameters:} \
|
66
|
+
"\n\n" \
|
67
|
+
" " + unmatched_parameters.collect { |parameter, h|
|
68
|
+
msg = "#{parameter} #{h[:expected].inspect}, was #{h[:actual].inspect}"
|
69
|
+
diff = ::RSpec::Matchers::ExpectedsForMultipleDiffs.from(h[:expected]) \
|
70
|
+
.message_with_diff(message, ::RSpec::Expectations.differ, h[:actual])
|
71
|
+
msg += diff if diff
|
72
|
+
msg
|
73
|
+
}.join("\n ")
|
74
|
+
end
|
75
|
+
else
|
76
|
+
%Q{expected "#{@resource_name}[#{@expected_identity}]"} \
|
77
|
+
" with action :#{@expected_action} to be in Chef run." \
|
78
|
+
" Other #{@resource_name} resources:" \
|
79
|
+
"\n\n" \
|
80
|
+
" " + similar_resources.map(&:to_s).join("\n ") + "\n "
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def failure_message_when_negated
|
85
|
+
if resource
|
86
|
+
message = %Q{expected "#{resource}" actions #{resource.performed_actions.inspect} to not exist}
|
87
|
+
else
|
88
|
+
message = %Q{expected "#{resource}" to not exist}
|
89
|
+
end
|
90
|
+
|
91
|
+
message << " at compile time" if @compile_time
|
92
|
+
message << " at converge time" if @converge_time
|
93
|
+
message
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def unmatched_parameters
|
99
|
+
return @_unmatched_parameters if @_unmatched_parameters
|
100
|
+
|
101
|
+
@_unmatched_parameters = {}
|
102
|
+
|
103
|
+
params.each do |parameter, expected|
|
104
|
+
unless matches_parameter?(parameter, expected)
|
105
|
+
@_unmatched_parameters[parameter] = {
|
106
|
+
expected: expected,
|
107
|
+
actual: safe_send(parameter),
|
108
|
+
}
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
@_unmatched_parameters
|
113
|
+
end
|
114
|
+
|
115
|
+
def matches_parameter?(parameter, expected)
|
116
|
+
value = safe_send(parameter)
|
117
|
+
if parameter == :source
|
118
|
+
# Chef 11+ stores the source parameter internally as an Array
|
119
|
+
Array(expected) == Array(value)
|
120
|
+
elsif expected.is_a?(Class)
|
121
|
+
# Ruby can't compare classes with ===
|
122
|
+
expected == value
|
123
|
+
else
|
124
|
+
expected === value
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def correct_phase?
|
129
|
+
if @compile_time
|
130
|
+
resource.performed_action(@expected_action)[:compile_time]
|
131
|
+
elsif @converge_time
|
132
|
+
resource.performed_action(@expected_action)[:converge_time]
|
133
|
+
else
|
134
|
+
true
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def safe_send(parameter)
|
139
|
+
resource.send(parameter)
|
140
|
+
rescue NoMethodError
|
141
|
+
nil
|
142
|
+
end
|
143
|
+
|
144
|
+
#
|
145
|
+
# Any other resources in the Chef run that have the same resource
|
146
|
+
# type. Used by {failure_message} to be ultra helpful.
|
147
|
+
#
|
148
|
+
# @return [Array<Chef::Resource>]
|
149
|
+
#
|
150
|
+
def similar_resources
|
151
|
+
@_similar_resources ||= @runner.find_resources(@resource_name)
|
152
|
+
end
|
153
|
+
|
154
|
+
#
|
155
|
+
# Find the resource in the Chef run by the given class name and
|
156
|
+
# resource identity/name.
|
157
|
+
#
|
158
|
+
# @see ChefSpec::SoloRunner#find_resource
|
159
|
+
#
|
160
|
+
# @return [Chef::Resource, nil]
|
161
|
+
#
|
162
|
+
def resource
|
163
|
+
@_resource ||= @runner.find_resource(@resource_name, @expected_identity, @expected_action)
|
164
|
+
end
|
165
|
+
|
166
|
+
#
|
167
|
+
# The list of parameters passed to the {with} matcher.
|
168
|
+
#
|
169
|
+
# @return [Hash]
|
170
|
+
#
|
171
|
+
def params
|
172
|
+
@_params ||= {}
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module ChefSpec::Matchers
|
2
|
+
class StateAttrsMatcher
|
3
|
+
#
|
4
|
+
# Create a new state_attrs matcher.
|
5
|
+
#
|
6
|
+
# @param [Array] state_attrs
|
7
|
+
#
|
8
|
+
def initialize(state_attrs)
|
9
|
+
@expected_attrs = state_attrs.map(&:to_sym)
|
10
|
+
end
|
11
|
+
|
12
|
+
def matches?(resource)
|
13
|
+
@resource = resource
|
14
|
+
@resource && matches_state_attrs?
|
15
|
+
end
|
16
|
+
|
17
|
+
def description
|
18
|
+
%Q{have state attributes #{@expected_attrs.inspect}}
|
19
|
+
end
|
20
|
+
|
21
|
+
def failure_message
|
22
|
+
if @resource
|
23
|
+
"expected #{state_attrs.inspect} to equal #{@expected_attrs.inspect}"
|
24
|
+
else
|
25
|
+
"expected _something_ to have state attributes, but the " \
|
26
|
+
"_something_ you gave me was nil!" \
|
27
|
+
"\n" \
|
28
|
+
"Ensure the resource exists before making assertions:" \
|
29
|
+
"\n\n" \
|
30
|
+
" expect(resource).to be" \
|
31
|
+
"\n "
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def failure_message_when_negated
|
36
|
+
if @resource
|
37
|
+
"expected #{state_attrs.inspect} to not equal " \
|
38
|
+
"#{@expected_attrs.inspect}"
|
39
|
+
else
|
40
|
+
"expected _something_ to not have state attributes, but the " \
|
41
|
+
"_something_ you gave me was nil!" \
|
42
|
+
"\n" \
|
43
|
+
"Ensure the resource exists before making assertions:" \
|
44
|
+
"\n\n" \
|
45
|
+
" expect(resource).to be" \
|
46
|
+
"\n "
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
#
|
53
|
+
# Determine if all the expected state attributes are present on the
|
54
|
+
# given resource.
|
55
|
+
#
|
56
|
+
# @return [true, false]
|
57
|
+
#
|
58
|
+
def matches_state_attrs?
|
59
|
+
@expected_attrs == state_attrs
|
60
|
+
end
|
61
|
+
|
62
|
+
#
|
63
|
+
# The list of state attributes declared on the given resource.
|
64
|
+
#
|
65
|
+
# @return [Array<Symbol>]
|
66
|
+
#
|
67
|
+
def state_attrs
|
68
|
+
@resource.class.state_attrs.map(&:to_sym)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module ChefSpec::Matchers
|
2
|
+
class SubscribesMatcher
|
3
|
+
include ChefSpec::Normalize
|
4
|
+
|
5
|
+
def initialize(signature)
|
6
|
+
signature.match(/^([^\[]*)\[(.*)\]$/)
|
7
|
+
@expected_resource_type = $1
|
8
|
+
@expected_resource_name = $2
|
9
|
+
end
|
10
|
+
|
11
|
+
def matches?(resource)
|
12
|
+
@instance = ChefSpec::Matchers::NotificationsMatcher.new(resource.to_s)
|
13
|
+
|
14
|
+
if @action
|
15
|
+
@instance.to(@action)
|
16
|
+
end
|
17
|
+
|
18
|
+
if @immediately
|
19
|
+
@instance.immediately
|
20
|
+
end
|
21
|
+
|
22
|
+
if @delayed
|
23
|
+
@instance.delayed
|
24
|
+
end
|
25
|
+
|
26
|
+
if @before
|
27
|
+
@instance.before
|
28
|
+
end
|
29
|
+
|
30
|
+
if resource
|
31
|
+
runner = resource.run_context.node.runner
|
32
|
+
expected = runner.find_resource(@expected_resource_type, @expected_resource_name)
|
33
|
+
|
34
|
+
@instance.matches?(expected)
|
35
|
+
else
|
36
|
+
@instance.matches?(nil)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def on(action)
|
41
|
+
@action = action
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
def immediately
|
46
|
+
@immediately = true
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def delayed
|
51
|
+
@delayed = true
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
def before
|
56
|
+
@before = true
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def description
|
61
|
+
@instance.description
|
62
|
+
end
|
63
|
+
|
64
|
+
def failure_message
|
65
|
+
@instance.failure_message
|
66
|
+
end
|
67
|
+
|
68
|
+
def failure_message_when_negated
|
69
|
+
@instance.failure_message_when_negated
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module ChefSpec
|
2
|
+
module Matchers
|
3
|
+
require_relative "matchers/do_nothing_matcher"
|
4
|
+
require_relative "matchers/include_any_recipe_matcher"
|
5
|
+
require_relative "matchers/include_recipe_matcher"
|
6
|
+
require_relative "matchers/link_to_matcher"
|
7
|
+
require_relative "matchers/notifications_matcher"
|
8
|
+
require_relative "matchers/render_file_matcher"
|
9
|
+
require_relative "matchers/resource_matcher"
|
10
|
+
require_relative "matchers/state_attrs_matcher"
|
11
|
+
require_relative "matchers/subscribes_matcher"
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module ChefSpec
|
2
|
+
module Normalize
|
3
|
+
#
|
4
|
+
# Calculate the name of a resource, replacing dashes with underscores
|
5
|
+
# and converting symbols to strings and back again.
|
6
|
+
#
|
7
|
+
# @param [String, Chef::Resource] thing
|
8
|
+
#
|
9
|
+
# @return [Symbol]
|
10
|
+
#
|
11
|
+
def resource_name(thing)
|
12
|
+
if thing.respond_to?(:declared_type) && thing.declared_type
|
13
|
+
name = thing.declared_type
|
14
|
+
elsif thing.respond_to?(:resource_name)
|
15
|
+
name = thing.resource_name
|
16
|
+
else
|
17
|
+
name = thing
|
18
|
+
end
|
19
|
+
name.to_s.gsub("-", "_").to_sym
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
begin
|
2
|
+
require "chef-cli/policyfile_services/export_repo"
|
3
|
+
require "chef-cli/policyfile_services/install"
|
4
|
+
require "chef/workstation_config_loader"
|
5
|
+
rescue LoadError
|
6
|
+
raise ChefSpec::Error::GemLoadError.new(gem: "chef-cli", name: "ChefCLI")
|
7
|
+
end
|
8
|
+
|
9
|
+
module ChefSpec
|
10
|
+
class Policyfile
|
11
|
+
class << self
|
12
|
+
extend Forwardable
|
13
|
+
def_delegators :instance, :setup!, :teardown!
|
14
|
+
end
|
15
|
+
|
16
|
+
include Singleton
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@tmpdir = Dir.mktmpdir
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# Setup and install the necessary dependencies in the temporary directory
|
24
|
+
#
|
25
|
+
def setup!
|
26
|
+
policyfile_path = RSpec.configuration.policyfile_path
|
27
|
+
if policyfile_path.nil?
|
28
|
+
policyfile_path = File.join(Dir.pwd, "Policyfile.rb")
|
29
|
+
end
|
30
|
+
|
31
|
+
Chef::WorkstationConfigLoader.new(nil).load
|
32
|
+
|
33
|
+
installer = ChefCLI::PolicyfileServices::Install.new(
|
34
|
+
policyfile: policyfile_path,
|
35
|
+
ui: ChefCLI::UI.null,
|
36
|
+
config: Chef::Config
|
37
|
+
)
|
38
|
+
|
39
|
+
installer.run
|
40
|
+
|
41
|
+
exporter = ChefCLI::PolicyfileServices::ExportRepo.new(
|
42
|
+
policyfile: policyfile_path,
|
43
|
+
export_dir: @tmpdir
|
44
|
+
)
|
45
|
+
|
46
|
+
FileUtils.rm_rf(@tmpdir)
|
47
|
+
exporter.run
|
48
|
+
|
49
|
+
::RSpec.configure do |config|
|
50
|
+
config.cookbook_path = [
|
51
|
+
File.join(@tmpdir, "cookbooks"),
|
52
|
+
File.join(@tmpdir, "cookbook_artifacts"),
|
53
|
+
]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# Remove the temporary directory
|
59
|
+
#
|
60
|
+
def teardown!
|
61
|
+
FileUtils.rm_rf(@tmpdir) if File.exist?(@tmpdir)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
RSpec.configure do |config|
|
67
|
+
config.before(:suite) { ChefSpec::Policyfile.setup! }
|
68
|
+
config.after(:suite) { ChefSpec::Policyfile.teardown! }
|
69
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
begin
|
2
|
+
require "chef/mixin/template"
|
3
|
+
require "chef/provider/template_finder"
|
4
|
+
rescue LoadError
|
5
|
+
end
|
6
|
+
|
7
|
+
module ChefSpec
|
8
|
+
class Renderer
|
9
|
+
include ChefSpec::Normalize
|
10
|
+
|
11
|
+
# @return [Chef::Runner]
|
12
|
+
attr_reader :chef_run
|
13
|
+
|
14
|
+
# @return [Chef::Resource]
|
15
|
+
attr_reader :resource
|
16
|
+
|
17
|
+
#
|
18
|
+
# Create a new Renderer for the given Chef run and resource.
|
19
|
+
#
|
20
|
+
# @param [Chef::Runner] chef_run
|
21
|
+
# the Chef run containing the resources
|
22
|
+
# @param [Chef::Resource] resource
|
23
|
+
# the resource to render content from
|
24
|
+
#
|
25
|
+
def initialize(chef_run, resource)
|
26
|
+
@chef_run = chef_run
|
27
|
+
@resource = resource
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# The content of the resource (this method delegates to the)
|
32
|
+
# various private rendering methods.
|
33
|
+
#
|
34
|
+
# @return [String, nil]
|
35
|
+
# the contents of the file as a string, or nil if the resource
|
36
|
+
# does not contain or respond to a content renderer.
|
37
|
+
#
|
38
|
+
def content
|
39
|
+
case resource_name(resource)
|
40
|
+
when :template
|
41
|
+
content_from_template(chef_run, resource)
|
42
|
+
when :file
|
43
|
+
content_from_file(chef_run, resource)
|
44
|
+
when :cookbook_file
|
45
|
+
content_from_cookbook_file(chef_run, resource)
|
46
|
+
else
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
#
|
54
|
+
# Compute the contents of a template using Chef's templating logic.
|
55
|
+
#
|
56
|
+
# @param [Chef::RunContext] chef_run
|
57
|
+
# the run context for the node
|
58
|
+
# @param [Chef::Provider::Template] template
|
59
|
+
# the template resource
|
60
|
+
#
|
61
|
+
# @return [String]
|
62
|
+
#
|
63
|
+
def content_from_template(chef_run, template)
|
64
|
+
cookbook_name = template.cookbook || template.cookbook_name
|
65
|
+
template_location = cookbook_collection(chef_run.node)[cookbook_name].preferred_filename_on_disk_location(chef_run.node, :templates, template.source)
|
66
|
+
|
67
|
+
if Chef::Mixin::Template.const_defined?(:TemplateContext) # Chef 11+
|
68
|
+
template_context = Chef::Mixin::Template::TemplateContext.new([])
|
69
|
+
template_context.update({
|
70
|
+
node: chef_run.node,
|
71
|
+
template_finder: template_finder(chef_run, cookbook_name),
|
72
|
+
}.merge(template.variables))
|
73
|
+
if template.respond_to?(:helper_modules) # Chef 11.4+
|
74
|
+
template_context._extend_modules(template.helper_modules)
|
75
|
+
end
|
76
|
+
template_context.render_template(template_location)
|
77
|
+
else
|
78
|
+
template.provider.new(template, chef_run.run_context).send(:render_with_context, template_location) do |file|
|
79
|
+
File.read(file.path)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
#
|
85
|
+
# Get the contents of a file resource.
|
86
|
+
#
|
87
|
+
# @param [Chef::RunContext] chef_run
|
88
|
+
# the run context for the node
|
89
|
+
# @param [Chef::Provider::File] file
|
90
|
+
# the file resource
|
91
|
+
#
|
92
|
+
# @return [String]
|
93
|
+
#
|
94
|
+
def content_from_file(chef_run, file)
|
95
|
+
file.content
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# Get the contents of a cookbook file using Chef.
|
100
|
+
#
|
101
|
+
# @param [Chef::RunContext] chef_run
|
102
|
+
# the run context for the node
|
103
|
+
# @param [Chef::Provider::CookbookFile] cookbook_file
|
104
|
+
# the file resource
|
105
|
+
#
|
106
|
+
def content_from_cookbook_file(chef_run, cookbook_file)
|
107
|
+
cookbook_name = cookbook_file.cookbook || cookbook_file.cookbook_name
|
108
|
+
cookbook = cookbook_collection(chef_run.node)[cookbook_name]
|
109
|
+
File.read(cookbook.preferred_filename_on_disk_location(chef_run.node, :files, cookbook_file.source))
|
110
|
+
end
|
111
|
+
|
112
|
+
# The cookbook collection for the current Chef run context. Handles
|
113
|
+
# the differing cases between Chef 10 and Chef 11.
|
114
|
+
#
|
115
|
+
# @param [Chef::Node] node
|
116
|
+
# the Chef node to get the cookbook collection from
|
117
|
+
#
|
118
|
+
# @return [Array<Chef::Cookbook>]
|
119
|
+
def cookbook_collection(node)
|
120
|
+
if chef_run.respond_to?(:run_context)
|
121
|
+
chef_run.run_context.cookbook_collection # Chef 11.8+
|
122
|
+
elsif node.respond_to?(:run_context)
|
123
|
+
node.run_context.cookbook_collection # Chef 11+
|
124
|
+
else
|
125
|
+
node.cookbook_collection # Chef 10
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Return a new instance of the TemplateFinder if we are running on Chef 11.
|
130
|
+
#
|
131
|
+
# @param [Chef::RunContext] chef_run
|
132
|
+
# the run context for the noe
|
133
|
+
# @param [String] cookbook_name
|
134
|
+
# the name of the cookbook
|
135
|
+
#
|
136
|
+
# @return [Chef::Provider::TemplateFinder, nil]
|
137
|
+
def template_finder(chef_run, cookbook_name)
|
138
|
+
if Chef::Provider.const_defined?(:TemplateFinder) # Chef 11+
|
139
|
+
Chef::Provider::TemplateFinder.new(chef_run.run_context, cookbook_name, chef_run.node)
|
140
|
+
else
|
141
|
+
nil
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|