Skip to main content

Custom resources glossary

The following domain-specific language (DSL) methods are available when writing custom resources.

For further information about how to write custom resources please see about custom resources

action_class

action_class makes methods available to all actions within a single custom resource.

For example, a template requires 'yes' or 'no' written as a string, but you would like the user to use true or false for convenience. To allow both the :add and :remove actions to have access to this method, place the method in the action_class block.

property :example, [true, false], default: true

action :add do
  template "file.conf" do
    source 'file.conf.erb'
    variables(
      chocolate: bool_to_string(new_resource.example)
    )
    action :create
  end
end

action :remove do
  template "file.conf" do
    source 'file.conf.erb'
    variables(
      chocolate: bool_to_string(new_resource.example)
    )
    action :delete
  end
end

action_class do
  def bool_to_string(b)
    b ? 'yes' : 'false'
  end
end

coerce

coerce is used to transform user input into a canonical form. The value is passed in, and the transformed value returned as output. Lazy values will not be passed to this method until after they’re evaluated.

coerce is run in the context of the instance, which gives it access to other properties.

Here we transform,true/false in to yes, no for a template later on.

property :browseable,
        [true, false, String],
        default: true,
        coerce: proc { |p| p ? 'yes' : 'no' },

If you are modifying the properties type, you will also need to accept that Ruby type as an input.

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 aren’t 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 isn’t the desired state:

action :some_action do
  converge_if_changed do
    # some property
  end
end

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 the current value for content and mode
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

action :create do
  # If the value of content has changed
  # write file
  converge_if_changed :content do
    IO.write(new_resource.path, new_resource.content)
  end

  # If the value of mode has changed then
  # chmod file
  converge_if_changed :mode do
    ::File.chmod(new_resource.mode, new_resource.path)
  end
end

Chef Infra Client will only update the property values that require updates and won’t make changes when the property values are already in the desired state.

current_value_does_not_exist!

When using the load_current_value block, use current_value_does_not_exist! to indicate that the value doesn’t exist and that current_resource should therefore be nil.

load_current_value do |new_resource|
    port_data = powershell_exec(%Q{Get-WmiObject -Class Win32_TCPIPPrinterPort -Filter "Name='#{new_resource.port_name}'"}).result

    if port_data.empty?
      current_value_does_not_exist!
    else
      ipv4_address port_data["HostAddress"]
    end
  endo
end

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'

# Define bbbbb aas the default action
default_action :bbbbb

action :aaaaa do
 # the first action listed in the custom resource
end

action :bbbbb do
 # the second action listed in the custom resource
end

deprecated

Deprecating a resource

Deprecate resources that you no longer wish to maintain. 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.

Use the deprecated method to deprecate a resource in a cookbook. For example:

deprecated 'The foo_bar resource has been deprecated and will be removed in the next major release of this cookbook scheduled for 25/01/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!'

deprecated_property_alias

To rename a property with a deprecation warning for users of the old property name, use deprecated_property_alias:

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.'

desired_state

Add desired_state: to set the desired state property for a resource.

Allowed valuesDefault
true falsetrue
  • 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’s 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

lazy

When setting a node attribute as the default value for a custom resource property, wrap the node attribute in lazy {} so that its value is available when the resource executes.

property :thing, String, default: lazy { node['thingy'] }

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 value 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 aren’t changed to the default values when Chef Infra Client configures the node.

new_resource.property

Custom resources are designed to use resources that are built into Chef Infra and external custom resources. To disambiguate from the current resource being used and other resources, new_resource.property is required.

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

The following properties are identical to the properties in the execute resource, which we’re embedding in the custom resource.

  • property :cwd
  • property :environment
  • property :user
  • property :sensitive

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
  • sensitive new_resource.sensitive

Correctly use the properties of the execute resource and not the identically-named override properties of the custom resource.

partial

To DRY (don’t repeat yourself) up code, custom resources can include partials from common files.

For example, if all of your resources need the version property, you can add this to a partial/_common.rb file and include that Ruby code in your resource using the use directive.

In resources/partial/_common.rb, define the version property:

# resources/partial/_common.rb
property :version, String,
          name_property: true,
          description: 'Java version to install'

