Chef:Powerful Infrastructure Automation
上QQ阅读APP看书,第一时间看更新

Resources

Resources are programmatic building blocks in Chef; they are a declarative mechanism to manipulate a resource on a host. Resources deliberately hide the underlying implementation that is left to a provider. It is important to recognize that a resource describes what is being manipulated, not how it is being manipulated; this is by design, as it provides a high level of abstraction for Chef recipes to be as platform-neutral as possible.

For example, Chef has built-in resources that include the following:

  • Cron jobs
  • Deployments
  • File system components (mount points, files and directories, and so on)
  • Source code repositories (Git and svn)
  • Logs
  • PowerShell scripts (Windows targets)
  • Shell scripts
  • Templates
  • Packages
  • Users and groups

Resources combined with providers (discussed shortly) are collectively referred to as LWRPs or lightweight resource providers; they make up a large portion of the functionality within a Chef recipe.

Resources are composed of a resource name (package name, file path, service name, and so on), an action, and some attributes that describe that resource.

Using resources

Resources, as we have seen in some examples, take the following form:

resource_name <name parameter> <ruby_block>

In the preceding code, resource_name is the registered name of the resource, such as file, package, and user. The name parameter is a special argument to the resource that gives this resource a unique name. This is often also used by the resource as a default value for an attribute that naturally maps to the name of the resource such as filename, username, and package name (you can see a pattern here); however, you can use an arbitrary name attribute and manually set the attribute. The Ruby block is just a block of code in Ruby; this is how Chef's DSL works. In Chef, each resource expects some specific things inside its configuration block. You will find that many resources have different expectations, but in general, a resource block in a recipe will be of the following form:

resource_name "name attribute" do
   attribute "value"
   attribute "value"
end

The previous example, which created a new user, was the following:

user "smith" do
  action :create
  system true
  comment "Agent Smith"
  uid  1000
  gid  "agents"
  home "/home/matrix"
  shell "/bin/zsh"
end 

Here, the resource name is user, the name attribute is smith, and the block of code being passed to the resource has seven attributes: action, system, comment, uid, gid, home, and shell. Each of these attributes has a value associated with it; internally, the Ruby code for the resource will store these in some variables to be used when manipulating the specified resource. In this case, constructing a user on the end host through the correct provider will be helpful.

One of these attributes, action, is a bit unique; its job is to tell the resource what action to take on the resource. The list of available actions will be different with each resource, but typically, a resource will have actions such as create, delete, or update. Have a look at the documentation for the resource you are working with; the documentation will describe the available actions and what they do separately from the other attributes.

To demonstrate how the name attribute is used as a default value for the user resource, the following recipe has the same behavior as the previous one, but has one minor change:

user "agent_smith" do
  username "smith"
  action :create
  system true
  comment "Agent Smith"
  uid  1000
  gid  "agents"
  home "/home/matrix"
  shell "/bin/zsh"
end 

Here, you can see that an additional attribute, username, has been added to the resource block with the value that was previously in the name attribute. Additionally, the name attribute has been changed to "agent_smith". If you were to execute this recipe or the previous example, it would have the same effect: to create a local system user, smith, with the UID, GID, home, and other attributes specified.

Overriding a default behavior

In addition to properties, resources also have a default action. More often than not, the default action is create, but again, you will want to consult the documentation for the resource you are working with to make sure that you know what the default behavior is for a resource. You don't want to accidentally destroy something you thought you were creating!

A concrete example might be installing the tcpdump package on your system. To install the default version with no customization, you could use a resource description such as the following:

package "tcpdump"

This works because the default action of the package resource is to perform an installation. If you look at the source code for the package resource, you will see the following at the beginning of the constructor:

def initialize(name, run_context=nil)
  super
  @action = :install
  @allowed_actions.push(:install, :upgrade, 
                        :remove, :purge, :reconfig)
  @candidate_version = nil
  @options = nil
  @package_name = name

This tells us that the default action, if unspecified, is to install the package and to use the name attribute as the package name. So, the previous simple one-line resource is the same as writing out the following block:

package "tcpdump" do 
  action :install
  package_name "tcpdump"
end

Here, however, package_name will default to the name attribute, so we do not need to provide it if the resource name is the same as the package you wish to install. Additionally, if you wanted to be more verbose with your resource description and install a specific version of the tcpdump package, you could rewrite the package resource to look something like the following:

package "tcpdump" do
   action :install
  version "X.Y.Z"
end

If you read the documentation for the package resource or examine the full constructor for the package class, you will see that it has a number of other attributes as well as what they do and where their default values come from. All the resources follow this form; they are simply Ruby classes that have an expected structure, which they inherit from the base resource class.