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
:delete Action methods, how to support
why_run (dry-run or what-if mode) and how to use the
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 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
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
Let’s look at the
:create action first. We first check if the
@current_resource already exists, and if so, we log a message and do nothing.
@current_resouce 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 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.
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
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
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
@current_resource.snmp_enabled for use later in our
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
@current_resource to an instance of
Chef::Resource::WindowsPrinterPort and copy one or more attributes from the
@new_resource, which is passed in from the
windows_printer_port Resource in the Recipe. Chef creates the
@new_resource 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
@new_resource.port_name if the user specified a custom port name, or we use
IP_<ipv4_address> if the user didn’t specify a custom port name.
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
@current_resource.exists attribute to
true since we now know that the printer port already exists.
This week we learned how to create the basic skeleton for Action methods (
: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
port_exists? private methods which do the real work on the server.
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!