chef 12.4.3 → 12.5.0.alpha.1

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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -2
  3. data/lib/chef.rb +1 -1
  4. data/lib/chef/application/solo.rb +1 -1
  5. data/lib/chef/application/windows_service_manager.rb +17 -12
  6. data/lib/chef/chef_class.rb +7 -0
  7. data/lib/chef/chef_fs/config.rb +22 -24
  8. data/lib/chef/chef_fs/file_pattern.rb +4 -15
  9. data/lib/chef/chef_fs/file_system/cookbook_dir.rb +1 -0
  10. data/lib/chef/chef_fs/knife.rb +35 -7
  11. data/lib/chef/chef_fs/path_utils.rb +65 -34
  12. data/lib/chef/constants.rb +27 -0
  13. data/lib/chef/delayed_evaluator.rb +21 -0
  14. data/lib/chef/dsl/recipe.rb +20 -2
  15. data/lib/chef/event_dispatch/base.rb +40 -16
  16. data/lib/chef/event_dispatch/dsl.rb +64 -0
  17. data/lib/chef/exceptions.rb +6 -1
  18. data/lib/chef/formatters/doc.rb +3 -1
  19. data/lib/chef/guard_interpreter/resource_guard_interpreter.rb +3 -1
  20. data/lib/chef/http/http_request.rb +1 -1
  21. data/lib/chef/knife/bootstrap/templates/chef-full.erb +1 -1
  22. data/lib/chef/knife/ssl_check.rb +3 -2
  23. data/lib/chef/knife/user_edit.rb +1 -2
  24. data/lib/chef/mixin/params_validate.rb +362 -135
  25. data/lib/chef/node.rb +19 -0
  26. data/lib/chef/platform/handler_map.rb +0 -5
  27. data/lib/chef/platform/rebooter.rb +1 -1
  28. data/lib/chef/property.rb +539 -0
  29. data/lib/chef/provider.rb +129 -12
  30. data/lib/chef/provider/deploy.rb +3 -5
  31. data/lib/chef/provider/lwrp_base.rb +1 -75
  32. data/lib/chef/provider/package.rb +1 -1
  33. data/lib/chef/provider/powershell_script.rb +32 -19
  34. data/lib/chef/provider/registry_key.rb +5 -5
  35. data/lib/chef/provider/service/macosx.rb +5 -1
  36. data/lib/chef/recipe.rb +1 -8
  37. data/lib/chef/resource.rb +499 -84
  38. data/lib/chef/resource/file/verification.rb +7 -1
  39. data/lib/chef/resource/lwrp_base.rb +1 -7
  40. data/lib/chef/run_context.rb +404 -83
  41. data/lib/chef/version.rb +1 -1
  42. data/lib/chef/win32/registry.rb +10 -2
  43. data/lib/chef/workstation_config_loader.rb +3 -158
  44. data/spec/data/run_context/cookbooks/include/recipes/default.rb +24 -0
  45. data/spec/data/run_context/cookbooks/include/recipes/includee.rb +3 -0
  46. data/spec/functional/rebooter_spec.rb +1 -1
  47. data/spec/functional/resource/{powershell_spec.rb → powershell_script_spec.rb} +3 -3
  48. data/spec/functional/win32/registry_helper_spec.rb +12 -0
  49. data/spec/functional/win32/service_manager_spec.rb +2 -2
  50. data/spec/integration/knife/chef_repo_path_spec.rb +13 -11
  51. data/spec/integration/recipes/recipe_dsl_spec.rb +0 -15
  52. data/spec/integration/recipes/resource_action_spec.rb +343 -0
  53. data/spec/spec_helper.rb +1 -0
  54. data/spec/support/shared/functional/win32_service.rb +2 -1
  55. data/spec/unit/application/solo_spec.rb +4 -3
  56. data/spec/unit/chef_class_spec.rb +23 -0
  57. data/spec/unit/chef_fs/path_util_spec.rb +108 -0
  58. data/spec/unit/event_dispatch/dsl_spec.rb +87 -0
  59. data/spec/unit/json_compat_spec.rb +4 -3
  60. data/spec/unit/knife/ssl_check_spec.rb +4 -0
  61. data/spec/unit/mixin/params_validate_spec.rb +4 -2
  62. data/spec/unit/node_spec.rb +7 -0
  63. data/spec/unit/property/state_spec.rb +506 -0
  64. data/spec/unit/property/validation_spec.rb +658 -0
  65. data/spec/unit/property_spec.rb +968 -0
  66. data/spec/unit/provider/{powershell_spec.rb → powershell_script_spec.rb} +0 -0
  67. data/spec/unit/provider/registry_key_spec.rb +12 -0
  68. data/spec/unit/provider/service/macosx_spec.rb +4 -4
  69. data/spec/unit/provider_spec.rb +1 -3
  70. data/spec/unit/recipe_spec.rb +0 -4
  71. data/spec/unit/registry_helper_spec.rb +15 -1
  72. data/spec/unit/resource/file/verification_spec.rb +33 -5
  73. data/spec/unit/resource/{powershell_spec.rb → powershell_script_spec.rb} +0 -0
  74. data/spec/unit/resource_spec.rb +2 -2
  75. data/spec/unit/run_context/child_run_context_spec.rb +133 -0
  76. data/spec/unit/run_context_spec.rb +7 -0
  77. metadata +25 -25
  78. data/spec/unit/workstation_config_loader_spec.rb +0 -283
@@ -26,6 +26,7 @@ require 'chef/mixin/powershell_out'
26
26
  require 'chef/mixin/provides'
27
27
  require 'chef/platform/service_helpers'
28
28
  require 'chef/node_map'
29
+ require 'forwardable'
29
30
 
30
31
  class Chef
31
32
  class Provider
@@ -65,6 +66,7 @@ class Chef
65
66
 
66
67
  @recipe_name = nil
67
68
  @cookbook_name = nil
69
+ self.class.include_resource_dsl_module(new_resource)
68
70
  end
69
71
 
70
72
  def whyrun_mode?
@@ -119,11 +121,11 @@ class Chef
119
121
  check_resource_semantics!
120
122
 
121
123
  # user-defined LWRPs may include unsafe load_current_resource methods that cannot be run in whyrun mode
122
- if !whyrun_mode? || whyrun_supported?
124
+ if whyrun_mode? && !whyrun_supported?
125
+ events.resource_current_state_load_bypassed(@new_resource, @action, @current_resource)
126
+ else
123
127
  load_current_resource
124
128
  events.resource_current_state_loaded(@new_resource, @action, @current_resource)
125
- elsif whyrun_mode? && !whyrun_supported?
126
- events.resource_current_state_load_bypassed(@new_resource, @action, @current_resource)
127
129
  end
128
130
 
129
131
  define_resource_requirements
