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,101 @@
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 'chef/mash'
18
+
19
+ require 'poise/error'
20
+
21
+
22
+ module Poise
23
+ module Helpers
24
+ module Inversion
25
+ # A mixin for inversion options resources.
26
+ #
27
+ # @api private
28
+ # @since 2.0.0
29
+ # @see Poise::Helpers::Inversion
30
+ module OptionsResource
31
+ include Poise
32
+
33
+ # Method missing delegation to allow DSL-style options.
34
+ #
35
+ # @example
36
+ # my_app_options 'app' do
37
+ # key1 'value1'
38
+ # key2 'value2'
39
+ # end
40
+ def method_missing(method_sym, *args, &block)
41
+ super(method_sym, *args, &block)
42
+ rescue NoMethodError
43
+ # First time we've seen this key and using it as an rvalue, NOPE.GIF.
44
+ raise unless !args.empty? || block || _options[method_sym]
45
+ if !args.empty? || block
46
+ _options[method_sym] = block || args.first
47
+ end
48
+ _options[method_sym]
49
+ end
50
+
51
+ # Insert the options data in to the run state. This has to match the
52
+ # layout used in {Poise::Helpers::Inversion::Provider.inversion_options}.
53
+ #
54
+ # @api private
55
+ def after_created
56
+ raise Poise::Error.new("Inversion resource name not set for #{self.class.name}") unless self.class.inversion_resource
57
+ node.run_state['poise_inversion'] ||= {}
58
+ node.run_state['poise_inversion'][self.class.inversion_resource] ||= {}
59
+ node.run_state['poise_inversion'][self.class.inversion_resource][resource] ||= {}
60
+ node.run_state['poise_inversion'][self.class.inversion_resource][resource][for_provider] ||= {}
61
+ node.run_state['poise_inversion'][self.class.inversion_resource][resource][for_provider].update(_options)
62
+ end
63
+
64
+ module ClassMethods
65
+ # @overload inversion_resource()
66
+ # Return the inversion resource name for this class.
67
+ # @return [Symbol]
68
+ # @overload inversion_resource(val)
69
+ # Set the inversion resource name for this class. You can pass either
70
+ # a symbol in DSL format or a resource class that uses Poise. This
71
+ # name is used to determine which resources the inversion provider is
72
+ # a candidate for.
73
+ # @param val [Symbol, Class] Name to set.
74
+ # @return [Symbol]
75
+ def inversion_resource(val=nil)
76
+ if val
77
+ val = val.resource_name if val.is_a?(Class)
78
+ Chef::Log.debug("[#{self.name}] Setting inversion resource to #{val}")
79
+ @poise_inversion_resource = val.to_sym
80
+ end
81
+ @poise_inversion_resource || (superclass.respond_to?(:inversion_resource) ? superclass.inversion_resource : nil)
82
+ end
83
+
84
+ # @api private
85
+ def included(klass)
86
+ super
87
+ klass.extend(ClassMethods)
88
+ klass.class_exec do
89
+ actions(:run)
90
+ attribute(:resource, kind_of: String, name_attribute: true)
91
+ attribute(:for_provider, kind_of: [String, Symbol], default: '*')
92
+ attribute(:_options, kind_of: Hash, default: lazy { Mash.new })
93
+ end
94
+ end
95
+ end
96
+
97
+ extend ClassMethods
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,62 @@
1
+ #
2
+ # Copyright 2013-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
+
18
+ module Poise
19
+ module Helpers
20
+ # Resource mixin to allow lazyily-evaluated defaults in resource attributes.
21
+ # This is designed to be used with {LWRPPolyfill} or a similar #attributes
22
+ # method.
23
+ #
24
+ # @since 1.0.0
25
+ # @example
26
+ # class MyResource < Chef::Resource
27
+ # include Poise::Helpers::LWRPPolyfill
28
+ # include Poise::Helpers::LazyDefault
29
+ # attribute(:path, default: lazy { name + '_temp' })
30
+ # end
31
+ module LazyDefault
32
+ # Override the default set_or_return to support lazy evaluation of the
33
+ # default value. This only actually matters when it is called from a class
34
+ # level context via #attributes.
35
+ def set_or_return(symbol, arg, validation)
36
+ if validation && validation[:default].is_a?(Chef::DelayedEvaluator)
37
+ validation = validation.dup
38
+ validation[:default] = instance_eval(&validation[:default])
39
+ end
40
+ super(symbol, arg, validation)
41
+ end
42
+
43
+ # @!classmethods
44
+ module ClassMethods
45
+ # Create a lazyily-evaluated block.
46
+ #
47
+ # @param block [Proc] Callable to return the default value.
48
+ # @return [Chef::DelayedEvaluator]
49
+ def lazy(&block)
50
+ Chef::DelayedEvaluator.new(&block)
51
+ end
52
+
53
+ def included(klass)
54
+ super
55
+ klass.extend(ClassMethods)
56
+ end
57
+ end
58
+
59
+ extend ClassMethods
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,96 @@
1
+ #
2
+ # Copyright 2013-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 'poise/utils/resource_provider_mixin'
18
+
19
+
20
+ module Poise
21
+ module Helpers
22
+ # A resource and provider mixin to add back some compatability with Chef's
23
+ # LWRPBase classes.
24
+ #
25
+ # @since 1.0.0
26
+ module LWRPPolyfill
27
+ include Poise::Utils::ResourceProviderMixin
28
+
29
+ # Provide default_action and actions like LWRPBase but better equipped for subclassing.
30
+ module Resource
31
+ def initialize(*args)
32
+ super
33
+ # Try to not stomp on stuff if already set in a parent
34
+ @action = self.class.default_action if @action == :nothing
35
+ (@allowed_actions << self.class.actions).flatten!.uniq!
36
+ end
37
+
38
+ # @!classmethods
39
+ module ClassMethods
40
+ def default_action(name=nil)
41
+ if name
42
+ @default_action = name
43
+ actions(name)
44
+ end
45
+ @default_action || ( respond_to?(:superclass) && superclass.respond_to?(:default_action) && superclass.default_action ) || actions.first || :nothing
46
+ end
47
+
48
+ def actions(*names)
49
+ @actions ||= ( respond_to?(:superclass) && superclass.respond_to?(:actions) ? superclass.actions.dup : [] )
50
+ (@actions << names).flatten!.uniq!
51
+ @actions
52
+ end
53
+
54
+ def attribute(name, opts)
55
+ # Ruby 1.8 can go to hell
56
+ define_method(name) do |arg=nil, &block|
57
+ arg = block if arg.nil? # Try to allow passing either
58
+ set_or_return(name, arg, opts)
59
+ end
60
+ end
61
+
62
+ def included(klass)
63
+ super
64
+ klass.extend(ClassMethods)
65
+ end
66
+ end
67
+
68
+ extend ClassMethods
69
+ end
70
+
71
+ # Helper to handle load_current_resource for direct subclasses of Provider
72
+ module Provider
73
+ # @!classmethods
74
+ module ClassMethods
75
+ def included(klass)
76
+ super
77
+ klass.extend(ClassMethods)
78
+
79
+ # Mask Chef::Provider#load_current_resource because it throws NotImplementedError.
80
+ if klass.is_a?(Class) && klass.superclass == Chef::Provider
81
+ klass.class_exec do
82
+ def load_current_resource
83
+ end
84
+ end
85
+ end
86
+
87
+ # Reinstate the Chef DSL, removed in Chef 12.
88
+ klass.class_exec { include Chef::DSL::Recipe }
89
+ end
90
+ end
91
+
92
+ extend ClassMethods
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,78 @@
1
+ #
2
+ # Copyright 2013-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 'poise/helpers/subcontext_block'
18
+ require 'poise/subcontext/runner'
19
+
20
+
21
+ module Poise
22
+ module Helpers
23
+ # A provider mixin to provide #notifying_block, a scoped form of Chef's
24
+ # use_inline_resources.
25
+ #
26
+ # @since 1.0.0
27
+ # @example
28
+ # class MyProvider < Chef::Provider
29
+ # include Chef::Helpers::NotifyingBlock
30
+ #
31
+ # def action_run
32
+ # notifying_block do
33
+ # template '/etc/myapp.conf' do
34
+ # # ...
35
+ # end
36
+ # end
37
+ # end
38
+ # end
39
+ module NotifyingBlock
40
+ include Poise::Helpers::SubcontextBlock
41
+
42
+ private
43
+
44
+ # Create and converge a subcontext for the recipe DSL. This is similar to
45
+ # Chef's use_inline_resources but is scoped to a block. All DSL resources
46
+ # declared inside the block will be converged when the block returns, and
47
+ # the updated_by_last_action flag will be set if any of the inner
48
+ # resources are updated.
49
+ #
50
+ # @api public
51
+ # @param block [Proc] Block to run in the subcontext.
52
+ # @return [void]
53
+ # @example
54
+ # def action_run
55
+ # notifying_block do
56
+ # template '/etc/myapp.conf' do
57
+ # # ...
58
+ # end
59
+ # end
60
+ # end
61
+ def notifying_block(&block)
62
+ # Make sure to mark the resource as updated-by-last-action if
63
+ # any sub-run-context resources were updated (any actual
64
+ # actions taken against the system) during the
65
+ # sub-run-context convergence.
66
+ begin
67
+ subcontext = subcontext_block(&block)
68
+ # Converge the new context.
69
+ Poise::Subcontext::Runner.new(new_resource, subcontext).converge
70
+ ensure
71
+ new_resource.updated_by_last_action(
72
+ subcontext && subcontext.resource_collection.any?(&:updated?)
73
+ )
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,117 @@
1
+ #
2
+ # Copyright 2013-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 'chef/mash'
18
+
19
+
20
+ module Poise
21
+ module Helpers
22
+ # A resource mixin to add a new kind of attribute, an option collector.
23
+ # These attributes can act as mini-DSLs for things which would otherwise be
24
+ # key/value pairs.
25
+ #
26
+ # @since 1.0.0
27
+ # @example Defining an option collector
28
+ # class MyResource < Chef::Resource
29
+ # include Poise::Helpers::OptionCollector
30
+ # attribute(:my_options, option_collector: true)
31
+ # end
32
+ # @example Using an option collector
33
+ # my_resource 'name' do
34
+ # my_options do
35
+ # key1 'value1'
36
+ # key2 'value2'
37
+ # end
38
+ # end
39
+ module OptionCollector
40
+ # Instance context used to eval option blocks.
41
+ # @api private
42
+ class OptionEvalContext
43
+ attr_reader :_options
44
+
45
+ def initialize(parent)
46
+ @parent = parent
47
+ @_options = {}
48
+ end
49
+
50
+ def method_missing(method_sym, *args, &block)
51
+ @parent.send(method_sym, *args, &block)
52
+ rescue NameError
53
+ # Even though method= in the block will set a variable instead of
54
+ # calling method_missing, still try to cope in case of self.method=.
55
+ method_sym = method_sym.to_s.chomp('=').to_sym
56
+ if args.length > 0 || block
57
+ @_options[method_sym] = args.first || block
58
+ elsif !@_options.include?(method_sym)
59
+ # We haven't seen this name before, re-raise the NameError.
60
+ raise
61
+ end
62
+ @_options[method_sym]
63
+ end
64
+ end
65
+
66
+ # @!classmethods
67
+ module ClassMethods
68
+ # Override the normal #attribute() method to support defining option
69
+ # collectors too.
70
+ def attribute(name, options={})
71
+ # If present but false-y, make sure it is removed anyway so it
72
+ # doesn't confuse ParamsValidate.
73
+ if options.delete(:option_collector)
74
+ option_collector_attribute(name, options)
75
+ else
76
+ super
77
+ end
78
+ end
79
+
80
+ # Define an option collector attribute. Normally used via {.attribute}.
81
+ #
82
+ # @param name [String, Symbol] Name of the attribute to define.
83
+ # @param default [Object] Default value for the options.
84
+ def option_collector_attribute(name, default: {})
85
+ # Unlike LWRPBase.attribute, I don't care about Ruby 1.8. Worlds tiniest violin.
86
+ define_method(name.to_sym) do |arg=nil, &block|
87
+ iv_sym = :"@#{name}"
88
+
89
+ value = instance_variable_get(iv_sym) || begin
90
+ default = instance_eval(&default) if default.is_a?(Chef::DelayedEvaluator) # Handle lazy{}
91
+ Mash.new(default) # Wrap in a mash because fuck str vs sym.
92
+ end
93
+ if arg
94
+ raise Exceptions::ValidationFailed, "Option #{name} must be a Hash" if arg && !arg.is_a?(Hash)
95
+ # Should this and the update below be a deep merge?
96
+ value.update(arg)
97
+ end
98
+ if block
99
+ ctx = OptionEvalContext.new(self)
100
+ ctx.instance_exec(&block)
101
+ value.update(ctx._options)
102
+ end
103
+ instance_variable_set(iv_sym, value)
104
+ value
105
+ end
106
+ end
107
+
108
+ def included(klass)
109
+ super
110
+ klass.extend(ClassMethods)
111
+ end
112
+ end
113
+
114
+ extend ClassMethods
115
+ end
116
+ end
117
+ end