Pro Puppet (53 page)

Read Pro Puppet Online

Authors: Jeffrey McCune James Turnbull

BOOK: Pro Puppet
11.58Mb size Format: txt, pdf, ePub

This fact actually creates a series of facts, each fact taken from information collected from the
/etc/networks
file. This file associates network names with networks. Our snippet parses this file and adds a series of facts, one per each network in the file. So, if our file looked like:

default    0.0.0.0
loopback    127.0.0.0
link-local    169.254.0.0

Then three facts would be returned:

network_default => 0.0.0.0
network_loopback => 127.0.0.0
network_link-local => 169.254.0.0

You can take a similar approach to commands, or files, or a variety of other sources.

Testing the Facts

There is a simple process for testing your facts: Import them into Facter and use it to test them before using them in Puppet. To do this, you need to set up a testing environment. Create a directory structure to hold our test facts—we'll call ours
lib/ruby/facter
. Situate this structure beneath the
root
user's home directory. Then create an environmental variable,
$RUBYLIB
, that references this directory and will allow Facter to find our test facts:

# mkdir -p ~/lib/ruby/facter
# export RUBYLIB=~/lib/ruby

Then copy your fact snippets into this new directory:

# cp /var/puppet/facts/home.rb $RUBYLIB/facter

After this, you can call Facter with the name of the fact you've just created. If the required output appears, your fact is working correctly. On the following lines, we've tested our
home
fact and discovered it has returned the correct value:

# facter home
/root

If your fact is not working correctly, an error message you can debug will be generated.

Facts just scratch the surface of Puppet's extensibility, and adding to types, providers, and functions adds even more capability. We're going to demonstrate that in the next section.

Developing Custom Types, Providers and Functions

When developing custom types, providers and functions it is important to remember that Puppet and Facter are open-source tools developed by both Puppet Labs and a wide community of contributors. Sharing custom facts and resource types helps everyone in the community, and it means you can also get input from the community on your work. Extending Puppet or Facter is also an excellent way to give back to that community. You can share your custom code via the Puppet mailing list or on the Puppet Wiki, by logging a Redmine ticket, or by setting up your own source repository for Puppet code on the Puppet forge (
http://forge.puppetlabs.com
).

Lastly, don't underestimate the usefulness of code people before you have already developed that you can use and adapt for your environment. Explore existing Puppet modules, plug-ins, facts and other code via Google and on resources like GitHub. Like all systems administrators, we know that imitation is the ultimate form of flattery.

In the following sections, we demonstrate how to configure Puppet to distribute your own custom code. You'll also see how to write a variety of custom types and providers, and finally how to write your own Puppet functions.

Configuring Puppet for Types, Providers and Functions

The best way to distribute custom types, providers and functions is to include them in modules, using “plug-ins in modules,” the same concept we introduced earlier this chapter to distribute custom facts. Just like custom facts, you again place your custom code into a Puppet module and use that module in your configuration. Puppet will take care of distributing your code to your Puppet masters and agents.

Again, just like custom facts, you can take two approaches to managing custom code: placing it in function-specific modules or centralizing it into a single module. We're going to demonstrate adding custom code in a single, function-specific module.

So, where in our modules does custom code go? Let's create a simple module called
apache
as an example:

apache/
apache/manifests
apache/manifests/init.pp
apache/files
apache/templates
apache/lib/facter
apache/lib/puppet/type
apache/lib/puppet/provider
apache/lib/puppet/parser/functions

Here we've created our standard module directory structure, but we've added another directory,
lib
. We saw the
lib
directory earlier in the chapter when we placed custom facts into its Facter subdirectory. The lib directory also contains other “plug-ins” like types, providers and functions, which we want to add to Puppet. The
lib/puppet/type
and
lib/puppet/provider
directories hold custom types and providers respectively. The last directory,
lib/puppet/parser/functions
, holds custom functions.

Like we did when we configured Puppet for custom facts, you need to enable “plug-ins in modules” in your Puppet configuration. To do this, enable the
pluginsync
option in the
[main]
section of the Puppet master's
puppet.conf
configuration file, as follows:

[main]
pluginsync = true

The
pluginsync
setting, when set to true, turns on the “plug-ins in modules” capability. Now, when agents connect to the master, each agent will check its modules for custom code. Puppet will take this custom code and sync it to the relevant agents. It can then be used on these agents. The only exception to this is custom functions. Functions run on the Puppet master rather than the Puppet agents, so they won't be synched down to an agent. They will only be synched if the Puppet agent is run on the Puppet master, i.e., if you are managing Puppet with Puppet.

