Custom Resource DSL
Use the Custom Resource DSL to define behaviors within custom resources, such as:
- Loading the value of a specific property
- Comparing the current property value against a desired property value
- Telling Chef Infra Client when and how to make changes
action_class
The converge_if_changed
method may be used multiple times. The following example shows how to use the converge_if_changed
method to compare the multiple desired property values against the current property values (as loaded by the load_current_value
method).
property :path, String
property :content, String
property :mode, String
load_current_value do |desired|
if ::File.exist?(desired.path)
content IO.read(desired.path)
mode ::File.stat(desired.path).mode
end
end
action :create do
converge_if_changed :content do
IO.write(new_resource.path, new_resource.content)
end
converge_if_changed :mode do
::File.chmod(new_resource.mode, new_resource.path)
end
end
where
-
load_current_value
loads the property values for bothcontent
andmode
- A
converge_if_changed
block tests onlycontent
- A
converge_if_changed
block tests onlymode
Chef Infra Client will only update the property values that require updates and will not make changes when the property values are already in the desired state
converge_if_changed
Use the converge_if_changed
method inside an action
block in a custom resource to compare the desired property values against the current property values (as loaded by the load_current_value
method). Use the converge_if_changed
method to ensure that updates only occur when property values on the system are not the desired property values and to otherwise prevent a resource from being converged.
To use the converge_if_changed
method, wrap it around the part of a recipe or custom resource that should only be converged when the current state is not the desired state:
action :some_action do
converge_if_changed do
# some property
end
end
For example, a custom resource defines two properties (content
and path
) and a single action (:create
). Use the load_current_value
method to load the property value to be compared, and then use the converge_if_changed
method to tell Chef Infra Client what to do if that value is not the desired value:
property :content, String
property :path, String, name_property: true
load_current_value do
if ::File.exist?(path)
content IO.read(path)
end
end
action :create do
converge_if_changed do
IO.write(new_resource.path, new_resource.content)
end
end
When the file does not exist, the IO.write(new_resource.path, new_resource.content)
code is executed and the Chef Infra Client output will print something similar to:
Recipe: recipe_name::block
* resource_name[blah] action create
- update my_file[blah]
- set content to "hola mundo" (was "hello world")
Converging Multiple Properties
The converge_if_changed
method may be used multiple times. The following example shows how to use the converge_if_changed
method to compare the multiple desired property values against the current property values (as loaded by the load_current_value
method).
property :path, String
property :content, String
property :mode, String
load_current_value do |desired|
if ::File.exist?(desired.path)
content IO.read(desired.path)
mode ::File.stat(desired.path).mode
end
end
action :create do
converge_if_changed :content do
IO.write(new_resource.path, new_resource.content)
end
converge_if_changed :mode do
::File.chmod(new_resource.mode, new_resource.path)
end
end
where
-
load_current_value
loads the property values for bothcontent
andmode
- A
converge_if_changed
block tests onlycontent
- A
converge_if_changed
block tests onlymode
Chef Infra Client will only update the property values that require updates and will not make changes when the property values are already in the desired state
default_action
The default action in a custom resource is, by default, the first action listed in the custom resource. For example, action aaaaa
is the default resource:
property :property_name, RubyType, default: 'value'
...
action :aaaaa do
# the first action listed in the custom resource
end
action :bbbbb do
# the second action listed in the custom resource
end
The default_action
method may also be used to specify the default action. For example:
property :property_name, RubyType, default: 'value'
default_action :aaaaa
action :aaaaa do
# the first action listed in the custom resource
end
action :bbbbb do
# the second action listed in the custom resource
end
defines action aaaaa
as the default action. If default_action :bbbbb
is specified, then action bbbbb
is the default action. Use this method for clarity in custom resources, if deliberately stating the default resource is desired, or to specify a default action that is not listed first in the custom resource.
load_current_value
Use the load_current_value
method to load the specified property values from the node, and then use those values when the resource is converged. This method may take a block argument.
property :path, String
property :content, String
property :mode, String
load_current_value do |new_resource|
if ::File.exist?(new_resource.path)
content IO.read(new_resource.path)
mode ::File.stat(new_resource.path).mode
end
end
Use the load_current_value
method to guard against property values being replaced. For example:
property :homepage, String
property :page_not_found, String
load_current_value do
if ::File.exist?('/var/www/html/index.html')
homepage IO.read('/var/www/html/index.html')
end
if ::File.exist?('/var/www/html/404.html')
page_not_found IO.read('/var/www/html/404.html')
end
end
This ensures the values for homepage
and page_not_found
are not changed to the default values when Chef Infra Client configures the node.
Block Arguments
Any properties that are marked identity: true
, desired_state: false
, or name_property: true
will be directly available from load_current_value
. If access to other properties of a resource is needed, use a block argument with load_current_value. The block argument will have the values of the requested resource. For example:
// Property is directly available example
property :action, String, name_property: true
property :content, String
load_current_value do |desired|
puts "The user requested action = #{action} in the resource"
puts "The user typed content = #{desired.content} in the resource"
end
// Block argument example
property :action, String
property :content, String
load_current_value do |desired|
puts "The user requested action = #{desired.action} in the resource"
puts "The user typed content = #{desired.content} in the resource"
end
property
Use the property
method to define properties for the custom resource. The syntax is:
property :property_name, ruby_type, default: 'value', parameter: 'value'
where
-
:property_name
is the name of the property -
ruby_type
is the optional Ruby type or array of types, such asString
,Integer
,true
, orfalse
-
default: 'value'
is the optional default value loaded into the resource -
parameter: 'value'
optional parameters
For example, the following properties define username
and password
properties with no default values specified:
property :username, String
property :password, String
ruby_type
The property ruby_type is a positional parameter. Use to ensure a property value is of a particular ruby class, such as true
, false
, nil
, String
, Array
, Hash
, Integer
, Symbol
. Use an array of ruby classes to allow a value to be of more than one type. For example:
property :aaaa, String
property :bbbb, Integer
property :cccc, Hash
property :dddd, [true, false]
property :eeee, [String, nil]
property :ffff, [Class, String, Symbol]
property :gggg, [Array, Hash]
validators
A validation parameter is used to add zero (or more) validation parameters to a property.
Parameter | Description |
---|---|
|
Use to define a collection of unique keys and values (a ruby hash) for which the key is the error message and the value is a lambda to validate the parameter. For example:
|
|
Use to specify the default value for a property. For example:
|
|
Use to match a value with
|
|
Use to match a value to a regular expression. For example:
|
|
Indicates that a property is required. For example:
|
|
Use to ensure that a value has a given method. This can be a single method name or an array of method names. For example:
|
Some examples of combining validation parameters:
property :spool_name, String, regex: /$\w+/
property :enabled, equal_to: [true, false, 'true', 'false'], default: true
desired_state
Add desired_state:
to set the desired state property for a resource. This value may be true
or false
, and all properties default to true.
- When
true
, the state of the property is determined by the state of the system - When
false
, the value of the property impacts how the resource executes, but it is not determined by the state of the system.
For example, if you were to write a resource to create volumes on a cloud provider you would need define properties such as volume_name
, volume_size
, and volume_region
. The state of these properties would determine if your resource needed to converge or not. For the resource to function you would also need to define properties such as cloud_login
and cloud_password
. These are necessary properties for interacting with the cloud provider, but their state has no impact on decision to converge the resource or not, so you would set desired_state
to false
for these properties.
property :volume_name, String
property :volume_size, Integer
property :volume_region, String
property :cloud_login, String, desired_state: false
property :cloud_password, String, desired_state: false
identity
Add identity:
to set a resource to a particular set of properties. This value may be true
or false
.
- When
true
, data for that property is returned as part of the resource data set and may be available to external applications, such as reporting - When
false
, no data for that property is returned.
If no properties are marked true
, the property that defaults to the name
of the resource is marked true
.
For example, the following properties define username
and password
properties with no default values specified, but with identity
set to true
for the user name:
property :username, String, identity: true
property :password, String
Working With Properties
The Custom Resource DSL includes several helper methods for accessing and manipulating the values of properties defined within a custom resource.
new_resource.property
Custom resources are designed to use core resources that are built into Chef. In some cases, it may be necessary to specify a property in the custom resource that is the same as a property in a core resource, for the purpose of overriding that property when used with the custom resource. For example:
property :command, String, name_property: true
property :version, String
# Useful properties from the `execute` resource
property :cwd, String
property :environment, Hash, default: {}
property :user, [String, Integer]
property :sensitive, [true, false], default: false
prefix = '/opt/languages/node'
load_current_value do
current_value_does_not_exist! if node.run_state['nodejs'].nil?
version node.run_state['nodejs'][:version]
end
action :run do
execute 'execute-node' do
cwd cwd
environment environment
user user
sensitive sensitive
# gsub replaces 10+ spaces at the beginning of the line with nothing
command <<-CODE.gsub(/^ {10}/, '')
#{prefix}/#{new_resource.version}/#{command}
CODE
end
end
where the property :cwd
, property :environment
, property :user
, and property :sensitive
are identical to properties in the execute resource, embedded as part of the action :run
action. Because both the custom properties and the execute properties are identical, this will result in an error message similar to:
ArgumentError
-------------
wrong number of arguments (0 for 1)
To prevent this behavior, use new_resource.
to tell Chef Infra Client to process the properties from the core resource instead of the properties in the custom resource. For example:
property :command, String, name_property: true
property :version, String
# Useful properties from the `execute` resource
property :cwd, String
property :environment, Hash, default: {}
property :user, [String, Integer]
property :sensitive, [true, false], default: false
prefix = '/opt/languages/node'
load_current_value do
current_value_does_not_exist! if node.run_state['nodejs'].nil?
version node.run_state['nodejs'][:version]
end
action :run do
execute 'execute-node' do
cwd new_resource.cwd
environment new_resource.environment
user new_resource.user
sensitive new_resource.sensitive
# gsub replaces 10+ spaces at the beginning of the line with nothing
command <<-CODE.gsub(/^ {10}/, '')
#{prefix}/#{new_resource.version}/#{new_resource.command}
CODE
end
end
where cwd new_resource.cwd
, environment new_resource.environment
, user new_resource.user
, and sensitive new_resource.sensitive
correctly use the properties of the execute resource and not the identically-named override properties of the custom resource.
property_is_set?
Use the property_is_set?
method to check if the value for a property is set. The syntax is:
property_is_set?(:property_name)
The property_is_set?
method will return true
if the property is set.
For example, the following custom resource creates and/or updates user properties, but not their password. The property_is_set?
method checks if the user has specified a password and then tells Chef Infra Client what to do if the password is not identical:
action :create do
converge_if_changed do
shell_out!("rabbitmqctl create_or_update_user #{username} --prop1 #{prop1} ... ")
end
if property_is_set?(:password)
if shell_out("rabbitmqctl authenticate_user #{username}#{password}").error?
converge_by "Updating password for user #{username} ..." do
shell_out!("rabbitmqctl update_user #{username} --password #{password}")
end
end
end
end
reset_property
Use the reset_property
method to clear the value for a property as if it had never been set, and then use the default value. For example, to clear the value for a property named password
:
reset_property(:password)
provides
Use the provides
method to associate a custom resource with the Recipe DSL on different operating systems. When multiple custom resources use the same DSL, specificity rules are applied to determine the priority, from highest to lowest:
- provides :my_custom_resource, platform_version: ‘0.1.2’
- provides :my_custom_resource, platform: ‘platform_name’
- provides :my_custom_resource, platform_family: ‘platform_family’
- provides :my_custom_resource, os: ‘operating_system’
- provides :my_custom_resource
For example:
provides :my_custom_resource, platform: 'redhat' do |node|
node['platform_version'].to_i >= 7
end
provides :my_custom_resource, platform: 'redhat'
provides :my_custom_resource, platform_family: 'rhel'
provides :my_custom_resource, os: 'linux'
provides :my_custom_resource
This allows you to use multiple custom resources files that provide the same resource to the user, but for different operating systems or operation system versions. With this you can eliminate the need for platform or platform version logic within your resources.
resource_name
Note
-
) is a valid character and may be used in cookbook and custom resource names, but it is discouraged. Chef Infra Client will return an error if a hyphen is not converted to an underscore (_
) when referencing from a recipe the name of a custom resource in which a hyphen is located.Use the resource_name
method at the top of a custom resource to declare a custom name for that resource. For example:
resource_name :my_resource_name
The resource_name
is only used as a fallback name for display purposes. Even for display purposes, the name used in recipe code which matches a provides
statement is favored over the resource_name
setting, so the resource_name
has little effect. In Chef Infra Client 16 and later, the first provides
in a resource declaration also sets the fallback resource_name
, so we do not recommend that users set the resource_name
at all. The resource_name
setting is necessary for backwards compatibility with Chef Infra Client 12 through 15.
For example, the httpd.rb
file in the website
cookbook could be assigned a custom resource name like this:
resource_name :httpd
property :homepage, String, default: '<h1>Hello world!</h1>'
action :create do
package 'httpd'
service 'httpd' do
action [:enable, :start]
end
file '/var/www/html/index.html' do
content new_resource.homepage
end
end
and is then usable in a recipe like this:
httpd 'build website' do
homepage '<h1>Welcome to the Example Co. website!</h1>'
action :create
end
deprecated
Deprecating entire resources
Chef Infra Client 14+ provides new primitives that allow you to deprecate resources or properties with the same functionality used for deprecations in Chef Infra Client resources. This allows you make breaking changes to enterprise or community cookbooks with friendly notifications to downstream cookbook consumers directly in the Chef Infra Client run.
Deprecate the foo_bar resource in a cookbook
deprecated 'The foo_bar resource has been deprecated and will be removed in the next major release of this cookbook scheduled for 12/25/2021!'
property :thing, String, name_property: true
action :create do
# Chef resource code
end
Deprecating a property
Deprecate the badly_named
property in a resource:
property :badly_named, String, deprecated: 'The badly_named property has been deprecated and will be removed in the next major release of this cookbook scheduled for 12/25/2021!'
Deprecate and alias
Rename a property with a deprecation warning for users of the old property name:
deprecated_property_alias 'badly_named', 'really_well_named', 'The badly_named property was renamed really_well_named in the 2.0 release of this cookbook. Please update your cookbooks to use the new property name.'
© Chef Software, Inc.
Licensed under the Creative Commons Attribution 3.0 Unported License.
The Chef™ Mark and Chef Logo are either registered trademarks/service marks or trademarks/servicemarks of Chef, in the United States and other countries and are used with Chef Inc's permission.
We are not affiliated with, endorsed or sponsored by Chef Inc.
https://docs.chef.io/dsl_custom_resource/