poise 2.0.0

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