poise 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.kitchen.travis.yml +9 -0
  4. data/.kitchen.yml +18 -0
  5. data/.rubocop.yml +2 -0
  6. data/.travis.yml +25 -0
  7. data/.yardopts +5 -0
  8. data/Berksfile +26 -0
  9. data/Berksfile.lock +10 -0
  10. data/CHANGELOG.md +58 -0
  11. data/Gemfile +32 -0
  12. data/LICENSE +202 -0
  13. data/README.md +198 -0
  14. data/Rakefile +17 -0
  15. data/lib/poise.rb +71 -0
  16. data/lib/poise/error.rb +24 -0
  17. data/lib/poise/helpers.rb +33 -0
  18. data/lib/poise/helpers/chefspec_matchers.rb +92 -0
  19. data/lib/poise/helpers/defined_in.rb +111 -0
  20. data/lib/poise/helpers/fused.rb +127 -0
  21. data/lib/poise/helpers/include_recipe.rb +62 -0
  22. data/lib/poise/helpers/inversion.rb +374 -0
  23. data/lib/poise/helpers/inversion/options_provider.rb +41 -0
  24. data/lib/poise/helpers/inversion/options_resource.rb +101 -0
  25. data/lib/poise/helpers/lazy_default.rb +62 -0
  26. data/lib/poise/helpers/lwrp_polyfill.rb +96 -0
  27. data/lib/poise/helpers/notifying_block.rb +78 -0
  28. data/lib/poise/helpers/option_collector.rb +117 -0
  29. data/lib/poise/helpers/resource_name.rb +99 -0
  30. data/lib/poise/helpers/subcontext_block.rb +58 -0
  31. data/lib/poise/helpers/subresources.rb +29 -0
  32. data/lib/poise/helpers/subresources/child.rb +217 -0
  33. data/lib/poise/helpers/subresources/container.rb +165 -0
  34. data/lib/poise/helpers/subresources/default_containers.rb +73 -0
  35. data/lib/poise/helpers/template_content.rb +165 -0
  36. data/lib/poise/provider.rb +55 -0
  37. data/lib/poise/resource.rb +75 -0
  38. data/lib/poise/subcontext.rb +27 -0
  39. data/lib/poise/subcontext/resource_collection.rb +56 -0
  40. data/lib/poise/subcontext/runner.rb +50 -0
  41. data/lib/poise/utils.rb +60 -0
  42. data/lib/poise/utils/resource_provider_mixin.rb +53 -0
  43. data/lib/poise/version.rb +20 -0
  44. data/poise.gemspec +38 -0
  45. data/test/cookbooks/poise_test/attributes/default.rb +17 -0
  46. data/test/cookbooks/poise_test/libraries/app.rb +43 -0
  47. data/test/cookbooks/poise_test/libraries/app_config.rb +46 -0
  48. data/test/cookbooks/poise_test/libraries/inversion.rb +67 -0
  49. data/test/cookbooks/poise_test/metadata.rb +18 -0
  50. data/test/cookbooks/poise_test/recipes/default.rb +25 -0
  51. data/test/cookbooks/poise_test/recipes/inversion.rb +42 -0
  52. data/test/gemfiles/chef-12.0.gemfile +18 -0
  53. data/test/gemfiles/chef-12.1.gemfile +18 -0
  54. data/test/gemfiles/chef-12.2.gemfile +18 -0
  55. data/test/gemfiles/chef-12.gemfile +18 -0
  56. data/test/gemfiles/master.gemfile +20 -0
  57. data/test/integration/default/serverspec/default_spec.rb +35 -0
  58. data/test/integration/default/serverspec/inversion_spec.rb +43 -0
  59. data/test/spec/helpers/chefspec_matchers_spec.rb +45 -0
  60. data/test/spec/helpers/defined_in_spec.rb +62 -0
  61. data/test/spec/helpers/fused_spec.rb +92 -0
  62. data/test/spec/helpers/include_recipe_spec.rb +34 -0
  63. data/test/spec/helpers/inversion/options_resource_spec.rb +212 -0
  64. data/test/spec/helpers/inversion_spec.rb +273 -0
  65. data/test/spec/helpers/lazy_default_spec.rb +74 -0
  66. data/test/spec/helpers/lwrp_polyfill_spec.rb +128 -0
  67. data/test/spec/helpers/notifying_block_spec.rb +87 -0
  68. data/test/spec/helpers/option_collector_spec.rb +82 -0
  69. data/test/spec/helpers/resource_name_spec.rb +39 -0
  70. data/test/spec/helpers/subcontext_block_spec.rb +94 -0
  71. data/test/spec/helpers/subresources/child_spec.rb +339 -0
  72. data/test/spec/helpers/subresources/container_spec.rb +175 -0
  73. data/test/spec/helpers/template_context_spec.rb +147 -0
  74. data/test/spec/poise_spec.rb +185 -0
  75. data/test/spec/provider_spec.rb +28 -0
  76. data/test/spec/resource_spec.rb +85 -0
  77. data/test/spec/spec_helper.rb +18 -0
  78. data/test/spec/utils/resource_provider_mixin_spec.rb +52 -0
  79. data/test/spec/utils_spec.rb +110 -0
  80. metadata +190 -0
