Authors: Jeffrey McCune James Turnbull
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.
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 ourslib/ruby/facter
. Situate this structure beneath theroot
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 ourhome
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.
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.
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 calledapache
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 thelib
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. Thelib/puppet/type
andlib/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 thepluginsync
option in the[main]
section of the Puppet master'spuppet.conf
configuration file, as follows:
[main]
pluginsync = true
Thepluginsync
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.
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 callrepo
. 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:
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:
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 thelib/puppet/type
directory, we're going to create a file calledrepo.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 thePuppet::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 isensurable
. Theensurable
statement is a useful shortcut that tells Puppet to create anensure
property for this type. Theensure
property determines the state of the configuration item, for example:
service { “sshd”:
ensure => present,
}
Theensurable
statement tells Puppet to expect three methods:create
,destroy
andexists?
in our provider (You'll see the code for this in
Listing 10-6
). These methods are, respectively:
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 theensurable
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, calledsource
:
newparam(:source) do
desc "The repo source"
validate do |value|
if value =~ /^git/
resource[:provider] = :git
else
resource[:provider] = :svn
end
end
isnamevar
end
Thesource
parameter will tell therepo
type where to go to retrieve, clone, or check out our source repository.
In thesource
parameter we're also using a hook calledvalidate
. 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 thevalidate
hook, Puppet also has themunge
hook. You can use themunge
hook to adjust the value of the parameter rather than validating it before passing it to the provider.
Ourvalidate
code specifies that if thesource
parameter starts withgit,
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 theprovider
attribute in your resource, like so:
repo { “puppet”:
source => “git://github.com/puppetlabs/puppet.git”,
path => “/home/puppet”,
provider => git,
ensure => present,
}