Note
In earlier releases of Puppet, “plug-ins in modules” required some additional configuration. You can read about that configuration on the Puppet Labs Documentation site at http://docs.puppetlabs.com/guides/plugins_in_modules.html.

Writing a Puppet Type and Provider

Puppet types are used to manage individual configuration items. Puppet has a package type, a service type, a user type, and all the other types available. Each type has one or more providers. Each provider handles the management of that configuration on a different platform or tool: for example, the package type has aptitude, yum, RPM, and DMG providers (among 22 others).

We're going to show you a simple example of how to create an additional type and provider, one that manages version control systems (VCS), which we're going to call
repo
. In this case we're going to create the type and two providers, one for Git and one for SVN. Our type is going to allow you to create, manage and delete VCS repositories.

A Puppet type contains the characteristics of the configuration item we're describing, for example in the case of VCS management type:

  • The name of the repository being managed
  • The source of the repository

Correspondingly, the Puppet providers specify the actions required to manage the state of the configuration item. Obviously, each provider has a set of similar actions that tell it how to:

  • Create the resource
  • Delete the resource
  • Check for the resource's existence or state
  • Make changes to the resource's content
Creating Our Type

Let's start by creating our type. We're going to create a module called custom to store it in:

custom/
custom/manifests/init.pp
custom/lib/puppet/type
custom/lib/puppet/provider

Inside the
lib/puppet/type
directory, we're going to create a file called
repo.rb
to store our type definition:

custom/lib/puppet/type/repo.rb

You can see that file in
Listing 10-5
.

Listing 10-5.
The repo type

Puppet::Type.newtype(:repo) do
    @doc = "Manage repos"
    ensurable
    newparam(:source) do         
        desc "The repo source"         
        validate do |value|
            if value =~ /^git/                
                resource[:provider] = :git            
            else                 
                resource[:provider] = :svn             
            end
        end     
        
        isnamevar    
    end    
    newparam(:path) do     
        desc "Destination path"      
    
        validate do |value|         
            unless value =~ /^\/[a-z0-9]+/     
                raise ArgumentError, "%s is not a valid file path" % value
            end
        end
    end
end

In this example, we start our type with the
Puppet::Type.newtype
block and specify the name of type to be created,
repo
. We can also see a
@doc
string which is where we specify the documentation for your type. We recommend you provide clear documentation including examples of how to use the type, for a good example have a look at the documentation provided for the Cron type at
https://github.com/puppetlabs/puppet/blob/master/lib/puppet/type/cron.rb
.

The next statement is
ensurable
. The
ensurable
statement is a useful shortcut that tells Puppet to create an
ensure
property for this type. The
ensure
property determines the state of the configuration item, for example:

service { “sshd”:
    ensure => present,
}

The
ensurable
statement tells Puppet to expect three methods:
create
,
destroy
and
exists?
in our provider (You'll see the code for this in
Listing 10-6
). These methods are, respectively:

  • A command to create the resource
  • A command to delete the resource
  • A command to check for the existence of the resource

All we then need to do is specify these methods and their contents and Puppet creates the supporting infrastructure around them. Types have two kinds of values - properties and parameters. Properties “do things.” They tell us how the provider works. We've only defined one property,
ensure
, by using the
ensurable
statement. Puppet expects that properties will generally have corresponding methods in the provider that we'll see later in this chapter. Parameters are variables and contain information relevant to configuring the resource the type manages, rather than “doing things.”

Next, we've defined a parameter, called
source
:

newparam(:source) do
  desc "The repo source"
  validate do |value|
    if value =~ /^git/
      resource[:provider] = :git
   else
     resource[:provider] = :svn
  end
 end
   isnamevar
end

The
source
parameter will tell the
repo
type where to go to retrieve, clone, or check out our source repository.

In the
source
parameter we're also using a hook called
validate
. It's normally used to check the parameter value for appropriateness; here, we're using it to take a guess at what provider to use.

Note
In addition to the
validate
hook, Puppet also has the
munge
hook. You can use the
munge
hook to adjust the value of the parameter rather than validating it before passing it to the provider.

Our
validate
code specifies that if the
source
parameter starts with
git,
then use the Git provider; if not, then default to the Subversion provider. This is fairly crude as a default, and you can override this by defining the
provider
attribute in your resource, like so:

repo { “puppet”:
  source => “git://github.com/puppetlabs/puppet.git”,
  path => “/home/puppet”,
  provider => git,
  ensure => present,
}

Other books

Moriarty by Anthony Horowitz
Floral Depravity by Beverly Allen
Gathering Shadows by Nancy Mehl
Fatal by Harold Schechter
Holly Hearts Hollywood by Conrad, Kenley
A Planet of Viruses by Carl Zimmer
Summer of the Midnight Sun by Tracie Peterson