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,17 @@
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_boiler/rakefile'
@@ -0,0 +1,71 @@
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/provider'
18
+ require 'chef/resource'
19
+
20
+ require 'poise/utils/resource_provider_mixin'
21
+
22
+
23
+ module Poise
24
+ include Poise::Utils::ResourceProviderMixin
25
+ autoload :Helpers, 'poise/helpers'
26
+ autoload :Provider, 'poise/provider'
27
+ autoload :Resource, 'poise/resource'
28
+ autoload :Subcontext, 'poise/subcontext'
29
+ autoload :Utils, 'poise/utils'
30
+ autoload :VERSION, 'poise/version'
31
+ end
32
+
33
+ # Callable form to allow passing in options:
34
+ # include Poise(ParentResource)
35
+ # include Poise(parent: ParentResource)
36
+ # include Poise(container: true)
37
+ def Poise(options={})
38
+ # Allow passing a class as a shortcut
39
+ if options.is_a?(Class)
40
+ options = {parent: options}
41
+ end
42
+
43
+ # Create a new anonymous module
44
+ mod = Module.new
45
+
46
+ # Fake the name.
47
+ mod.define_singleton_method(:name) do
48
+ super() || 'Poise'
49
+ end
50
+
51
+ mod.define_singleton_method(:included) do |klass|
52
+ super(klass)
53
+ # Pull in the main helper to cover most of the needed logic.
54
+ klass.class_exec { include Poise }
55
+ # Set the defined_in values as needed.
56
+ klass.poise_defined!(caller)
57
+ # Resource-specific options.
58
+ if klass < Chef::Resource
59
+ klass.poise_subresource(options[:parent], options[:parent_optional], options[:parent_auto]) if options[:parent]
60
+ klass.poise_subresource_container(options[:container_namespace]) if options[:container]
61
+ klass.poise_fused if options[:fused]
62
+ klass.poise_inversion(options[:inversion_options_resource]) if options[:inversion]
63
+ end
64
+ # Provider-specific options.
65
+ if klass < Chef::Provider
66
+ klass.poise_inversion(options[:inversion], options[:inversion_attribute]) if options[:inversion]
67
+ end
68
+ end
69
+
70
+ mod
71
+ end
@@ -0,0 +1,24 @@
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
+
18
+ module Poise
19
+ # Base exception class for Poise errors.
20
+ #
21
+ # @since 2.0.0
22
+ class Error < Exception
23
+ end
24
+ end
@@ -0,0 +1,33 @@
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
+
18
+ module Poise
19
+ module Helpers
20
+ autoload :ChefspecMatchers, 'poise/helpers/chefspec_matchers'
21
+ autoload :DefinedIn, 'poise/helpers/defined_in'
22
+ autoload :Fused, 'poise/helpers/fused'
23
+ autoload :IncludeRecipe, 'poise/helpers/include_recipe'
24
+ autoload :Inversion, 'poise/helpers/inversion'
25
+ autoload :LazyDefault, 'poise/helpers/lazy_default'
26
+ autoload :LWRPPolyfill, 'poise/helpers/lwrp_polyfill'
27
+ autoload :NotifyingBlock, 'poise/helpers/notifying_block'
28
+ autoload :OptionCollector, 'poise/helpers/option_collector'
29
+ autoload :ResourceName, 'poise/helpers/resource_name'
30
+ autoload :Subresources, 'poise/helpers/subresources'
31
+ autoload :TemplateContent, 'poise/helpers/template_content'
32
+ end
33
+ end
@@ -0,0 +1,92 @@
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
+ begin
18
+ require 'chefspec'
19
+ require 'rspec/expectations'
20
+ rescue LoadError
21
+ # Don't panic! We will no-op later on if these aren't available.
22
+ end
23
+
24
+ require 'poise/helpers/lwrp_polyfill'
25
+ require 'poise/helpers/resource_name'
26
+
27
+
28
+ module Poise
29
+ module Helpers
30
+ # A resource mixin to register ChefSpec matchers for a resource
31
+ # automatically.
32
+ #
33
+ # If you are using the provides() form for naming resources, ensure that is
34
+ # set before declaring actions.
35
+ #
36
+ # @since 2.0.0
37
+ # @example Define a class
38
+ # class Chef::Resource::MyResource < Chef::Resource
39
+ # include Poise::Helpers::ChefspecMatchers
40
+ # actions(:run)
41
+ # end
42
+ # @example Use a matcher
43
+ # expect(chef_run).to run_my_resource('...')
44
+ module ChefspecMatchers
45
+ include Poise::Helpers::LWRPPolyfill::Resource
46
+ include Poise::Helpers::ResourceName
47
+
48
+ # Create a matcher for a given resource type and action. This is
49
+ # idempotent so if a matcher already exists, it will not be recreated.
50
+ #
51
+ # @api private
52
+ def self.create_matcher(resource, action)
53
+ # Check that we have everything we need.
54
+ return unless defined?(ChefSpec) && defined?(RSpec::Matchers) && resource
55
+ method = :"#{action}_#{resource}"
56
+ return if RSpec::Matchers.method_defined?(method)
57
+ RSpec::Matchers.send(:define_method, method) do |resource_name|
58
+ ChefSpec::Matchers::ResourceMatcher.new(resource, action, resource_name)
59
+ end
60
+ end
61
+
62
+ # @!classmethods
63
+ module ClassMethods
64
+ # Create a resource-level matcher for this resource.
65
+ #
66
+ # @see Resource::ResourceName.provides
67
+ def provides(name)
68
+ ChefSpec.define_matcher(name) if defined?(ChefSpec)
69
+ super
70
+ end
71
+
72
+ # Create matchers for all declared actions.
73
+ #
74
+ # @see Resource::LWRPPolyfill.actions
75
+ def actions(*names)
76
+ super.tap do |actions|
77
+ actions.each do |action|
78
+ ChefspecMatchers.create_matcher(resource_name, action)
79
+ end
80
+ end
81
+ end
82
+
83
+ def included(klass)
84
+ super
85
+ klass.extend ClassMethods
86
+ end
87
+ end
88
+
89
+ extend ClassMethods
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,111 @@
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 'poise/error'
18
+ require 'poise/utils'
19
+
20
+
21
+ module Poise
22
+ module Helpers
23
+ # A mixin to track where a resource or provider was defined. This can
24
+ # provide either the filename of the class or the cookbook it was defined in.
25
+ #
26
+ # @since 2.0.0
27
+ # @example
28
+ # class MyProvider < Chef::provider
29
+ # include Poise::Helpers::DefinedIn
30
+ #
31
+ # def action_create
32
+ # template '...' do
33
+ # # ...
34
+ # cookbook new_resource.poise_defined_in
35
+ # end
36
+ # end
37
+ # end
38
+ module DefinedIn
39
+ # Wrapper for {.poise_defined_in_cookbook} to pass the run context for you.
40
+ #
41
+ # @see .poise_defined_in_cookbook
42
+ # @param file [String, nil] Optional file path to check instead of the path
43
+ # this class was defined in.
44
+ # @return [String]
45
+ def poise_defined_in_cookbook(file=nil)
46
+ self.class.poise_defined_in_cookbook(run_context, file)
47
+ end
48
+
49
+ # @!classmethods
50
+ module ClassMethods
51
+ # The file this class or module was defined in, or nil if it isn't found.
52
+ #
53
+ # @return [String]
54
+ def poise_defined_in
55
+ raise Poise::Error.new("Unable to determine location of #{self.name}") unless @poise_defined_in
56
+ @poise_defined_in
57
+ end
58
+
59
+ # The cookbook this class or module was defined in. Can pass a file to
60
+ # check that instead.
61
+ #
62
+ # @param run_context [Chef::RunContext] Run context to check cookbooks in.
63
+ # @param file [String, nil] Optional file path to check instead of the
64
+ # path this class was defined in.
65
+ # @return [String]
66
+ def poise_defined_in_cookbook(run_context, file=nil)
67
+ file ||= poise_defined_in
68
+ Chef::Log.debug("[#{self.name}] Checking cookbook name for #{file}")
69
+ Poise::Utils.find_cookbook_name(run_context, file).tap do |cookbook|
70
+ Chef::Log.debug("[#{self.name}] found cookbook #{cookbook.inspect}")
71
+ end
72
+ end
73
+
74
+ # Record that the class/module was defined. Called automatically by Ruby
75
+ # for all normal cases.
76
+ #
77
+ # @param caller_array [Array<String>] A strack trace returned by #caller.
78
+ # @return [void]
79
+ def poise_defined!(caller_array)
80
+ # Only try to set this once.
81
+ return if @poise_defined_in
82
+ # Path to ignore, assumes Halite transformation which I'm not thrilled
83
+ # about.
84
+ poise_libraries = File.expand_path('../..', __FILE__)
85
+ # Parse out just the filenames.
86
+ caller_array = caller_array.map {|line| line.split(/:/, 2).first }
87
+ # Find the first non-poise line.
88
+ caller_path = caller_array.find do |line|
89
+ !line.start_with?(poise_libraries)
90
+ end
91
+ Chef::Log.debug("[#{self.name}] Recording poise_defined_in as #{caller_path}")
92
+ @poise_defined_in = caller_path
93
+ end
94
+
95
+ # @api private
96
+ def inherited(klass)
97
+ super
98
+ klass.poise_defined!(caller)
99
+ end
100
+
101
+ def included(klass)
102
+ super
103
+ klass.extend(ClassMethods)
104
+ klass.poise_defined!(caller)
105
+ end
106
+ end
107
+
108
+ extend ClassMethods
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,127 @@
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/provider'
18
+
19
+
20
+ module Poise
21
+ module Helpers
22
+ # Resource mixin to create "fused" resources where the resource and provider
23
+ # are implemented in the same class.
24
+ #
25
+ # @since 2.0.0
26
+ # @example
27
+ # class Chef::Resource::MyResource < Chef::Resource
28
+ # include Poise(fused: true)
29
+ # attribute(:path, kind_of: String)
30
+ # attribute(:message, kind_of: String)
31
+ # action(:run) do
32
+ # file new_resource.path do
33
+ # content new_resource.message
34
+ # end
35
+ # end
36
+ # end
37
+ module Fused
38
+ # Hack is_a? so that the DSL will consider this a Provider for the
39
+ # purposes of attaching enclosing_provider.
40
+ #
41
+ # @api private
42
+ # @param klass [Class]
43
+ # @return [Boolean]
44
+ def is_a?(klass)
45
+ if klass == Chef::Provider
46
+ # Lies, damn lies, and Ruby code.
47
+ true
48
+ else
49
+ super
50
+ end
51
+ end
52
+
53
+ # Hack provider_for_action so that the resource is also the provider.
54
+ #
55
+ # @api private
56
+ # @param action [Symbol]
57
+ # @return [Chef::Provider]
58
+ def provider_for_action(action)
59
+ provider(self.class.fused_provider_class) unless provider
60
+ super
61
+ end
62
+
63
+ # @!classmethods
64
+ module ClassMethods
65
+ # Define a provider action. The block should contain the usual provider
66
+ # code.
67
+ #
68
+ # @param name [Symbol] Name of the action.
69
+ # @param block [Proc] Action implementation.
70
+ # @example
71
+ # action(:run) do
72
+ # file '/temp' do
73
+ # user 'root'
74
+ # content 'temp'
75
+ # end
76
+ # end
77
+ def action(name, &block)
78
+ fused_actions[name.to_sym] = block
79
+ # Make sure this action is allowed, also sets the default if first.
80
+ if respond_to?(:actions)
81
+ actions(name.to_sym)
82
+ end
83
+ end
84
+
85
+ # Storage accessor for fused action blocks. Maps action name to proc.
86
+ #
87
+ # @api private
88
+ # @return [Hash<Symbol, Proc>]
89
+ def fused_actions
90
+ (@fused_actions ||= {})
91
+ end
92
+
93
+ # Create a provider class for the fused actions in this resource.
94
+ # Inherits from the fused provider class of the resource's superclass if
95
+ # present.
96
+ #
97
+ # @api private
98
+ # @return [Class]
99
+ def fused_provider_class
100
+ @fused_provider_class ||= begin
101
+ provider_superclass = begin
102
+ self.superclass.fused_provider_class
103
+ rescue NoMethodError
104
+ Chef::Provider
105
+ end
106
+ actions = fused_actions
107
+ class_name = self.name
108
+ Class.new(provider_superclass) do
109
+ include Poise
110
+ define_singleton_method(:name) { class_name + ' (fused)' }
111
+ actions.each do |action, block|
112
+ define_method(:"action_#{action}", &block)
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ def included(klass)
119
+ super
120
+ klass.extend(ClassMethods)
121
+ end
122
+ end
123
+
124
+ extend ClassMethods
125
+ end
126
+ end
127
+ end