@@ -0,0 +1,212 @@
1
+ #
2
+ # Copyright 2015, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'spec_helper'
18
+
19
+ describe Poise::Helpers::Inversion::OptionsResource do
20
+ let(:default_attributes) do
21
+ {'poise-test' => {}}
22
+ end
23
+ resource(:poise_test_options) do
24
+ include described_class
25
+ inversion_resource(:poise_test_inversion)
26
+ end
27
+ provider(:poise_test_options)
28
+
29
+ describe '#_options' do
30
+ context 'with simple options' do
31
+ recipe do
32
+ poise_test_options 'test' do
33
+ foo 'bar'
34
+ baz 42
35
+ end
36
+ end
37
+
38
+ it { is_expected.to run_poise_test_options('test').with(_options: {'foo' => 'bar', 'baz' => 42}) }
39
+ end # /context with simple options
40
+
41
+ context 'with node options' do
42
+ recipe do
43
+ poise_test_options 'test' do
44
+ foo 'bar'
45
+ baz node.name
46
+ end
47
+ end
48
+
49
+ it { is_expected.to run_poise_test_options('test').with(_options: {'foo' => 'bar', 'baz' => 'chefspec.local'}) }
50
+ end # /context with node options
51
+
52
+ context 'with new_resource-based options' do
53
+ resource(:poise_test) do
54
+ def foo(val=nil)
55
+ set_or_return(:foo, val, {})
56
+ end
57
+ end
58
+ provider(:poise_test) do
59
+ include Chef::DSL::Recipe
60
+ def action_run
61
+ poise_test_options new_resource.name do
62
+ foo new_resource.foo
63
+ end
64
+ end
65
+ end
66
+ recipe do
67
+ poise_test 'test' do
68
+ foo 'bar'
69
+ end
70
+ end
71
+
72
+ it { is_expected.to run_poise_test_options('test').with(_options: {'foo' => 'bar'}) }
73
+ end # /context with new_resource-based options
74
+
75
+ context 'with a bad method' do
76
+ recipe do
77
+ poise_test_options 'test' do
78
+ foo noode.name
79
+ baz 42
80
+ end
81
+ end
82
+
83
+ it { expect { subject }.to raise_error NoMethodError }
84
+ end # /context with a bad method
85
+
86
+ context 'with derived options' do
87
+ recipe do
88
+ poise_test_options 'test' do
89
+ foo node.name
90
+ baz foo + 'a'
91
+ end
92
+ end
93
+
94
+ it { is_expected.to run_poise_test_options('test').with(_options: {'foo' => 'chefspec.local', 'baz' => 'chefspec.locala'}) }
95
+ end # /context with derived options
96
+ end # /describe #_options
97
+
98
+ describe 'provider options' do
99
+ resource(:poise_test_inversion) do
100
+ include Poise::Helpers::Inversion::Resource
101
+ end
102
+ provider(:poise_test_inversion_one) do
103
+ include Poise::Helpers::Inversion::Provider
104
+ inversion_resource(:poise_test_inversion)
105
+ inversion_attribute('poise-test')
106
+ provides(:poise_test_inversion)
107
+ def self.provides_auto?(node, resource)
108
+ true
109
+ end
110
+ end
111
+ subject do
112
+ chef_run.poise_test_inversion('test').provider_for_action(:nothing).options
113
+ end
114
+
115
+ context 'before service resource' do
116
+ recipe(subject: false) do
117
+ poise_test_options 'test' do
118
+ position 'before'
119
+ end
120
+
121
+ poise_test_inversion 'test'
122
+ end
123
+
124
+ it { is_expected.to eq({'position' => 'before'}) }
125
+ end # /context before service resource
126
+
127
+ context 'after service resource' do
128
+ recipe(subject: false) do
129
+ poise_test_inversion 'test'
130
+
131
+ poise_test_options 'test' do
132
+ position 'after'
133
+ end
134
+ end
135
+
136
+ it { is_expected.to eq({'position' => 'after'}) }
137
+ end # /context after service resource
138
+
139
+ # This is a regression test because the poise-service version couldn't
140
+ # do this combination.
141
+ context 'before service resource for a provider' do
142
+ recipe(subject: false) do
143
+ poise_test_options 'test' do
144
+ for_provider :poise_test_inversion
145
+ position 'before'
146
+ end
147
+
148
+ poise_test_inversion 'test'
149
+ end
150
+
151
+ it { is_expected.to eq({'position' => 'before'}) }
152
+ end # /context before service resource for a provider
153
+
154
+ context 'after service resource for a provider' do
155
+ recipe(subject: false) do
156
+ poise_test_inversion 'test'
157
+
158
+ poise_test_options 'test' do
159
+ for_provider :poise_test_inversion
160
+ position 'after'
161
+ end
162
+ end
163
+
164
+ it { is_expected.to eq({'position' => 'after'}) }
165
+ end # /context after service resource for a provider
166
+
167
+ context 'after service resource for a non-matching provider' do
168
+ recipe(subject: false) do
169
+ poise_test_inversion 'test'
170
+
171
+ poise_test_options 'test' do
172
+ for_provider :other
173
+ position 'after'
174
+ end
175
+ end
176
+
177
+ it { is_expected.to eq({}) }
178
+ end # /context after service resource for a non-matching provider
179
+
180
+ context 'mutiple options' do
181
+ recipe(subject: false) do
182
+ poise_test_options 'test1' do
183
+ resource 'test'
184
+ position 'before'
185
+ one 1
186
+ end
187
+
188
+ poise_test_inversion 'test'
189
+
190
+ poise_test_options 'test2' do
191
+ resource 'test'
192
+ for_provider :poise_test_inversion
193
+ two 2
194
+ end
195
+
196
+ poise_test_options 'test3' do
197
+ resource 'test'
198
+ position 'after'
199
+ three 3
200
+ end
201
+
202
+ poise_test_options 'test4' do
203
+ resource 'test'
204
+ for_provider :other
205
+ four 4
206
+ end
207
+ end
208
+
209
+ it { is_expected.to eq({'position' => 'after', 'one' => 1, 'two' => 2, 'three' => 3}) }
210
+ end # /context mutiple options
211
+ end # /describe provider options
212
+ end
@@ -0,0 +1,273 @@
1
+ #
2
+ # Copyright 2015, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'spec_helper'
18
+
19
+ describe Poise::Helpers::Inversion do
20
+ describe Poise::Helpers::Inversion::Resource do
21
+ subject { resource(:poise_test_inversion).new('test', nil) }
22
+ resource(:poise_test_inversion) do
23
+ include described_class
24
+ end
25
+
26
+ describe '#options' do
27
+ context 'with no options' do
28
+ it { expect(subject.options).to be_a Mash }
29
+ it { expect(subject.options).to eq({}) }
30
+ it { expect(subject.options(:name)).to be_a Mash }
31
+ it { expect(subject.options(:name)).to eq({}) }
32
+ end # /context with no options
33
+
34
+ context 'with all-provider options' do
35
+ before { subject.options(key: 'global') }
36
+ it { expect(subject.options).to be_a Mash }
37
+ it { expect(subject.options).to eq({'key' => 'global'}) }
38
+ end # /context with all-provider options
39
+
40
+ context 'with single provider options' do
41
+ before { subject.options(:name, key: 'single') }
42
+ it { expect(subject.options(:name)).to be_a Mash }
43
+ it { expect(subject.options(:name)).to eq({'key' => 'single'}) }
44
+ end # /context with single provider options
45
+
46
+ context 'with both options' do
47
+ before do
48
+ subject.options(key: 'global')
49
+ subject.options(:name, key: 'single')
50
+ end
51
+ it { expect(subject.options).to eq({'key' => 'global'}) }
52
+ it { expect(subject.options(:name)).to eq({'key' => 'single'}) }
53
+ end # /context with both options
54
+ end # /describe #options
55
+
56
+ describe '#provider' do
57
+ context 'with no provider' do
58
+ its(:provider) { is_expected.to eq nil }
59
+ end # /context with no provider
60
+
61
+ context 'with a class provider' do
62
+ before { subject.provider(Object) }
63
+ its(:provider) { is_expected.to eq Object }
64
+ end # /context with a class provider
65
+
66
+ context 'with a symbol provider' do
67
+ let(:sentinel) do
68
+ double('provider sentinel', name: '').tap do |s|
69
+ allow(s).to receive(:kind_of?) {|klass| klass == Class }
70
+ end
71
+ end
72
+ before do
73
+ expect(Poise::Helpers::Inversion).to receive(:provider_for).with(:poise_test_inversion, nil, :invert).and_return(sentinel)
74
+ subject.provider(:invert)
75
+ end
76
+ its(:provider) { is_expected.to eq sentinel }
77
+ end # /context with a symbol provider
78
+
79
+ context 'with a symbol that is not a provider' do
80
+ before do
81
+ expect(Poise::Helpers::Inversion).to receive(:provider_for).and_return(nil)
82
+ subject.provider(:ruby_block)
83
+ end
84
+ its(:provider) { is_expected.to eq Chef::Provider::RubyBlock }
85
+ end # /context with a symbol that is not a provider
86
+ end # /describe #provider
87
+ end # /decribe Poise::Helpers::Inversion::Resource
88
+
89
+ describe Poise::Helpers::Inversion::Provider do
90
+ # Due to internals of the Halite helpers, make sure this is on its own name.
91
+ # Otherwise it can try to run provides? for resources matching the name but
92
+ # in another example.
93
+ subject(:subject_provider) { provider(:poise_test_inversion) }
94
+ provider(:poise_test_inversion) do
95
+ include described_class
96
+ inversion_resource(:poise_test_inversion)
97
+ provides(:inverted)
98
+ end
99
+
100
+ describe '.inversion_resource' do
101
+ context 'with a symbol' do
102
+ its(:inversion_resource) { is_expected.to eq :poise_test_inversion }
103
+ end # /context with a symbol
104
+
105
+ context 'with a class' do
106
+ provider(:poise_test_inversion) do
107
+ include described_class
108
+ fake_class = Class.new
109
+ def fake_class.resource_name
110
+ :poise_test_inversion
111
+ end
112
+ inversion_resource(fake_class)
113
+ end
114
+ its(:inversion_resource) { is_expected.to eq :poise_test_inversion }
115
+ end # /context with a class
116
+ end # /describe .inversion_resource
117
+
118
+ describe '.inversion_attribute' do
119
+ context 'with a string' do
120
+ provider(:poise_test_inversion) do
121
+ include described_class
122
+ inversion_resource(:poise_test_inversion)
123
+ inversion_attribute('string')
124
+ end
125
+ its(:inversion_attribute) { is_expected.to eq %w{string} }
126
+ end # /context with a string
127
+
128
+ context 'with an array' do
129
+ provider(:poise_test_inversion) do
130
+ include described_class
131
+ inversion_resource(:poise_test_inversion)
132
+ inversion_attribute([:sym1, :sym2])
133
+ end
134
+ its(:inversion_attribute) { is_expected.to eq %w{sym1 sym2} }
135
+ end # /context with an array
136
+ end # /describe .inversion_attribute
137
+
138
+ describe '.resolve_inversion_attribute' do
139
+ context 'with attributes' do
140
+ it do
141
+ expect(subject).to receive(:inversion_attribute).and_return(%w{key1 key2})
142
+ fake_node = {'key1' => {'key2' => 'value'}}
143
+ expect(subject.resolve_inversion_attribute(fake_node)).to eq 'value'
144
+ end
145
+ end # /context with attributes
146
+
147
+ context 'with non-existent attributes' do
148
+ it do
149
+ expect(subject).to receive(:inversion_attribute).and_return(%w{key1 key3})
150
+ fake_node = {'key1' => {'key2' => 'value'}}
151
+ expect { subject.resolve_inversion_attribute(fake_node) }.to raise_error(Poise::Error)
152
+ end
153
+ end # /context with non-existent attributes
154
+
155
+ context 'with fallback' do
156
+ it do
157
+ expect(subject).to receive(:inversion_attribute).and_return(nil)
158
+ fake_run_context = double('run context')
159
+ fake_node = double('node', run_context: fake_run_context)
160
+ allow(fake_node).to receive(:'[]') {|key| {'key1' => 'value'}[key] }
161
+ expect(subject).to receive(:poise_defined_in_cookbook).with(fake_run_context).and_return('key1')
162
+ expect(subject.resolve_inversion_attribute(fake_node)).to eq 'value'
163
+ end
164
+ end # /context with fallback
165
+ end # /describe .resolve_inversion_attribute
166
+
167
+ describe '.inversion_options' do
168
+ let(:attributes) { Hash.new }
169
+ let(:run_state) { Hash.new }
170
+ let(:node) { double('node', run_state: {'poise_inversion' => {poise_test_inversion: run_state}}) }
171
+ let(:new_resource) { double('new_resource', name: 'test', options: {}) }
172
+ subject { subject_provider.inversion_options(node, new_resource) }
173
+ before do
174
+ allow(subject_provider).to receive(:resolve_inversion_attribute).with(node).and_return(attributes)
175
+ end
176
+
177
+ context 'defaults' do
178
+ it { is_expected.to eq({}) }
179
+ end # /context defaults
180
+
181
+ context 'with global attributes' do
182
+ before do
183
+ attributes['provider'] = 'global'
184
+ attributes['options'] = {key: 'globalval'}
185
+ end
186
+ it { is_expected.to eq({'provider' => 'global', 'key' => 'globalval'}) }
187
+ end # /context with global attributes
188
+
189
+ context 'with global run state' do
190
+ before do
191
+ run_state['test'] = {
192
+ '*' => {provider: 'runstate', key: 'runstateval'},
193
+ }
194
+ end
195
+ it { is_expected.to eq({'provider' => 'runstate', 'key' => 'runstateval'}) }
196
+ end # /context with global run state
197
+
198
+ context 'with global resource options' do
199
+ before do
200
+ allow(new_resource).to receive(:options).with(no_args).and_return({
201
+ key: 'optionsval',
202
+ })
203
+ end
204
+ it { is_expected.to eq({'key' => 'optionsval'}) }
205
+ end # /context with global resource options
206
+
207
+ context 'with specific attributes' do
208
+ before do
209
+ attributes['test'] = {provider: 'specific', key: 'specificval'}
210
+ end
211
+ it { is_expected.to eq({'provider' => 'specific', 'key' => 'specificval'}) }
212
+ end # /context with specific attributes
213
+
214
+ context 'with specific run state' do
215
+ before do
216
+ run_state['test'] = {
217
+ 'inverted' => {provider: 'runstate', key: 'specficival'},
218
+ }
219
+ end
220
+ it { is_expected.to eq({'provider' => 'runstate', 'key' => 'specficival'}) }
221
+ end # /context with specific run state
222
+
223
+ context 'with specific resource options' do
224
+ before do
225
+ allow(new_resource).to receive(:options).with(:inverted).and_return({
226
+ key: 'optionsval',
227
+ })
228
+ end
229
+ it { is_expected.to eq({'key' => 'optionsval'}) }
230
+ end # /context with specific resource options
231
+
232
+ context 'with overlapping options' do
233
+ before do
234
+ attributes['provider'] = 'global'
235
+ attributes['options'] = {attrs: 'globalval'}
236
+ attributes['test'] = {attrs: 'specificval'}
237
+ run_state['test'] = {
238
+ '*' => {provider: 'runstate', runstate: 'runstateval'},
239
+ 'inverted' => {runstate: 'runstatespecific'},
240
+ }
241
+ allow(new_resource).to receive(:options).with(no_args).and_return({
242
+ resource: 'resourceval',
243
+ })
244
+ allow(new_resource).to receive(:options).with(:inverted).and_return({
245
+ resource: 'resourcespecific',
246
+ })
247
+ end
248
+ it { is_expected.to eq({
249
+ 'attrs'=> 'specificval',
250
+ 'resource'=> 'resourcespecific',
251
+ 'runstate'=> 'runstatespecific',
252
+ 'provider'=> 'runstate',
253
+ }) }
254
+ end # /context with overlapping options
255
+ end # /describe .inversion_options
256
+
257
+ describe '.resolve_inversion_provider' do
258
+ context 'with no options' do
259
+ it do
260
+ expect(subject).to receive(:inversion_options).and_return({})
261
+ expect(subject.resolve_inversion_provider(nil, nil)).to eq 'auto'
262
+ end
263
+ end # /context with no options
264
+
265
+ context 'with a provider' do
266
+ it do
267
+ expect(subject).to receive(:inversion_options).and_return({'provider' => 'invert'})
268
+ expect(subject.resolve_inversion_provider(nil, nil)).to eq 'invert'
269
+ end
270
+ end # /context with a provider
271
+ end # /describe .resolve_inversion_provider
272
+ end # /describe Poise::Helpers::Inversion::Provider
273
+ end