@@ -136,9 +138,7 @@ class Chef
136
138
  # we can't execute the action.
137
139
  # in non-whyrun mode, this will still cause the action to be
138
140
  # executed normally.
139
- if whyrun_supported? && !requirements.action_blocked?(@action)
140
- send("action_#{@action}")
141
- elsif whyrun_mode?
141
+ if whyrun_mode? && (!whyrun_supported? || requirements.action_blocked?(@action))
142
142
  events.resource_bypassed(@new_resource, @action, self)
143
143
  else
144
144
  send("action_#{@action}")
@@ -183,6 +183,121 @@ class Chef
183
183
  Chef::ProviderResolver.new(node, resource, :nothing).provided_by?(self)
184
184
  end
185
185
 
186
+ #
187
+ # Include attributes, public and protected methods from this Resource in
188
+ # the provider.
189
+ #
190
+ # If this is set to true, delegate methods are included in the provider so
191
+ # that you can call (for example) `attrname` and it will call
192
+ # `new_resource.attrname`.
193
+ #
194
+ # The actual include does not happen until the first time the Provider
195
+ # is instantiated (so that we don't have to worry about load order issues).
196
+ #
197
+ # @param include_resource_dsl [Boolean] Whether to include resource DSL or
198
+ # not (defaults to `false`).
199
+ #
200
+ def self.include_resource_dsl(include_resource_dsl)
201
+ @include_resource_dsl = include_resource_dsl
202
+ end
203
+
204
+ # Create the resource DSL module that forwards resource methods to new_resource
205
+ #
206
+ # @api private
207
+ def self.include_resource_dsl_module(resource)
208
+ if @include_resource_dsl && !defined?(@included_resource_dsl_module)
209
+ provider_class = self
210
+ @included_resource_dsl_module = Module.new do
211
+ extend Forwardable
212
+ define_singleton_method(:to_s) { "#{resource_class} forwarder module" }
213
+ define_singleton_method(:inspect) { to_s }
214
+ dsl_methods =
215
+ resource.class.public_instance_methods +
216
+ resource.class.protected_instance_methods -
217
+ provider_class.instance_methods
218
+ def_delegators(:new_resource, *dsl_methods)
219
+ end
220
+ include @included_resource_dsl_module
221
+ end
222
+ end
223
+
224
+ # Enables inline evaluation of resources in provider actions.
225
+ #
226
+ # Without this option, any resources declared inside the Provider are added
227
+ # to the resource collection after the current position at the time the
228
+ # action is executed. Because they are added to the primary resource
229
+ # collection for the chef run, they can notify other resources outside
230
+ # the Provider, and potentially be notified by resources outside the Provider
231
+ # (but this is complicated by the fact that they don't exist until the
232
+ # provider executes). In this mode, it is impossible to correctly set the
233
+ # updated_by_last_action flag on the parent Provider resource, since it
234
+ # executes and returns before its component resources are run.
235
+ #
236
+ # With this option enabled, each action creates a temporary run_context
237
+ # with its own resource collection, evaluates the action's code in that
238
+ # context, and then converges the resources created. If any resources
239
+ # were updated, then this provider's new_resource will be marked updated.
240
+ #
241
+ # In this mode, resources created within the Provider cannot interact with
242
+ # external resources via notifies, though notifications to other
243
+ # resources within the Provider will work. Delayed notifications are executed
244
+ # at the conclusion of the provider's action, *not* at the end of the
245
+ # main chef run.
246
+ #
247
+ # This mode of evaluation is experimental, but is believed to be a better
248
+ # set of tradeoffs than the append-after mode, so it will likely become
249
+ # the default in a future major release of Chef.
250
+ #
251
+ def self.use_inline_resources
252
+ extend InlineResources::ClassMethods
253
+ include InlineResources
254
+ end
255
+
256
+ # Chef::Provider::InlineResources
257
+ # Implementation of inline resource convergence for providers. See
258
+ # Provider.use_inline_resources for a longer explanation.
259
+ #
260
+ # This code is restricted to a module so that it can be selectively
261
+ # applied to providers on an opt-in basis.
262
+ #
263
+ # @api private
264
+ module InlineResources
265
+
266
+ # Our run context is a child of the main run context; that gives us a
267
+ # whole new resource collection and notification set.
268
+ def initialize(resource, run_context)
269
+ super(resource, run_context.create_child)
270
+ end
271
+
272
+ # Class methods for InlineResources. Overrides the `action` DSL method
273
+ # with one that enables inline resource convergence.
274
+ #
275
+ # @api private
276
+ module ClassMethods
277
+ # Defines an action method on the provider, running the block to
278
+ # compile the resources, converging them, and then checking if any
279
+ # were updated (and updating new-resource if so)
280
+ def action(name, &block)
281
+ class_eval <<-EOM, __FILE__, __LINE__+1
282
+ def action_#{name}
283
+ return_value = compile_action_#{name}
284
+ Chef::Runner.new(run_context).converge
285
+ return_value
286
+ ensure
287
+ if run_context.resource_collection.any? {|r| r.updated? }
288
+ new_resource.updated_by_last_action(true)
289
+ end
290
+ end
291
+ EOM
292
+ # We put the action in its own method so that super() works.
293
+ define_method("compile_action_#{name}", &block)
294
+ end
295
+ end
296
+
297
+ require 'chef/dsl/recipe'
298
+ include Chef::DSL::Recipe::FullDSL
299
+ end
300
+
186
301
  protected
187
302
 
188
303
  def converge_actions
@@ -200,12 +315,14 @@ class Chef
200
315
  # manipulating notifies.
201
316
 
202
317
  converge_by ("evaluate block and run any associated actions") do
203
- saved_run_context = @run_context
204
- @run_context = @run_context.dup
205
- @run_context.resource_collection = Chef::ResourceCollection.new
206
- instance_eval(&block)
207
- Chef::Runner.new(@run_context).converge
208
- @run_context = saved_run_context
318
+ saved_run_context = run_context
319
+ begin
320
+ @run_context = run_context.create_child
321
+ instance_eval(&block)
322
+ Chef::Runner.new(run_context).converge
323
+ ensure
324
+ @run_context = saved_run_context
325
+ end
209
326
  end
210
327
  end
211
328
 
@@ -373,11 +373,9 @@ class Chef
373
373
  end
374
374
 
375
375
  def gem_resource_collection_runner
376
- gems_collection = Chef::ResourceCollection.new
377
- gem_packages.each { |rbgem| gems_collection.insert(rbgem) }
378
- gems_run_context = run_context.dup
379
- gems_run_context.resource_collection = gems_collection
380
- Chef::Runner.new(gems_run_context)
376
+ child_context = run_context.create_child
377
+ gem_packages.each { |rbgem| child_context.resource_collection.insert(rbgem) }
378
+ Chef::Runner.new(child_context)
381
379
  end
