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