Opscode
Home     Introduction to Chef     Cookbooks     Blog     GitHub     Tickets 

Light-weight Resources and Providers (LWRP)

Chef has the idea of Resources and Providers, which represent the description of the state you want a particular part of the system to be in, the Resources, and the underlying implementation of how we bring the resource into that state, the Providers. For example, all database vendors support the concept of database creation, so it is easily abstracted; but the underlying implementation is different for each.

Though light-weight Resources and Providers are ultimately compiled down to Ruby classes (they can be implemented directly as Ruby classes, if you desire), they are intended to enable writing Resources and Providers with little or no knowledge or scaffolding of Ruby classes.

This document covers the DSL for creating Resources and Providers. It is not meant to be an in-depth description of the implementation, but details are sprinkled throughout for the curious reader.

File Locations

Light-weight Resources and Providers are loaded from files in a Cookbook's "resources" and "providers" directories. The names of resultant Resources and Providers combine the cookbook name and the file name with an underscore. The only exception to this convention concerns files named default.rb. In this case, the Resource or Provider is named according to the cookbook name only.

Examples

Note: there is no default LWR/P for the AWS cookbook; they are referenced only for illustrating how names correspond.

Filename Resource or Provider Name, as used in the DSL Generated Class
/cookbooks/aws/resources/default.rb aws Chef::Resource::Aws
/cookbooks/aws/resources/elastic_ip.rb aws_elastic_ip Chef::Resource::AwsElasticIp
/cookbooks/aws/providers/default.rb aws Chef::Provider::Aws
/cookbooks/aws/providers/elastic_ip.rb aws_elastic_ip Chef::Provider::AwsElasticIp

Resources

A Resource can be thought of as an abstract interface. Each is defined by its attributes and their validation rules, as well as the names of the actions it can take.

Keyword: actions

Actions are specified using the "actions" keyword followed by a comma-separated list of names. For example, the line

specifies that the list of allowed actions for this resource should include foo and bar (and ultimately corresponds to the implementing Provider's "action_foo" and "action_bar" methods).

Note: several "actions" declarations will append to, not overwrite, the list of allowed actions.

Keyword: attribute

Attributes are specified using the "attribute" keyword followed by the attribute's name and an optional set of validation rules. For example, the line

creates an attribute named foo (accessible to the implementing Provider via the resource's "foo" method) with no validation; whereas, the line

creates an attribute named bar (accessible to the implementing Provider via the resource's "bar" method) that enforces that values specified in recipes must be of type String.

Validation parameters

The full set of options that can be passed to the attribute keyword in order to validate a parameter set on a Resource in a Recipe is:

:default Sets the default value for this parameter.
:kind_of Ensure that the value is a kind_of?(Whatever). If passed an array, it will ensure that the value is one of those types.
:required Raise an exception if this parameter is missing. Valid values are true or false, by default, options are not required.
:regex Match the value of the paramater against a regular expression.
:equal_to Match the value of the paramater with ==. An array means it can be equal to any of the values.
:name_attribute Specifies that this is set to the name of the resource when used. Valid value is true or false.
:callbacks Takes a hash of Procs, which should return true if the argument is valid. The key will be inserted into the error message if the Proc does not return true: "Option #{key}'s value #{value} #{message}!"
:respond_to Ensure that the value has a given method. Takes one method name or an array of method names.

Default Provider when invoking an LWR in a Recipe

If you omit the provider attribute when using an LWR in a recipe, Chef will look for an LWP of the same name in the same cookbook by default.

So, you can write:

"Example LWR usage without an explicit"

instead of:

"Example LWR usage with an explicit Provider"

Example, creating a light-weight Resource

In order to demonstrate light-weight Resources and contrast them with the traditional way to implement them, let's re-work the File Resource:

"Chef::Resource::File circa Chef 0.7.8"

The above code is simple, traditional ruby--no magic at all. We are basically creating a number of getter/setter methods and validating that the inputs match some set of criteria (a regex, a string, true/false, etc.). The light-weight version looks like:

Light-weight File Resource

Hopefully this is simpler to write and understand.

Providers

Background

A chef-client run has two stages:

  1. The compilation phase, in which the client examines each Recipe in order and adds its Resources to the ResourceCollection
  2. The execution phase, in which the client iterates over the ResourceCollection and performs the following:
    1. Based on the Resource's "provider" attribute, a new instance of the specified Provider is created (if the attribute is not set, one is selected based on the local platform). The originating Resource is stored in the new Provider as the @new_resource instance variable and is accessible when writing LWPs as new_resource.name, for example.
    2. The load_current_resource method is then called on the provider instance, giving it an opportunity to populate @current_resource with a new resource that represents the current state of the relevant part of the system.
    3. For each action specified in @new_resource.actions, the action_ method that corresponds to each action is called (e.g. action :create will invoke the action_create method of the Provider instance.)

Keyword: action

Actions are defined using the "action" keyword. Attributes from the originating Resource are accessible through the @new_resource instance variable, or, more idiomatically, simply as new_resource (see example below).

In-line Resources in Provider Actions

The Recipe DSL has been extended to Providers, meaning Resources can be constructed and executed in-line in the bodies of Provider actions (see example using the "execute" Resource below.)

Implementation

For the curious, when a Provider references new Resources in-line, they are inserted into the ResourceCollection in order of appearance after the currently-executing Resource. For example, if after phase 1, the ResourceCollection contains the Resources [A,B] and during phase 2, the action run on A's Provider references Resources C and D in-line, the ResourceCollection (and execution order) will end up as [A,C,D,B].

Example, creating a light-weight Provider

Taking the database example, our Resource might be defined by:

/cookbooks/opscode/resources/database.rb

A mysql Provider might look like:

/cookbooks/opscode/providers/mysql.rb

This would create a new Provider (Chef::Provider::OpscodeMysql) with a load_current_resource that does nothing, along with two methods, action_create and action_delete. When either of these methods is invoked, the corresponding block is executed, including properly resolving @new_resource.

Using our resource in a recipe:

using the database resource

Would create a database called monkeynews. It would also allow you to trivially switch out the database back-end.

Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.

Copyright © 2009 Opscode, Inc. All Rights Reserved.