Chef Custom Resources – Useful Methods

I was tasked to write a chef custom resource (LWRP – Lightweight Resources and Providers) while working on a project to migrate the deployment of an application from shell scripts to ruby code in chef.

As defined on chef.io, a custom resource:

  • Is a simple extension of Chef
  • Is implemented as part of a cookbook
  • Follows easy, repeatable syntax patterns
  • Effectively leverages resources that are built into Chef
  • Is reusable in the same way as resources that are built into Chef

The purpose of the custom resource was to download a zipped file from the nexus repo and extract it. However, we wanted to optimize it so that it would not extract the zipped file on every chef client run unless the version of the zipped file had changed since the last client run.

One approach was to create a file as a part of the client run that would store the version number. At the next client run that file will be read and compared to the version number passed by the recipe. If the set and passed versions differ it will run the extract command.

This approach was simple and straight forward but came with the overhead of maintaining an extra file when the same can be achieved by querying the chef node object. And then, I stumbled upon “load_current_value” and “converge_if_changed” methods of the chef custom resource DSL during my research. But I was unable to find any documentation that would explain the RIGHT way to use the resource methods.

So thought of writing down my learnings with the hope that someone will benefit from it.

What is load_current_value and converge_if_changed methods?

The load_current_value method is used to load the current value of a specified attribute from the node which can then be used at the converge stage of the chef client run.

The converge_if_changed method is used inside of the action block of the custom resource. It is used to do a comparison of the value of an attribute supplied by the recipe/resource during a chef client run to the value of the attribute  loaded by the load_current_value method.

The excerpts below show the way we implemented these methods in our resource.

The version of the zipped file is supplied as a property to the resource. We set the desired_state for the property to false as we want the recipe to decide the value for this desired state property.

property :version, kind_of: String, required: true, desired_state: false

Then we call the load_current_value method to determine the value of version currently set on the node object. We use search index on the chef server to look for our node based on its fqdn. The result is filtered on the particular attribute that we are looking for (in our case t_version).

load_current_value do
  results = search(:node, "name:#{node.fqdn}",
                  :filter_result => { 't_version' => [ 'config', 't_version']}).first
  version results['t_version']
end

Next we call the converge_if_changed method on the version property to compare its value set on the node to the value supplied to the resource by the recipe. If the values differ it will converge the resources defined in its block (in our case calling the chef built-in bash resource)

action :extract do
  converge_if_changed :version do
    bash 'extract_module' do
      cwd ::File.dirname("#{Chef::Config['file_cache_path']}/#{artifact}")
      code <<-EOH
      tar xzf #{artifact} -C #{extract_path} --strip-components=1
      EOH
    end
  end
 end

That’s it !!!

References
https://docs.chef.io/dsl_custom_resource.html