The Simplest Example Ever: How To Create and Use LWRP in Chef

This example will walk you through creating the most basic Chef LWRP starting from scratch or, in other words, a truly light weight lightweight resource provider 🙂

lightweight

I am working with a cookbook named my_cookbook, and I will define an LWRP named awesome_lwrp.
To do this, I will need two files, the Resource and the Provider. By default, the names of the Provider and the Resource files should match.
 

Create the Resource

Create a new folder resources in your cookbook directory
Add a file named awesome_lwrp.rb

Define the simplest resource ever:

actions :add, :delete
default_action :add

attribute :awesomeString, :kind_of => String, :required => true
attribute :awesomeInt, :kind_of => Integer, :required => false, :default => 3

Explanation:
awesome_lwrp supports two actions, add and delete. The default action is add.
awesome_lwrp accepts two arguments. The first one is a string, and it is required. The second one is an integer, it is optional and has a default value of 3.

 

Create the Provider

Create a new folder providers in your cookbook directory
Add a file named awesome_lwrp.rb

Defining a simple provider:

# Support “no-operation” mode
def whyrun_supported?
  true
end

# Should be considered a requirement for any
# lightweight resource authored against the 11.0+ versions of the chef-client
# Using this method ensures that the chef-client can notify parent lightweight resources
# after embedded resources have finished processing
use_inline_resources

# Implement action "add"
action :add do
  # Check condition
  # Use resource attribute
  if powershell_output_true?(@new_resource.awesomeInt)
    Chef::Log.debug "No need for changes!"
  else
    # Use converge_by for whyrun mode
    converge_by("Making awesome LWRP updates") do
      # Execute updates
      # Use resource attribute
      run_powershell_script(@new_resource.awesomeString)
      # Notify that a node was updated successfully (actually redundant here)
      @new_resource.updated_by_last_action(true)
      Chef::Log.debug "Awesome String was echoed"
    end
  end
end

action :delete do
  # Some code here
end

# Execute updates - this is a dummy method that simply echoes a string
def run_powershell_script(awesomeString)
  powershell_script 'Awesome Script' do
      code "echo " + awesomeString
      action :run
  end
end

# Check if updates need to be executed
def powershell_output_true?(awesomeInt)
  # A powershell command that returns false so that the update will get executed
  ps_command = "(1+1) -eq " + awesomeInt.to_s
  cmd_str = "powershell -Command " " + ps_command + " " "
  # Run powershell from cmd
  cmd = shell_out(cmd_str, { :returns => [0] })
  # Check powershell output
  if(cmd.stdout =~ /true/i)
     Chef::Log.debug "PowerShell output is true"
    return true
  else
    Chef::Log.debug "PowerShell output is false"
    return false
  end
end

What this code does:
For the sake of simplicity only the “add” action is implemented.
The “add” action checks if the updates are needed, and if the answer is yes it executes the updates and notifies the chef server that the node was updated. I am using dummy PowerShell methods simply to demonstrate the method flow.

Note that:

  • It is best practice to use use_inline_resources
  • The attributes supplied to the resource can be accessed using @new_resource.attributeName
  • It is good practice to call @new_resource.updated_by_last_action(true) if updates were executed, however it is redundant if you are using use_inline_resources and/or converge_by
  • whyrun is used in combination with converge_by statements to support a “no-operation” mode, in which chef-client is printing out what updates would be executed, without actually executing them

I am not going to go into depth on whyrun_supported and use_inline_resources. For details please check chef docs

 

Calling the new awesome LWRP

Now you can call the resource in your recipe as following:

my_cookbook_awesome_lwrp 'Awesome Resource Name' do
  action :add
  awesomeString node['my_cookbook']['awesome-string']
end

Note that:

  • The default LWRP name is the concatenation of the cookbook name with the resource/provider name using underscores.
  • Instantiated resource name (in this case ‘Awesome Resource Name’) needs to be provided to use the LWRP.
  • The required attributes are required 😉
Appendix

In this example the LWRP is called with a node attribute. The node attribute can be defined in attributes/default.rb as following:

default['my_cookbook']['awesome-string'] = "Pumpkin Pie!"

 
* If your kitchen fails to recognize the method shell_out you need a newer version of Chef. Please specify this in your kitchen.yml

provisioner:
  name: chef_solo
  require_chef_omnibus: 12.4.1

 
That’s it! Happy lifting 😉

Leave a Reply

Your email address will not be published. Required fields are marked *