382
380
 
383
381
  def gem_packages
@@ -28,52 +28,10 @@ class Chef
28
28
  # Base class from which LWRP providers inherit.
29
29
  class LWRPBase < Provider
30
30
 
31
- # Chef::Provider::LWRPBase::InlineResources
32
- # Implementation of inline resource convergence for LWRP providers. See
33
- # Provider::LWRPBase.use_inline_resources for a longer explanation.
34
- #
35
- # This code is restricted to a module so that it can be selectively
36
- # applied to providers on an opt-in basis.
37
- module InlineResources
38
-
39
- # Class methods for InlineResources. Overrides the `action` DSL method
40
- # with one that enables inline resource convergence.
41
- module ClassMethods
42
- # Defines an action method on the provider, using
43
- # recipe_eval_with_update_check to execute the given block.
44
- def action(name, &block)
45
- define_method("action_#{name}") do
46
- recipe_eval_with_update_check(&block)
47
- end
48
- end
49
- end
50
-
51
- # Executes the given block in a temporary run_context with its own
52
- # resource collection. After the block is executed, any resources
53
- # declared inside are converged, and if any are updated, the
54
- # new_resource will be marked updated.
55
- def recipe_eval_with_update_check(&block)
56
- saved_run_context = @run_context
57
- temp_run_context = @run_context.dup
58
- @run_context = temp_run_context
59
- @run_context.resource_collection = Chef::ResourceCollection.new
60
-
61
- return_value = instance_eval(&block)
62
- Chef::Runner.new(@run_context).converge
63
- return_value
64
- ensure
65
- @run_context = saved_run_context
66
- if temp_run_context.resource_collection.any? {|r| r.updated? }
67
- new_resource.updated_by_last_action(true)
68
- end
69
- end
70
-
71
- end
72
-
73
31
  include Chef::DSL::Recipe
74
32
 
75
33
  # These were previously provided by Chef::Mixin::RecipeDefinitionDSLCore.
76
- # They are not included by its replacment, Chef::DSL::Recipe, but
34
+ # They are not included by its replacement, Chef::DSL::Recipe, but
77
35
  # they may be used in existing LWRPs.
78
36
  include Chef::DSL::PlatformIntrospection
79
37
  include Chef::DSL::DataQuery
@@ -122,38 +80,6 @@ class Chef
122
80
  provider_class
123
81
  end
124
82
 
125
- # Enables inline evaluation of resources in provider actions.
126
- #
127
- # Without this option, any resources declared inside the LWRP are added
128
- # to the resource collection after the current position at the time the
129
- # action is executed. Because they are added to the primary resource
130
- # collection for the chef run, they can notify other resources outside
131
- # the LWRP, and potentially be notified by resources outside the LWRP
132
- # (but this is complicated by the fact that they don't exist until the
133
- # provider executes). In this mode, it is impossible to correctly set the
134
- # updated_by_last_action flag on the parent LWRP resource, since it
135
- # executes and returns before its component resources are run.
136
- #
137
- # With this option enabled, each action creates a temporary run_context
138
- # with its own resource collection, evaluates the action's code in that
139
- # context, and then converges the resources created. If any resources
140
- # were updated, then this provider's new_resource will be marked updated.
141
- #
142
- # In this mode, resources created within the LWRP cannot interact with
143
- # external resources via notifies, though notifications to other
144
- # resources within the LWRP will work. Delayed notifications are executed
145
- # at the conclusion of the provider's action, *not* at the end of the
146
- # main chef run.
147
- #
148
- # This mode of evaluation is experimental, but is believed to be a better
149
- # set of tradeoffs than the append-after mode, so it will likely become
150
- # the default in a future major release of Chef.
151
- #
152
- def use_inline_resources
153
- extend InlineResources::ClassMethods
154
- include InlineResources
155
- end
156
-
157
83
  # DSL for defining a provider's actions.
158
84
  def action(name, &block)
159
85
  define_method("action_#{name}") do
@@ -142,7 +142,7 @@ class Chef
142
142
  def action_remove
143
143
  if removing_package?
144
144
  description = @new_resource.version ? "version #{@new_resource.version} of " : ""
145
- converge_by("remove #{description} package #{@current_resource.package_name}") do
145
+ converge_by("remove #{description}package #{@current_resource.package_name}") do
146
146
  remove_package(@current_resource.package_name, @new_resource.version)
147
147
  Chef::Log.info("#{@new_resource} removed")
148
148
  end
@@ -30,8 +30,8 @@ class Chef
30
30
  end
31
31
 
32
32
  def action_run
33
- valid_syntax = validate_script_syntax!
34
- super if valid_syntax
33
+ validate_script_syntax!
34
+ super
35
35
  end
36
36
 
37
37
  def flags
@@ -62,30 +62,43 @@ class Chef
62
62
  def validate_script_syntax!
63
63
  interpreter_arguments = default_interpreter_flags.join(' ')
64
64
  Tempfile.open(['chef_powershell_script-user-code', '.ps1']) do | user_script_file |
65
- user_script_file.puts("{#{@new_resource.code}}")
66
- user_script_file.close
65
+ # Wrap the user's code in a PowerShell script block so that
66
+ # it isn't executed. However, syntactically invalid script
67
+ # in that block will still trigger a syntax error which is
68
+ # exactly what we want here -- verify the syntax without
69
+ # actually running the script.
70
+ user_code_wrapped_in_powershell_script_block = <<-EOH
71
+ {
72
+ #{@new_resource.code}
73
+ }
74
+ EOH
75
+ user_script_file.puts user_code_wrapped_in_powershell_script_block
67
76
 
77
+ # A .close or explicit .flush required to ensure the file is
78
+ # written to the file system at this point, which is required since
79
+ # the intent is to execute the code just written to it.
80
+ user_script_file.close
68
81
  validation_command = "\"#{interpreter}\" #{interpreter_arguments} -Command #{user_script_file.path}"
69
82
 
70
- # For consistency with other script resources, allow even syntax errors
71
- # to be suppressed if the returns attribute would have suppressed it
72
- # at converge.
73
- valid_returns = [0]
74
- specified_returns = @new_resource.returns.is_a?(Integer) ?
75
- [@new_resource.returns] :
76
- @new_resource.returns
77
- valid_returns.concat([1]) if specified_returns.include?(1)
78
-
79
- result = shell_out!(validation_command, {returns: valid_returns})
80
- result.exitstatus == 0
83
+ # Note that other script providers like bash allow syntax errors
84
+ # to be suppressed by setting 'returns' to a value that the
85
+ # interpreter would return as a status code in the syntax
86
+ # error case. We explicitly don't do this here -- syntax
87
+ # errors will not be suppressed, since doing so could make
88
+ # it harder for users to detect / debug invalid scripts.
89
+
90
+ # Therefore, the only return value for a syntactically valid
91
+ # script is 0. If an exception is raised by shellout, this
92
+ # means a non-zero return and thus a syntactically invalid script.
93
+ shell_out!(validation_command, {returns: [0]})
81
94
  end
