The following items are new for Chef Infra Client 12.5 and/or are changes from previous versions. The short version:
- **New way to build custom resources** The process for extending the
collection of resources that are built into Chef has been
simplified. It is defined only in the `/resources` directory using a
simplified syntax that easily leverages the built-in collection of
resources. (All of the ways you used to build custom resources still
work.)
- **"resource attributes" are now known as "resource properties"** In
previous releases of Chef, resource properties are referred to as
attributes, but this is confusing for users because nodes also have
attributes. Starting with chef-client 12.5 release---and
retroactively updated for all previous releases of the
documentation---"resource attributes" are now referred to as
"resource properties" and the word "attribute" now refers
specifically to "node attributes".
- **ps_credential helper to embed usernames and passwords** Use the
`ps_credential` helper on Microsoft Windows to create a
`PSCredential` object---security credentials, such as a user name or
password---that can be used in the **dsc_script** resource.
- **New Handler DSL** A new DSL exists to make it easier to use events
that occur during Chef Infra Client run from recipes. The `on` method is
easily associated with events. The action Chef Infra Client takes as a
result of that event (when it occurs) is up to you.
- **The -j / --json-attributes supports policy revisions and
environments** The JSON file used by the `--json-attributes` option
for Chef Infra Client may now contain the policy name and policy group
associated with a policy revision or may contain the name of the
environment to which the node is associated.
- **verify property now uses path, not file** The `verify` property,
used by file-based resources such as **remote_file** and **file**,
runs user-defined correctness checks against the proposed new file
before making the change. For versions of Chef Infra Client prior to 12.5,
the name of the temporary file was stored as `file`; starting with
chef-client 12.5, use `path`. This change is documented as a warning
across all versions in any topic in which the `version` attribute is
documented.
- **depth property added to deploy resource** The `depth` property
allows the depth of a git repository to be truncated to the
specified number of versions.
- **The knife ssl check subcommand supports SNI** Support for Server
Name Indication (SNI) is added to the `knife ssl check` subcommand.
- **Chef Policy group and name can now be part of the node object**
Chef policy is a beta feature of Chef Infra Client that will eventually
replace roles, environments or manually specifying the run_list.
Policy group and name can now be stored as part of the node object
rather than in the client.rb file. A recent version of the Chef
server, such as 12.2.0 or higher, is needed to fully utilize this
feature.
## Custom Resources
A custom resource:
- Is a simple extension of Chef that adds your own resources
- Is implemented and shipped as part of a cookbook
- Follows easy, repeatable syntax patterns
- Effectively leverages resources that are built into Chef and/or
custom Ruby code
- Is reusable in the same way as resources that are built into Chef
For example, Chef includes built-in resources to manage files, packages,
templates, and services, but it does not include a resource that manages
websites.
Note
See our documentation on [custom resources](https://docs.chef.io/custom_resources/) for more information about custom resources,
including a scenario that shows how to build a `website` resource.
### Syntax
A custom resource is defined as a Ruby file and is located in a
cookbook's `/resources` directory. This file
- Declares the properties of the custom resource
- Loads current state of properties, if the resource already exists
- Defines each action the custom resource may take
The syntax for a custom resource is. For example:
```ruby
property :property_name, RubyType, default: 'value'
load_current_value do
# some Ruby for loading the current state of the resource
end
action :action_name do
# a mix of built-in Chef resources and Ruby
end
action :another_action_name do
# a mix of built-in Chef resources and Ruby
end
```
where the first action listed is the default action.
Warning
Do not use existing keywords from Chef Infra Client resource system in a
custom resource, like "name". For example, `property :property_name` in
the following invalid syntax:
`property :name, String, default: 'thename'`.
This example `site` utilizes Chef's built in `file`, `service` and
`package` resources, and includes `:create` and `:delete` actions. Since
it uses built in Chef resources, besides defining the property and
actions, the code is very similar to that of a recipe.
```ruby
property :homepage, String, default: 'Hello world!
'
action :create do
package 'httpd'
service 'httpd' do
action [:enable, :start]
end
file '/var/www/html/index.html' do
content homepage
end
end
action :delete do
package 'httpd' do
action :delete
end
end
```
where
- `homepage` is a property that sets the default HTML for the
`index.html` file with a default value of `'Hello world!
'`
- the `action` block uses the built-in collection of resources to tell
Chef Infra Client how to install Apache, start the service, and then
create the contents of the file located at
`/var/www/html/index.html`
- `action :create` is the default resource, because it is listed
first; `action :delete` must be called specifically (because it is
not the default resource)
Once built, the custom resource may be used in a recipe just like any of
the resources that are built into Chef. The resource gets its name from
the cookbook and from the file name in the `/resources` directory, with
an underscore (`_`) separating them. For example, a cookbook named
`exampleco` with a custom resource named `site.rb` is used in a recipe
like this:
```ruby
exampleco_site 'httpd' do
homepage 'Welcome to the Example Co. website!
'
end
```
and to delete the exampleco website, do the following:
```ruby
exampleco_site 'httpd' do
action :delete
end
```
## Custom Resource DSL
Use the Custom Resource DSL to define property 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
Use the `action_class` block to make methods available to the actions in
the custom resource. Modules with helper methods created as files in the
cookbook library directory may be included. New action methods may also
be defined directly in the `action_class` block. Code in the
`action_class` block has access to the new_resource properties.
Assume a helper module has been created in the cookbook
`libraries/helper.rb` file.
```ruby
module Sample
module Helper
def helper_method
# code
end
end
end
```
Methods may be made available to the custom resource actions by using an
`action_class` block.
```ruby
property file, String
action :delete do
helper_method
FileUtils.rm(new_resource.file) if file_ex
end
action_class do
def file_exist
::File.exist?(new_resource.file)
end
def file_ex
::File.exist?(new_resource.file)
end
require 'fileutils'
include Sample::Helper
end
```
### 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:
```ruby
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:
```ruby
property :content, String
property :path, String, name_property: true
load_current_value do
if ::File.exist?(new_resource.path)
content IO.read(new_resource.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(path, content)` code is
executed and Chef Infra Client output will print something similar to:
```bash
Recipe: recipe_name::block
* resource_name[blah] action create
- update my_file[blah]
- set content to "hola mundo" (was "hello world")
```
**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).
```ruby
property :path, String, name_property: true
property :content, String
property :mode, String
load_current_value do
if ::File.exist?(new_resource.path)
content IO.read(new_resource.path)
mode ::File.stat(new_resource.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 both `content`
and `mode`
- A `converge_if_changed` block tests only `content`
- A `converge_if_changed` block tests only `mode`
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:
```ruby
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:
```ruby
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.
Use the `load_current_value` method to guard against property values
being replaced. For example:
```ruby
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.
### 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:
```ruby
resource_name :node_execute
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:
```ruby
# 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:
```ruby
resource_name :node_execute
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
Use the `property` method to define properties for the custom resource.
The syntax is:
```ruby
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:
```ruby
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:
```ruby
property :aaaa, String
```
```ruby
property :bbbb, Integer
```
```ruby
property :cccc, Hash
```
```ruby
property :dddd, [true, false]
```
```ruby
property :eeee, [String, nil]
```
```ruby
property :ffff, [Class, String, Symbol]
```
```ruby
property :gggg, [Array, Hash]
```
**validators**
A validation parameter is used to add zero (or more) validation
parameters to a property.
: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'
|
:equal_to
|
Use to match a value with == . Use an array of values to match any of those values with == . For example:
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:
|
: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:
```ruby
property :spool_name, String, regex: /$\w+/
```
```ruby
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.
```ruby
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:
```ruby
property :username, String, identity: true
property :password, String
```
**Block Arguments**
Any properties that are marked `identity: true` or
`desired_state: false` will be available from `load_current_value`. If
access to other properties of a resource is needed, use a block argument
that contains all of the properties of the requested resource. For
example:
```ruby
resource_name :file
load_current_value do |new_resource|
puts "The user typed content = #{new_resource.content} in the resource"
end
```
### property_is_set?
Use the `property_is_set?` method to check if the value for a property
is set. The syntax is:
```ruby
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:
```ruby
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 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 :resource_name, platform_version: '0.1.2'
2. provides :resource_name, platform: 'platform_name'
3. provides :resource_name, platform_family: 'platform_family'
4. provides :resource_name, os: 'operating_system'
5. provides :resource_name
For example:
```ruby
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.
**override**
Chef will warn you if the Chef Infra Language is provided by another custom
resource or built-in resource. For example:
```ruby
class X < Chef::Resource
provides :file
end
class Y < Chef::Resource
provides :file
end
```
This will emit a warning that `Y` is overriding `X`. To disable this
warning, use `override: true`:
```ruby
class X < Chef::Resource
provides :file
end
class Y < Chef::Resource
provides :file, override: true
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`:
```ruby
reset_property(:password)
```
## Definition vs. Resource
The following examples show:
1. A definition
2. The same definition rewritten as a custom resource
3. The same definition, rewritten again to use a [common resource
property](/resource_common/)
### As a Definition
The following definition processes unique hostnames and ports, passed on
as parameters:
```ruby
define :host_porter, :port => 4000, :hostname => nil do
params[:hostname] ||= params[:name]
directory '/etc/#{params[:hostname]}' do
recursive true
end
file '/etc/#{params[:hostname]}/#{params[:port]}' do
content 'some content'
end
end
```
### As a Resource
The definition is improved by rewriting it as a custom resource:
```ruby
property :port, Integer, default: 4000
property :hostname, String, name_property: true
action :create do
directory "/etc/#{hostname}" do
recursive true
end
file "/etc/#{hostname}/#{port}" do
content 'some content'
end
end
```
Once built, the custom resource may be used in a recipe just like the
any of the resources that are built into Chef. The resource gets its
name from the cookbook and from the file name in the `/resources`
directory, with an underscore (`_`) separating them. For example, a
cookbook named `host` with a custom resource in the `/resources`
directory named `porter.rb`. Use it in a recipe like this:
```ruby
host_porter node['hostname'] do
port 4000
end
```
or:
```ruby
host_porter 'www1' do
port 4001
end
```
### Common Properties
Unlike definitions, custom resources are able to use [common resource
properties](/resource_common/). For example, `only_if`:
```ruby
host_porter 'www1' do
port 4001
only_if '{ node['hostname'] == 'foo.bar.com' }'
end
```
## ps_credential Helper
Use the `ps_credential` helper to embed a `PSCredential` object--- [a
set of security credentials, such as a user name or
password](https://technet.microsoft.com/en-us/magazine/ff714574.aspx)
---within a script, which allows that script to be run using security
credentials.
For example, assuming the `CertificateID` is configured in the local
configuration manager, the `SeaPower1@3` object is created and embedded
within the `seapower-user` script:
```ruby
dsc_script 'seapower-user' do
code <<-EOH
User AlbertAtom
{
UserName = 'AlbertAtom'
Password = #{ps_credential('SeaPower1@3')}
}
EOH
configuration_data <<-EOH
@{
AllNodes = @(
@{
NodeName = "localhost";
CertificateID = 'A8D1234559F349F7EF19104678908F701D4167'
}
)
}
EOH
end
```
## Handler DSL
Use the Handler DSL to attach a callback to an event. If the event
occurs during Chef Infra Client run, the associated callback is
executed. For example:
- Sending email if a chef-client run fails
- Sending a notification to chat application if an audit run fails
- Aggregating statistics about resources updated during a chef-client
runs to StatsD
### on Method
Use the `on` method to associate an event type with a callback. The
callback defines what steps are taken if the event occurs during Chef
Client run and is defined using arbitrary Ruby code. The syntax is as
follows:
```ruby
Chef.event_handler do
on :event_type do
# some Ruby
end
end
```
where
- `Chef.event_handler` declares a block of code within a recipe that
is processed when the named event occurs during a chef-client run
- `on` defines the block of code that will tell Chef Infra Client how to
handle the event
- `:event_type` is a valid exception event type, such as `:run_start`,
`:run_failed`, `:converge_failed`, `:resource_failed`, or
`:recipe_not_found`
For example:
```bash
Chef.event_handler do
on :converge_start do
puts "Ohai! I have started a converge."
end
end
```
### Example: Send Email
Use the `on` method to create an event handler that sends email when
Chef Infra Client run fails. This will require:
- A way to tell Chef Infra Client how to send email
- An event handler that describes what to do when the `:run_failed`
event is triggered
- A way to trigger the exception and test the behavior of the event
handler
Note
See our documentation on the
Handler DSL for more information about using event handlers
in recipes.
**Define How Email is Sent**
Use a library to define the code that sends email when a chef-client run
fails. Name the file `helper.rb` and add it to a cookbook's `/libraries`
directory:
```ruby
require 'net/smtp'
module HandlerSendEmail
class Helper
def send_email_on_run_failure(node_name)
message = "From: Chef \n"
message << "To: Grant \n"
message << "Subject: Chef run failed\n"
message << "Date: #{Time.now.rfc2822}\n\n"
message << "Chef run failed on #{node_name}\n"
Net::SMTP.start('localhost', 25) do |smtp|
smtp.send_message message, 'chef@chef.io', 'grantmc@chef.io'
end
end
end
end
```
**Add the Handler**
Invoke the library helper in a recipe:
```ruby
Chef.event_handler do
on :run_failed do
HandlerSendEmail::Helper.new.send_email_on_run_failure(
Chef.run_context.node.name
)
end
end
```
- Use `Chef.event_handler` to define the event handler
- Use the `on` method to specify the event type
Within the `on` block, tell Chef Infra Client how to handle the event when
it's triggered.
**Test the Handler**
Use the following code block to trigger the exception and have Chef
Client send email to the specified email address:
```ruby
ruby_block 'fail the run' do
block do
fail 'deliberately fail the run'
end
end
```
## New Resource Properties
The following property is new for the **deploy** resource:
depth
|
Ruby Type: Integer
The depth of a git repository, truncated to the specified number of revisions. |
## Specify Policy Revision
Use the following command to specify a policy revision:
```bash
chef client -j JSON
```
where the JSON file is similar to:
```javascript
{
"policy_name": "appserver",
"policy_group": "staging"
}
```
Or use the following settings to specify a policy revision in the
client.rb file:
policy_group |
The name of a policy group that exists on the Chef server. |
policy_name |
The name of a policy, as identified by the name setting in a Policyfile.rb file. |
## New Configuration Settings
The following settings are new for the client.rb file and enable the use
of policy files:
named_run_list |
The run-list associated with a policy file. |
policy_group |
The name of a policy group that exists on the Chef server. (See "Specify Policy Revision" in this readme for more information.) |
policy_name |
The name of a policy, as identified by the name setting in a Policyfile.rb file. (See "Specify Policy Revision" in this readme for more information.) |
## chef-client Options
The following options are new or updated for Chef Infra Client executable and
enable the use of policy files:
`-n NAME`, `--named-run-list NAME`
: The run-list associated with a policy file.
`-j PATH`, `--json-attributes PATH`
: This option now supports using a JSON file to associate a policy
revision.
Use this option to use policy files by specifying a JSON file that
contains the following settings:
policy_group |
The name of a policy group that exists on the Chef server. |
policy_name |
The name of a policy, as identified by the name setting in a Policyfile.rb file. |
For example:
```javascript
{
"policy_name": "appserver",
"policy_group": "staging"
}
```
This option also supports using a JSON file to associate an
environment:
Use this option to set the `chef_environment` value for a node.
Note
Any environment specified for `chef_environment` by a JSON file will
take precedence over an environment specified by the `--environment`
option when both options are part of the same command.
For example, run the following:
```bash
chef-client -j /path/to/file.json
```
where `/path/to/file.json` is similar to:
```javascript
{
"chef_environment": "pre-production"
}
```
This will set the environment for the node to `pre-production`.