And then in your custom resources, include that code with the use directive:

# resources/install_type_a.rb
provides :adoptopenjdk_install
unified_mode true
use 'partial/_common'

property :variant,
          String,
          description: 'Install flavour', default: 'openj9'
# resources/openjdk_install.rb
provides :openjdk_install
unified_mode true
use 'partial/_common'

property :install_type,
          String,
          default: lazy { default_openjdk_install_method(version) },
          equal_to: %w( package source ),
          description: 'Installation type'

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 as String, Integer, true, or false
  • 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

property_is_set?

Use the property_is_set? method to check if the value for a property has been passed into the resource.

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 isn’t 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

provides

Use the provides method to associate multiple custom resource files with the same resources name. For example:

# Provide custom_resource_name to Red Hat 7 and above
provides :custom_resource_name, platform: 'redhat' do |node|
  node['platform_version'].to_i >= 7
end

# Provide custom_resource_name to all Red Hat platforms
provides :custom_resource_name, platform: 'redhat'

# Provide custom_resource_name to the Red Hat platform family
provides :custom_resource_name, platform_family: 'rhel'

# Provide custom_resource_name to all linux machines
provides :custom_resource_name, os: 'linux'

# Provide custom_resource_name, useful if your resource file isn't named the same as the resource you want to provide
provides :custom_resource_name

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.

Precedence

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:

  1. provides :custom_resource_name, platform_version: '0.1.2'
  2. provides :custom_resource_name, platform: 'platform_name'
  3. provides :custom_resource_name, platform_family: 'platform_family'
  4. provides :custom_resource_name, os: 'operating_system'
  5. provides :custom_resource_name

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)

resource_name

Note

resource_name was deprecated in Chef Infra Client 15 and became EOL in 16.2.44. Use the provides method instead of resource_name.

For resources running on Chef Infra Client from 12.5 through 15, use resource_name:

resource_name :foo

For resources running on Chef Infra Client 15.13.8 to 16.1.16, use both methods to maintain backwards compatibility:

resource_name :foo
provides :foo

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

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]

run_context

Chef loads and tracks the current run in the run context object.

root_context

sensitive

A property can be marked sensitive by specifying sensitive: true on the property. This prevents the contents of the property from being exported to data collection and sent to an Automate server or shown in the logs of the Chef Infra Client run.

target_mode

Target Mode executes Chef Infra Client runs on nodes that don’t have Chef Infra Client installed on them.

To enable a custom resource to run in Target Mode, add target_mode: true to the resource definition. For example:

provides :resource_name, target_mode: true
...

For more information on Target Mode, see the Target Mode documentation.

unified_mode

Unified mode is a setting that will compile and converge a custom resource’s action block in one pass and in the order that the code inside that block is composed, from beginning to end. This replaces Chef Infra’s two-pass parsing with single-pass parsing so that resources are executed as soon as they’re declared. This results in clearer code and requires less Ruby knowledge to understand the order of operations.

To enable Unified Mode in a resource, declare it at the top of the resource. For example:

unified_mode true

provides :resource_name

For information, see the Unified Mode documentation.

Validation parameters

Use a validation parameter to add zero (or more) validation parameters to a property.

ParameterDescription

:callbacks

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:

callbacks: {
             'should be a valid non-system port' => lambda {
               |p| p > 1024 && p < 65535
             }
           }

:default

Use to specify the default value for a property. For example:

default: 'a_string_value'
default: 123456789
default: []
default: ()
default: {}

:equal_to

Use to match a value with ==. Use an array of values to match any of those values with ==. For example:

equal_to: [true, false]
equal_to: ['php', 'perl']

:regex

Use to match a value to a regular expression. For example:

regex: [ /^([a-z]|[A-Z]|[0-9]|_|-)+$/, /^\d+$/ ]

:required

Indicates that a property is required. For example:

required: true

:respond_to

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:

respond_to: valid_encoding?

Some examples of combining validation parameters:

property :spool_name, String, regex: /$\w+/
property :enabled, equal_to: [true, false, 'true', 'false'], default: true
Edit this page on GitHub

Thank you for your feedback!

×