82
95
  end
83
96
 
84
97
  def default_interpreter_flags
85
- # 'Bypass' is preferable since it doesn't require user input confirmation
86
- # for files such as PowerShell modules downloaded from the
87
- # Internet. However, 'Bypass' is not supported prior to
88
- # PowerShell 3.0, so the fallback is 'Unrestricted'
98
+ # Execution policy 'Bypass' is preferable since it doesn't require
99
+ # user input confirmation for files such as PowerShell modules
100
+ # downloaded from the Internet. However, 'Bypass' is not supported
101
+ # prior to PowerShell 3.0, so the fallback is 'Unrestricted'
89
102
  execution_policy = Chef::Platform.supports_powershell_execution_bypass?(run_context.node) ? 'Bypass' : 'Unrestricted'
90
103
 
91
104
  [
@@ -64,7 +64,7 @@ class Chef
64
64
 
65
65
  def values_to_hash(values)
66
66
  if values
67
- @name_hash = Hash[values.map { |val| [val[:name], val] }]
67
+ @name_hash = Hash[values.map { |val| [val[:name].downcase, val] }]
68
68
  else
69
69
  @name_hash = {}
70
70
  end
@@ -100,8 +100,8 @@ class Chef
100
100
  end
101
101
  end
102
102
  @new_resource.unscrubbed_values.each do |value|
103
- if @name_hash.has_key?(value[:name])
104
- current_value = @name_hash[value[:name]]
103
+ if @name_hash.has_key?(value[:name].downcase)
104
+ current_value = @name_hash[value[:name].downcase]
105
105
  unless current_value[:type] == value[:type] && current_value[:data] == value[:data]
106
106
  converge_by("set value #{value}") do
107
107
  registry.set_value(@new_resource.key, value)
@@ -122,7 +122,7 @@ class Chef
122
122
  end
123
123
  end
124
124
  @new_resource.unscrubbed_values.each do |value|
125
- unless @name_hash.has_key?(value[:name])
125
+ unless @name_hash.has_key?(value[:name].downcase)
126
126
  converge_by("create value #{value}") do
127
127
  registry.set_value(@new_resource.key, value)
128
128
  end
@@ -133,7 +133,7 @@ class Chef
133
133
  def action_delete
134
134
  if registry.key_exists?(@new_resource.key)
135
135
  @new_resource.unscrubbed_values.each do |value|
136
- if @name_hash.has_key?(value[:name])
136
+ if @name_hash.has_key?(value[:name].downcase)
137
137
  converge_by("delete value #{value}") do
138
138
  registry.delete_value(@new_resource.key, value)
139
139
  end
@@ -42,6 +42,10 @@ class Chef
42
42
 
43
43
  PLIST_DIRS = gather_plist_dirs
44
44
 
45
+ def this_version_or_newer?(this_version)
46
+ Gem::Version.new(node['platform_version']) >= Gem::Version.new(this_version)
47
+ end
48
+
45
49
  def load_current_resource
46
50
  @current_resource = Chef::Resource::MacosxService.new(@new_resource.name)
47
51
  @current_resource.service_name(@new_resource.service_name)
@@ -56,7 +60,7 @@ class Chef
56
60
  @console_user = Etc.getlogin
57
61
  Chef::Log.debug("#{new_resource} console_user: '#{@console_user}'")
58
62
  cmd = "su "
59
- param = !node['platform_version'].include?('10.10') ? '-l ' : ''
63
+ param = this_version_or_newer?('10.10') ? '' : '-l '
60
64
  @base_user_cmd = cmd + param + "#{@console_user} -c"
61
65
  # Default LauchAgent session should be Aqua
62
66
  @session_type = 'Aqua' if @session_type.nil?
@@ -36,14 +36,7 @@ class Chef
36
36
  # A Recipe object is the context in which Chef recipes are evaluated.
37
37
  class Recipe
38
38
 
39
- include Chef::DSL::DataQuery
40
- include Chef::DSL::PlatformIntrospection
41
- include Chef::DSL::IncludeRecipe
42
- include Chef::DSL::Recipe
43
- include Chef::DSL::RegistryHelper
44
- include Chef::DSL::RebootPending
45
- include Chef::DSL::Audit
46
- include Chef::DSL::Powershell
39
+ include Chef::DSL::Recipe::FullDSL
47
40
 
48
41
  include Chef::Mixin::FromFile
49
42
  include Chef::Mixin::Deprecation
@@ -58,8 +58,6 @@ class Chef
58
58
  include Chef::Mixin::ShellOut
59
59
  include Chef::Mixin::PowershellOut
60
60
 
61
- NULL_ARG = Object.new
62
-
63
61
  #
64
62
  # The node the current Chef run is using.
65
63
  #
@@ -103,7 +101,7 @@ class Chef
103
101
  # @param run_context The context of the Chef run. Corresponds to #run_context.
104
102
  #
105
103
  def initialize(name, run_context=nil)
106
- name(name)
104
+ name(name) unless name.nil?
107
105
  @run_context = run_context
108
106
  @noop = nil
109
107
  @before = nil
@@ -132,37 +130,27 @@ class Chef
132
130
  end
133
131
 
134
132
  #
135
- # The name of this particular resource.
136
- #
137
- # This special resource attribute is set automatically from the declaration
138
- # of the resource, e.g.
139
- #
140
- # execute 'Vitruvius' do
141
- # command 'ls'
142
- # end
143
- #
144
- # Will set the name to "Vitruvius".
145
- #
146
- # This is also used in to_s to show the resource name, e.g. `execute[Vitruvius]`.
133
+ # The list of properties defined on this resource.
147
134
  #
148
- # This is also used for resource notifications and subscribes in the same manner.
135
+ # Everything defined with `property` is in this list.
149
136
  #
150
- # This will coerce any object into a string via #to_s. Arrays are a special case
151
- # so that `package ["foo", "bar"]` becomes package[foo, bar] instead of the more
152
- # awkward `package[["foo", "bar"]]` that #to_s would produce.
137
+ # @param include_superclass [Boolean] `true` to include properties defined
138
+ # on superclasses; `false` or `nil` to return the list of properties
139
+ # directly on this class.
153
140
  #
154
- # @param name [Object] The name to set, typically a String or Array
155
- # @return [String] The name of this Resource.
141
+ # @return [Hash<Symbol,Property>] The list of property names and types.
156
142
  #
157
- def name(name=nil)
158
- if !name.nil?
159
- if name.is_a?(Array)
160
- @name = name.join(', ')
143
+ def self.properties(include_superclass=true)
144
+ @properties ||= {}
145
+ if include_superclass
146
+ if superclass.respond_to?(:properties)
147
+ superclass.properties.merge(@properties)
161
148
  else
162
- @name = name.to_s
149
+ @properties.dup
163
150
  end
151
+ else
152
+ @properties
164
153
  end
165
- @name
166
154
  end
167
155
 
168
156
  #
@@ -182,8 +170,7 @@ class Chef
182
170
  end
183
171
  @action = arg
184
172
  else
185
- # Pull the action from the class if it's not set
186
- @action || self.class.default_action
173
+ @action
187
174
  end
188
175
  end
189
176
 
@@ -478,13 +465,21 @@ class Chef
478
465
  #
479
466
  # Get the value of the state attributes in this resource as a hash.
480
467
  #
468
+ # Does not include properties that are not set (unless they are identity
469
+ # properties).
470
+ #
481
471
  # @return [Hash{Symbol => Object}] A Hash of attribute => value for the
482
472
  # Resource class's `state_attrs`.
473
+ #
483
474
  def state_for_resource_reporter
484
- self.class.state_attrs.inject({}) do |state_attrs, attr_name|
485
- state_attrs[attr_name] = send(attr_name)
486
- state_attrs
475
+ state = {}
476
+ state_properties = self.class.state_properties
477
+ state_properties.each do |property|
478
+ if property.identity? || property.is_set?(self)
479
+ state[property.name] = send(property.name)
480
+ end
487
481
  end
482
+ state
488
483
  end
489
484
 
490
485
  #
@@ -497,17 +492,22 @@ class Chef
497
492
  alias_method :state, :state_for_resource_reporter
498
493
 
499
494
  #
500
- # The value of the identity attribute, if declared. Falls back to #name if
501
- # no identity attribute is declared.
495
+ # The value of the identity of this resource.
496
+ #
497
+ # - If there are no identity properties on the resource, `name` is returned.
498
+ # - If there is exactly one identity property on the resource, it is returned.
499
+ # - If there are more than one, they are returned in a hash.
502
500
  #
503
- # @return The value of the identity attribute.
501
+ # @return [Object,Hash<Symbol,Object>] The identity of this resource.
504
502
  #
505
503
  def identity
506
- if identity_attr = self.class.identity_attr
507
- send(identity_attr)
508
- else
509
- name
504
+ result = {}
505
+ identity_properties = self.class.identity_properties
506
+ identity_properties.each do |property|
507
+ result[property.name] = send(property.name)
510
508
  end
509
+ return result.values.first if identity_properties.size == 1
510
+ result
511
511
  end
512
512
 
513
513
  #
@@ -529,9 +529,7 @@ class Chef
529
529
  #
530
530
  # Equivalent to #ignore_failure.
531
531
  #
532
- def epic_fail(arg=nil)
533
- ignore_failure(arg)
534
- end
532
+ alias :epic_fail :ignore_failure
535
533
 
536
534
  #
537
535
  # Make this resource into an exact (shallow) copy of the other resource.
@@ -686,66 +684,389 @@ class Chef
686
684
  #
687
685
  # The provider class for this resource.
688
686
  #
687
+ # If `action :x do ... end` has been declared on this resource or its
688
+ # superclasses, this will return the `action_provider_class`.
689
+ #
689
690
  # If this is not set, `provider_for_action` will dynamically determine the
690
691
  # provider.
691
692
  #
692
693
  # @param arg [String, Symbol, Class] Sets the provider class for this resource.
693
694
  # If passed a String or Symbol, e.g. `:file` or `"file"`, looks up the
694
695
  # provider based on the name.
696
+ #
695
697
  # @return The provider class for this resource.
696
698
  #
699
+ # @see Chef::Resource.action_provider_class
700
+ #
697
701
  def provider(arg=nil)
698
702
  klass = if arg.kind_of?(String) || arg.kind_of?(Symbol)
699
703
  lookup_provider_constant(arg)
700
704
  else
701
705
  arg
702
706
  end
703
- set_or_return(:provider, klass, kind_of: [ Class ])
707
+ set_or_return(:provider, klass, kind_of: [ Class ]) ||
708
+ self.class.action_provider_class
704
709
  end
705
710
  def provider=(arg)
706
711
  provider(arg)
707
712
  end
708
713
 
709
- # Set or return the list of "state attributes" implemented by the Resource
710
- # subclass. State attributes are attributes that describe the desired state
711
- # of the system, such as file permissions or ownership. In general, state
712
- # attributes are attributes that could be populated by examining the state
713
- # of the system (e.g., File.stat can tell you the permissions on an
714
- # existing file). Contrarily, attributes that are not "state attributes"
715
- # usually modify the way Chef itself behaves, for example by providing
716
- # additional options for a package manager to use when installing a
717
- # package.
714
+ #
715
+ # Create a property on this resource class.
716
+ #
717
+ # If a superclass has this property, or if this property has already been
718
+ # defined by this resource, this will *override* the previous value.
719
+ #
720
+ # @param name [Symbol] The name of the property.
721
+ # @param type [Object,Array<Object>] The type(s) of this property.
722
+ # If present, this is prepended to the `is` validation option.
723
+ # @param options [Hash<Symbol,Object>] Validation options.
724
+ # @option options [Object,Array] :is An object, or list of
725
+ # objects, that must match the value using Ruby's `===` operator
726
+ # (`options[:is].any? { |v| v === value }`).
727
+ # @option options [Object,Array] :equal_to An object, or list
728
+ # of objects, that must be equal to the value using Ruby's `==`
729
+ # operator (`options[:is].any? { |v| v == value }`)
730
+ # @option options [Regexp,Array<Regexp>] :regex An object, or
731
+ # list of objects, that must match the value with `regex.match(value)`.
732
+ # @option options [Class,Array<Class>] :kind_of A class, or
733
+ # list of classes, that the value must be an instance of.
734
+ # @option options [Hash<String,Proc>] :callbacks A hash of
735
+ # messages -> procs, all of which match the value. The proc must
736
+ # return a truthy or falsey value (true means it matches).
737
+ # @option options [Symbol,Array<Symbol>] :respond_to A method
738
+ # name, or list of method names, the value must respond to.
739
+ # @option options [Symbol,Array<Symbol>] :cannot_be A property,
740
+ # or a list of properties, that the value cannot have (such as `:nil` or
741
+ # `:empty`). The method with a questionmark at the end is called on the
742
+ # value (e.g. `value.empty?`). If the value does not have this method,
743
+ # it is considered valid (i.e. if you don't respond to `empty?` we
744
+ # assume you are not empty).
745
+ # @option options [Proc] :coerce A proc which will be called to
746
+ # transform the user input to canonical form. The value is passed in,
747
+ # and the transformed value returned as output. Lazy values will *not*
748
+ # be passed to this method until after they are evaluated. Called in the
749
+ # context of the resource (meaning you can access other properties).
750
+ # @option options [Boolean] :required `true` if this property
751
+ # must be present; `false` otherwise. This is checked after the resource
752
+ # is fully initialized.
753
+ # @option options [Boolean] :name_property `true` if this
754
+ # property defaults to the same value as `name`. Equivalent to
755
+ # `default: lazy { name }`, except that #property_is_set? will
756
+ # return `true` if the property is set *or* if `name` is set.
757
+ # @option options [Boolean] :name_attribute Same as `name_property`.
758
+ # @option options [Object] :default The value this property
759
+ # will return if the user does not set one. If this is `lazy`, it will
760
+ # be run in the context of the instance (and able to access other
761
+ # properties).
762
+ # @option options [Boolean] :desired_state `true` if this property is
763
+ # part of desired state. Defaults to `true`.
764
+ # @option options [Boolean] :identity `true` if this property
765
+ # is part of object identity. Defaults to `false`.
766
+ #
767
+ # @example Bare property
768
+ # property :x
769
+ #
770
+ # @example With just a type
771
+ # property :x, String
772
+ #
773
+ # @example With just options
774
+ # property :x, default: 'hi'
775
+ #
776
+ # @example With type and options
777
+ # property :x, String, default: 'hi'
778
+ #
779
+ def self.property(name, type=NOT_PASSED, **options)
780
+ name = name.to_sym
781
+
782
+ options[:instance_variable_name] = :"@#{name}" if !options.has_key?(:instance_variable_name)
783
+ options.merge!(name: name, declared_in: self)
784
+
785
+ if type == NOT_PASSED
786
+ # If a type is not passed, the property derives from the
787
+ # superclass property (if any)
788
+ if properties.has_key?(name)
789
+ property = properties[name].derive(**options)
790
+ else
791
+ property = property_type(**options)
792
+ end
793
+
794
+ # If a Property is specified, derive a new one from that.
795
+ elsif type.is_a?(Property) || (type.is_a?(Class) && type <= Property)
796
+ property = type.derive(**options)
797
+
798
+ # If a primitive type was passed, combine it with "is"
799
+ else
800
+ if options[:is]
801
+ options[:is] = ([ type ] + [ options[:is] ]).flatten(1)
802
+ else
803
+ options[:is] = type
804
+ end
805
+ property = property_type(**options)
806
+ end
807
+
808
+ local_properties = properties(false)
809
+ local_properties[name] = property
810
+
811
+ property.emit_dsl
812
+ end
813
+
814
+ #
815
+ # Create a reusable property type that can be used in multiple properties
816
+ # in different resources.
817
+ #
818
+ # @param options [Hash<Symbol,Object>] Validation options. see #property for
819
+ # the list of options.
820
+ #
821
+ # @example
822
+ # property_type(default: 'hi')
823
+ #
824
+ def self.property_type(**options)
825
+ Property.derive(**options)
826
+ end
827
+
828
+ #
829
+ # The name of this particular resource.
830
+ #
831
+ # This special resource attribute is set automatically from the declaration
832
+ # of the resource, e.g.
833
+ #
834
+ # execute 'Vitruvius' do
835
+ # command 'ls'
836
+ # end
837
+ #
838
+ # Will set the name to "Vitruvius".
839
+ #
840
+ # This is also used in to_s to show the resource name, e.g. `execute[Vitruvius]`.
841
+ #
842
+ # This is also used for resource notifications and subscribes in the same manner.
843
+ #
844
+ # This will coerce any object into a string via #to_s. Arrays are a special case
845
+ # so that `package ["foo", "bar"]` becomes package[foo, bar] instead of the more
846
+ # awkward `package[["foo", "bar"]]` that #to_s would produce.
847
+ #
848
+ # @param name [Object] The name to set, typically a String or Array
849
+ # @return [String] The name of this Resource.
850
+ #
851
+ property :name, String, coerce: proc { |v| v.is_a?(Array) ? v.join(', ') : v.to_s }, desired_state: false
852
+
853
+ #
854
+ # Whether this property has been set (or whether it has a default that has
855
+ # been retrieved).
856
+ #
857
+ # @param name [Symbol] The name of the property.
858
+ # @return [Boolean] `true` if the property has been set.
859
+ #
860
+ def property_is_set?(name)
861
+ property = self.class.properties[name.to_sym]
862
+ raise ArgumentError, "Property #{name} is not defined in class #{self}" if !property
863
+ property.is_set?(self)
864
+ end
865
+
866
+ #
867
+ # Clear this property as if it had never been set. It will thereafter return
868
+ # the default.
869
+ # been retrieved).
870
+ #
871
+ # @param name [Symbol] The name of the property.
872
+ #
873
+ def reset_property(name)
874
+ property = self.class.properties[name.to_sym]
875
+ raise ArgumentError, "Property #{name} is not defined in class #{self}" if !property
876
+ property.reset(self)
877
+ end
878
+
879
+ #
880
+ # Create a lazy value for assignment to a default value.
881
+ #
882
+ # @param block The block to run when the value is retrieved.
883
+ #
884
+ # @return [Chef::DelayedEvaluator] The lazy value
885
+ #
886
+ def self.lazy(&block)
887
+ DelayedEvaluator.new(&block)
888
+ end
889
+
890
+ #
891
+ # Get or set the list of desired state properties for this resource.
892
+ #
893
+ # State properties are properties that describe the desired state
894
+ # of the system, such as file permissions or ownership.
895
+ # In general, state properties are properties that could be populated by
896
+ # examining the state of the system (e.g., File.stat can tell you the
897
+ # permissions on an existing file). Contrarily, properties that are not
898
+ # "state properties" usually modify the way Chef itself behaves, for example
899
+ # by providing additional options for a package manager to use when
900
+ # installing a package.
718
901
  #
719
902
  # This list is used by the Chef client auditing system to extract
720
903
  # information from resources to describe changes made to the system.
721
- def self.state_attrs(*attr_names)
722
- @state_attrs ||= []
723
- @state_attrs = attr_names unless attr_names.empty?
904
+ #
905
+ # This method is unnecessary when declaring properties with `property`;
906
+ # properties are added to state_properties by default, and can be turned off
907
+ # with `desired_state: false`.
908
+ #
909
+ # ```ruby
910
+ # property :x # part of desired state
911
+ # property :y, desired_state: false # not part of desired state
912
+ # ```
913
+ #
914
+ # @param names [Array<Symbol>] A list of property names to set as desired
915
+ # state.
916
+ #
917
+ # @return [Array<Property>] All properties in desired state.
918
+ #
919
+ def self.state_properties(*names)
920
+ if !names.empty?
921
+ names = names.map { |name| name.to_sym }.uniq
724
922
 
725
- # Return *all* state_attrs that this class has, including inherited ones
726
- if superclass.respond_to?(:state_attrs)
727
- superclass.state_attrs + @state_attrs
728
- else
729
- @state_attrs
923
+ local_properties = properties(false)
924
+ # Add new properties to the list.
925
+ names.each do |name|
926
+ property = properties[name]
927
+ if !property
928
+ self.property name, instance_variable_name: false, desired_state: true
929
+ elsif !property.desired_state?
930
+ self.property name, desired_state: true
931
+ end
932
+ end
933
+
934
+ # If state_attrs *excludes* something which is currently desired state,
935
+ # mark it as desired_state: false.
936
+ local_properties.each do |name,property|
937
+ if property.desired_state? && !names.include?(name)
938
+ self.property name, desired_state: false
939
+ end
940
+ end
730
941
  end
942
+
943
+ properties.values.select { |property| property.desired_state? }
731
944
  end
732
945
 
733
- # Set or return the "identity attribute" for this resource class. This is
734
- # generally going to be the "name attribute" for this resource. In other
735
- # words, the resource type plus this attribute uniquely identify a given
736
- # bit of state that chef manages. For a File resource, this would be the
737
- # path, for a package resource, it will be the package name. This will show
738
- # up in chef-client's audit records as a searchable field.
739
- def self.identity_attr(attr_name=nil)
740
- @identity_attr ||= nil
741
- @identity_attr = attr_name if attr_name
946
+ #
947
+ # Set or return the list of "state properties" implemented by the Resource
948
+ # subclass.
949
+ #
950
+ # Equivalent to calling #state_properties and getting `state_properties.keys`.
951
+ #
952
+ # @deprecated Use state_properties.keys instead. Note that when you declare
953
+ # properties with `property`: properties are added to state_properties by
954
+ # default, and can be turned off with `desired_state: false`
955
+ #
956
+ # ```ruby
957
+ # property :x # part of desired state
958
+ # property :y, desired_state: false # not part of desired state
959
+ # ```
960
+ #
961
+ # @param names [Array<Symbol>] A list of property names to set as desired
962
+ # state.
963
+ #
964
+ # @return [Array<Symbol>] All property names with desired state.
965
+ #
966
+ def self.state_attrs(*names)
967
+ state_properties(*names).map { |property| property.name }
968
+ end
742
969
 
743
- # If this class doesn't have an identity attr, we'll defer to the superclass:
744
- if @identity_attr || !superclass.respond_to?(:identity_attr)
745
- @identity_attr
746
- else
747
- superclass.identity_attr
970
+ #
971
+ # Set the identity of this resource to a particular set of properties.
972
+ #
973
+ # This drives #identity, which returns data that uniquely refers to a given
974
+ # resource on the given node (in such a way that it can be correlated
975
+ # across Chef runs).
976
+ #
977
+ # This method is unnecessary when declaring properties with `property`;
978
+ # properties can be added to identity during declaration with
979
+ # `identity: true`.
980
+ #
981
+ # ```ruby
982
+ # property :x, identity: true # part of identity
983
+ # property :y # not part of identity
984
+ # ```
985
+ #
986
+ # If no properties are marked as identity, "name" is considered the identity.
987
+ #
988
+ # @param names [Array<Symbol>] A list of property names to set as the identity.
989
+ #
990
+ # @return [Array<Property>] All identity properties.
991
+ #
992
+ def self.identity_properties(*names)
993
+ if !names.empty?
994
+ names = names.map { |name| name.to_sym }
995
+
996
+ # Add or change properties that are not part of the identity.
997
+ names.each do |name|
998
+ property = properties[name]
999
+ if !property
1000
+ self.property name, instance_variable_name: false, identity: true
1001
+ elsif !property.identity?
1002
+ self.property name, identity: true
1003
+ end
1004
+ end
1005
+
1006
+ # If identity_properties *excludes* something which is currently part of
1007
+ # the identity, mark it as identity: false.
1008
+ properties.each do |name,property|
1009
+ if property.identity? && !names.include?(name)
1010
+ self.property name, identity: false
1011
+ end
1012
+ end
748
1013
  end
1014
+
1015
+ result = properties.values.select { |property| property.identity? }
1016
+ result = [ properties[:name] ] if result.empty?
1017
+ result
1018
+ end
1019
+
1020
+ #
1021
+ # Set the identity of this resource to a particular property.
1022
+ #
1023
+ # This drives #identity, which returns data that uniquely refers to a given
1024
+ # resource on the given node (in such a way that it can be correlated
1025
+ # across Chef runs).
1026
+ #
1027
+ # This method is unnecessary when declaring properties with `property`;
1028
+ # properties can be added to identity during declaration with
1029
+ # `identity: true`.
1030
+ #
1031
+ # ```ruby
1032
+ # property :x, identity: true # part of identity
1033
+ # property :y # not part of identity
1034
+ # ```
1035
+ #
1036
+ # @param name [Symbol] A list of property names to set as the identity.
1037
+ #
1038
+ # @return [Symbol] The identity property if there is only one; or `nil` if
1039
+ # there are more than one.
1040
+ #
1041
+ # @raise [ArgumentError] If no arguments are passed and the resource has
1042
+ # more than one identity property.
1043
+ #
1044
+ def self.identity_property(name=nil)
1045
+ result = identity_properties(*Array(name))
1046
+ if result.size > 1
1047
+ raise Chef::Exceptions::MultipleIdentityError, "identity_property cannot be called on an object with more than one identity property (#{result.map { |r| r.name }.join(", ")})."
1048
+ end
1049
+ result.first
1050
+ end
1051
+
1052
+ #
1053
+ # Set a property as the "identity attribute" for this resource.
1054
+ #
1055
+ # Identical to calling #identity_property.first.key.
1056
+ #
1057
+ # @param name [Symbol] The name of the property to set.
1058
+ #
1059
+ # @return [Symbol]
1060
+ #
1061
+ # @deprecated `identity_property` should be used instead.
1062
+ #
1063
+ # @raise [ArgumentError] If no arguments are passed and the resource has
1064
+ # more than one identity property.
1065
+ #
1066
+ def self.identity_attr(name=nil)
1067
+ property = identity_property(name)
1068
+ return nil if !property
1069
+ property.name
749
1070
  end
750
1071
 
751
1072
  #
@@ -771,8 +1092,8 @@ class Chef
771
1092
  # have.
772
1093
  #
773
1094
  attr_accessor :allowed_actions
774
- def allowed_actions(value=NULL_ARG)
775
- if value != NULL_ARG
1095
+ def allowed_actions(value=NOT_PASSED)
1096
+ if value != NOT_PASSED
776
1097
  self.allowed_actions = value
777
1098
  end
778
1099
  @allowed_actions
@@ -906,9 +1227,9 @@ class Chef
906
1227
  #
907
1228
  # @return [Symbol] The name of this resource type (e.g. `:execute`).
908
1229
  #
909
- def self.resource_name(name=NULL_ARG)
1230
+ def self.resource_name(name=NOT_PASSED)
910
1231
  # Setter
911
- if name != NULL_ARG
1232
+ if name != NOT_PASSED
912
1233
  remove_canonical_dsl
913
1234
 
914
1235
  # Set the resource_name and call provides
@@ -923,13 +1244,25 @@ class Chef
923
1244
  @resource_name = nil
924
1245
  end
925
1246
  end
926
-
927
1247
  @resource_name
928
1248
  end
929
1249
  def self.resource_name=(name)
930
1250
  resource_name(name)
931
1251
  end
932
1252
 
1253
+ #
1254
+ # Use the class name as the resource name.
1255
+ #
1256
+ # Munges the last part of the class name from camel case to snake case,
1257
+ # and sets the resource_name to that:
1258
+ #
1259
+ # A::B::BlahDBlah -> blah_d_blah
1260
+ #
1261
+ def self.use_automatic_resource_name
1262
+ automatic_name = convert_to_snake_case(self.name.split('::')[-1])
1263
+ resource_name automatic_name
1264
+ end
1265
+
933
1266
  #
934
1267
  # The module where Chef should look for providers for this resource.
935
1268
  # The provider for `MyResource` will be looked up using
@@ -986,8 +1319,8 @@ class Chef
986
1319
  #
987
1320
  # @return [Array<Symbol>] The default actions for the resource.
988
1321
  #
989
- def self.default_action(action_name=NULL_ARG)
990
- unless action_name.equal?(NULL_ARG)
1322
+ def self.default_action(action_name=NOT_PASSED)
1323
+ unless action_name.equal?(NOT_PASSED)
991
1324
  @default_action = Array(action_name).map(&:to_sym)
992
1325
  self.allowed_actions |= @default_action
993
1326
  end
@@ -1001,9 +1334,90 @@ class Chef
1001
1334
  end
1002
1335
  end
1003
1336
  def self.default_action=(action_name)
1004
- default_action(action_name)
1337
+ default_action action_name
1338
+ end
1339
+
1340
+ #
1341
+ # Define an action on this resource.
1342
+ #
1343
+ # The action is defined as a *recipe* block that will be compiled and then
1344
+ # converged when the action is taken (when Resource is converged). The recipe
1345
+ # has access to the resource's attributes and methods, as well as the Chef
1346
+ # recipe DSL.
1347
+ #
1348
+ # Resources in the action recipe may notify and subscribe to other resources
1349
+ # within the action recipe, but cannot notify or subscribe to resources
1350
+ # in the main Chef run.
1351
+ #
1352
+ # Resource actions are *inheritable*: if resource A defines `action :create`
1353
+ # and B is a subclass of A, B gets all of A's actions. Additionally,
1354
+ # resource B can define `action :create` and call `super()` to invoke A's
1355
+ # action code.
1356
+ #
1357
+ # The first action defined (besides `:nothing`) will become the default
1358
+ # action for the resource.
1359
+ #
1360
+ # @param name [Symbol] The action name to define.
1361
+ # @param recipe_block The recipe to run when the action is taken. This block
1362
+ # takes no parameters, and will be evaluated in a new context containing:
1363
+ #
1364
+ # - The resource's public and protected methods (including attributes)
1365
+ # - The Chef Recipe DSL (file, etc.)
1366
+ # - super() referring to the parent version of the action (if any)
1367
+ #
1368
+ # @return The Action class implementing the action
1369
+ #
1370
+ def self.action(action, &recipe_block)
1371
+ action = action.to_sym
1372
+ new_action_provider_class.action(action, &recipe_block)
1373
+ self.allowed_actions += [ action ]
1374
+ default_action action if Array(default_action) == [:nothing]
1005
1375
  end
1006
1376
 
1377
+ #
1378
+ # The action provider class is an automatic `Provider` created to handle
1379
+ # actions declared by `action :x do ... end`.
1380
+ #
1381
+ # This class will be returned by `resource.provider` if `resource.provider`
1382
+ # is not set. `provider_for_action` will also use this instead of calling
1383
+ # out to `Chef::ProviderResolver`.
1384
+ #
1385
+ # If the user has not declared actions on this class or its superclasses
1386
+ # using `action :x do ... end`, then there is no need for this class and
1387
+ # `action_provider_class` will be `nil`.
1388
+ #
1389
+ # @api private
1390
+ #
1391
+ def self.action_provider_class
1392
+ @action_provider_class ||
1393
+ # If the superclass needed one, then we need one as well.
1394
+ if superclass.respond_to?(:action_provider_class) && superclass.action_provider_class
1395
+ new_action_provider_class
1396
+ end
1397
+ end
1398
+
1399
+ #
1400
+ # Ensure the action provider class actually gets created. This is called
1401
+ # when the user does `action :x do ... end`.
1402
+ #
1403
+ # @api private
1404
+ def self.new_action_provider_class
1405
+ return @action_provider_class if @action_provider_class
1406
+
1407
+ if superclass.respond_to?(:action_provider_class)
1408
+ base_provider = superclass.action_provider_class
1409
+ end
1410
+ base_provider ||= Chef::Provider
1411
+
1412
+ resource_class = self
1413
+ @action_provider_class = Class.new(base_provider) do
1414
+ use_inline_resources
1415
+ include_resource_dsl true
1416
+ define_singleton_method(:to_s) { "#{resource_class} action provider" }
1417
+ define_singleton_method(:inspect) { to_s }
1418
+ define_method(:load_current_resource) {}
1419
+ end
1420
+ end
1007
1421
 
1008
1422
  #
1009
1423
  # Internal Resource Interface (for Chef)
@@ -1076,7 +1490,7 @@ class Chef
1076
1490
 
1077
1491
  class << self
1078
1492
  # back-compat
1079
- # NOTE: that we do not support unregistering classes as descendents like
1493
+ # NOTE: that we do not support unregistering classes as descendants like
1080
1494
  # we used to for LWRP unloading because that was horrible and removed in
1081
1495
  # Chef-12.
1082
1496
  # @deprecated
@@ -1202,7 +1616,8 @@ class Chef
1202
1616
  end
1203
1617
 
1204
1618
  def provider_for_action(action)
1205
- provider = Chef::ProviderResolver.new(node, self, action).resolve.new(self, run_context)
1619
+ provider_class = Chef::ProviderResolver.new(node, self, action).resolve
1620
+ provider = provider_class.new(self, run_context)
1206
1621
  provider.action = action
1207
1622
  provider
1208
1623
  end