Ohai Chefs!
Last week, we looked at part one of creating an LWRP – the Resource. This week, we’ll look at part two – the Provider. We’ll look at a real Provider which uses Ruby and PowerShell to create and delete printer ports. Since the Provider code is so long, I’ll cover the first half this week, and the second half next week. The first half will cover the :create
and :delete
Action methods, how to support why_run
(dry-run or what-if mode) and how to use the load_current_resource
method.
As a reminder, LWRPs eanble you to easily install, create, delete, start, stop or otherwise manipulate resources; things like packages, printers, services, etc. The Resource is a simple interface, an API if you will, which makes it very easy for sysadmins to create Recipes which do a lot of work in a few lines of code.
The Provider
The Provider part of an LWRP is the OS-specific code which actually installs, creates, deletes, starts, or stops the resource on the managed node. As we’ll see in the example below, Providers are written in Ruby but often use Bash, PowerShell, or command-line utilities to do their work.
Or maybe Providers…
In an LWRP, a given Resource may have more than one Provider. For example the windows_feature
LWRP in the Windows cookbook has two Providers, one for installing features via dism.exe
, and one for installing features using the older servermanagercmd.exe
.
Show me the code!
Continuing our example from last week we’ll be looking at the Windows Printer Port LWRP .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
|
The :create
action
Let’s look at the :create
action first. We first check if the already exists, and if so, we log a message and do nothing.
is set to the resource on the managed node if it already exists. So if the printer port we are trying to create already exists, we don’t create it again. This is how we acheive idempotency in our LWRP and it’s a core tenet of Chef – don’t change a node’s state unless it’s necessary.
So if our printer port hasn’t yet been created, we call the create_printer_port
method which actually creates the printer port using Windows a PowerShell cmdlet. We’ll look at the create_printer_port
method next week. The create_printer_port
method call is wrapped in a converge_by
block, which is the secret to implementing why-run
mode.
Why-Run
Why-Run is fairly simple to implement in a Provider. You just need to define a whyrun_supported?
method which returns true
, and wrap any code which actually makes changes on the managed node in a converge_by
block with an appropriate message about what the code would do if you actually converged the node. For example, in our :create
action, we wrap the create_printer_port
method call in a converge_by
block with a log message which says we would have created a printer port.
If you’ve looked at Provider code in the past, or have written LWRPs, you have probably seen the new_resource.updated_by_last_action(true)
method call in the Provider Actions. This method call supports Notifications. So if the Resource changed, it would notify other resources.
When you implement Why-Run, you don’t need to call new_resource.updated_by_last_action(true)
because the converge_by
block does that for you automatically.
The load_current_resource
method
The load_current_resource
method is proably the hardest to understand how to actually write. Conceptually, it’s fairly straighforward. Using the Resource (windows_printer_port
) attributes which the user specified in the Recipe, load_current_resource
tries to find, on the server, an existing printer port which matches the one we are trying to create. If it finds a match, it sets to
true
. Remember that last week we created the exists
attribute by setting an attr_accessor :exists
on our Resource. Now, we get to use it.
You should know that the load_current_resource
method is already defined on the Chef::Provider
class. You just need to define, or override the method in your own Provider. Chef will call the load_current_resouce
method automatically when it iterates over the ResourceCollection during the chef client execution phase.
Just Gettin’ By…
We are just doing the bare minimum in our load_current_resource
method. For creating and deleting printer ports, this is probably enough. If we wanted to be able to modify a printer port, we would need to load in all the attributes from the current printer port on the managed node so we would have them available for comparison.
For example, if we wanted to modify an existing printer port to change the snmp_enabled
attribute from false
to true
, we would need to query the existing printer port on the server to see if SNMP was enabled or not, and save that value to for use later in our
:modify
action.
1 2 3 4 5 6 7 8 9 10 11 |
|
load_current_resource
nuts and bolts
So for our bare minimum load_current_resource
method, we need to set to an instance of
Chef::Resource::WindowsPrinterPort
and copy one or more attributes from the , which is passed in from the
windows_printer_port
Resource in the Recipe. Chef creates the class instance from the attributes in the Recipe and makes it available to the Provider automatically.
In this case, to determine if the printer port already exists, we need to query the Windows Registry using the port_name
attribute. The port_name
is usually IP_<ipv4_address>
, but could could be anthing if the user specified a custom port_name
in the Recipe.
So in line 5 above, we set from
if the user specified a custom port name, or we use
IP_<ipv4_address>
if the user didn’t specify a custom port name.
port_exists?
We then call our port_exists?
method which queries the Windows Registry and returns true
if the port already exists or false
if it doesn’t. We have a # TODO
note in our code where we would load in additional printer port attributes from the registry in the future.
Finally, we set our attribute to
true
since we now know that the printer port already exists.
Summary
This week we learned how to create the basic skeleton for Action methods (:create
, :delete
, etc.), how to support why-run
mode, and how to use the load_current_resource
method to determine if the Resource we are trying to create already exsists on the managed node.
Next week, we’ll cover the create_printer_port
, and port_exists?
private methods which do the real work on the server.
Feedback
Do you have any good examples of Providers which do something especially cool? Maybe from an LWRP you wrote? Or have you found Providers challenging to write? I’d love to hear your feedback in the comments. Thanks!