Chef Custom Resources - Missing Documentation
The new Chef documentation for Custom Resources is pretty lackluster.
This is probably because they’re too busy making awesome stuff, but I still needed to learn some stuff the hard way.
Thought I’d share them.
The basics
There is no longer a “state-only” resource and an implementing provider. Instead, there is a single “resource” that contains both state and behaviour.
A lot of terminology has been modified, partially to differentiate the new “custom resource” from the old “lightweight resource-provider” (LWRP) class.
attribute
is now property
and the syntax is slightly different, but this is documented.
If you want to use the “custom resource” paradigm on clients lower than Chef 12.5, you could depend on the ‘compat_resource’ cookbook and it’ll work.
Actions are in the resource’s context
Actions are part of the resource (and not the provider), so self
inside the action context refers to the resource.
This means that this:
# The LWRP way - Will not work in custom resources
# Resource
attribute :path
# Provider
action :bla do
puts "Path is #{new_resource.path}"
end
One should use this:
property :path
action :bla do
puts "Path is #{path}"
end
load_current_value
This is a new way for generating the “existing” resource (to see if you need to do anything).
You define a code block, using something like this:
load_current_value do
# DETECTION LOGIC HERE
end
This code is promoted into a method:
# lib/chef/resource.rb L1135
def self.load_current_value(&load_block)
define_method(:load_current_value!, &load_block)
end
And called by Chef (more details below):
# lib/chef/resource/action_class.rb L31
def load_current_resource
if new_resource.respond_to?(:load_current_value!)
# dup the resource and then reset desired-state properties.
current_resource = new_resource.dup
# We clear desired state in the copy, because it is supposed to be actual state.
# We keep identity properties and non-desired-state, which are assumed to be
# "control" values like `recurse: true`
current_resource.class.properties.each do |name,property|
if property.desired_state? && !property.identity? && !property.name_property?
property.reset(current_resource)
end
end
# Call the actual load_current_value! method. If it raises
# CurrentValueDoesNotExist, set current_resource to `nil`.
begin
# If the user specifies load_current_value do |desired_resource|, we
# pass in the desired resource as well as the current one.
if current_resource.method(:load_current_value!).arity > 0
current_resource.load_current_value!(new_resource)
else
current_resource.load_current_value!
end
rescue Chef::Exceptions::CurrentValueDoesNotExist
current_resource = nil
end
end
@current_resource = current_resource
end
This method:
- Clones the resource
- Resets all fields that aren’t:
- Non “desired state” (
desired_state:false
), meaning they describe the way to operate rather than a property of the resource - “Identity” properties (
identity: true
), meaning they help locate the resource - “Name” properties (
name_property: true
), meaning they default to the resource’s name (likepath
in thedirectory
resource) so it doesn’t make sense to initialize them to another value
Brackets contain the decorator used to mark an attribute that way.
All other fields are cleared so that the detection code can populate them with “current” values.
One could readlib/chef/property.rb
to see some of the attribute decorators and their documentation. - Non “desired state” (
- Executes the provided code block in the current resource’s context (now defined as
load_current_value!
).
If this block accepts any arguments, pass it the resource provided by the user.
This block is run in the “current” resource’s context.
If it throws aCurrentValueDoesNotExist
exception (done by callingcurrent_value_does_not_exist!
), the discovery process is aborted and there is no “current” resource.
So in short:
- You’re supplying a code block to discover the current state of the resource
- This block is executed in the context of the “current” resoruce
- It starts with the Identity/Name/Non-desired-state properties initialized to the value in the “desired” state of the resource
- It’s up to this block to either populate the remaining fields with the current value, or call
current_value_does_not_exist!
if the resource has no current state (e.g. a package that isn’t currently installed)
current_value instead of current_resource
Because of the resource -> value
terminology changes, the current state of the resource is now accessed by current_value
instead of current_resource
like in the past.
This object can be nil
(if there is no current state for the resource) or a copy of this resource, populated with values from the load_current_value
block