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,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
|