Authors: Jeffrey McCune James Turnbull
We've also used another piece of Puppet auto-magic, theisnamevar
method, to make this parameter the “name” variable for this type so that the value of this parameter is used as the name of the resource.
Finally, we've defined another parameter,path
:
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
This is a parameter value that specifies where the repo type should put the cloned/checked-out repository. In this parameter we've again used thevalidate
hook to create a block that checks the value for appropriateness. In this case we're just checking, very crudely, to make sure it looks like the destination path is a valid, fully-qualified file path. We could also use this validation for thesource
parameter to confirm that a valid source URL/location is being provided.
Next, we need to create a Subversion provider for our type. We create the provider and put it into:
custom/lib/puppet/provider/repo/svn.rb
You can see the Subversion provider in
Listing 10-6
.
Listing 10-6.
The Subversion provider
require 'fileutils'
Puppet::Type.type(:repo).provide(:svn) do
desc "Provides Subversion support for the repo type"
commands :svncmd => "svn"
commands :svnadmin => "svnadmin"
def create
svncmd "checkout", resource[:name], resource[:path]
end
def destroy
FileUtils.rm_rf resource[:path]
end
def exists?
File.directory? resource[:path]
end
end
In the provider code, we first required thefileutils
library, which we're going to use some methods from. Next, we defined the provider block itself:
Puppet::Type.type(:repo).provide(:svn) do
We specified that the provider is calledsvn
and is a provider for the type calledrepo
.
Then we used thedesc
method, which allows us to add some documentation to our provider.
Next, we defined the commands that this provider will use, thesvn
andsvnadmin
binaries, to manipulate our resource's configuration:
commands :svncmd => "svn"
commands :svnadmin => "svnadmin"
Puppet uses these commands to determine if the provider is appropriate to use on an agent. If Puppet can't find these commands in the local path, then it will disable the provider. Any resources that use this provider will fail and Puppet will report an error.
Next, we defined three methods -create
,destroy
andexists?
. These are the methods that theensurable
statement expects to find in the provider.
Thecreate
method ensures our resource is created. It uses thesvn
command to check out a repository specified byresource[:name]
. This references the value of the name parameter of the type. In our case, thesource
parameter in our type is also the name variable of the type, so we could also specifyresource[:source]
. We also specified the destination for the checkout using theresource[:path]
hash.
Thedelete
method ensures the deletion of the resource. In this case, it deletes the directory and files specified by theresource[:path]
parameter.
Lastly, theexists?
method checks to see if the resource exists. Its operation is pretty simple and closely linked with the value of the ensure attribute in the resource:
exists?
is false andensure
is set topresent
, then thecreate
method will be called.exists?
is true andensure
is set toabsent
, then thedestroy
method will be called.In the case of our method, theexists?
method works by checking if there is already a directory at the location specified in theresource[:path]
parameter.
We can also add another provider, this one for Git, in:
custom/lib/puppet/provider/repo/git.rb
We can see this provider in
Listing 10-7
.
Listing 10-7.
The Git provider
require 'fileutils'
Puppet::Type.type(:repo).provide(:git) do
desc "Provides Git support for the repo provider"
commands :gitcmd => "git"
def create
gitcmd "clone", resource[:name], resource[:path]
end
def destroy
FileUtils.rm_rf resource[:path]
end
def exists?
File.directory? resource[:path]
end
end
You can see that this provider is nearly identical to the Subversion provider we saw in
Listing 10-3
. We used thegit
command and itsclone
function rather than the Subversion equivalents, but you can see that thedestroy
andexists?
methods are identical.
Once you've got your type and providers in place, you can run Puppet and distribute them to the agents you wish to use therepo
type in and create resources that use this type, for example:
repo { "wordpress":
source => "http://core.svn.wordpress.org/trunk/",
path => "/var/www/wp",
provider => svn,
ensure => present,
}
Note
You can find a far more sophisticated version of therepo
type, and with additional providers, at
https://github.com/puppetlabs/puppet-vcsrepo
You've just seen a very simple type and provider that uses commands to create, delete and check for the status of a resource. In addition to these kinds of types and providers, Puppet also comes with a helper that allows you to parse and edit simple configuration files. This helper is called ParsedFile.
Unfortunately, you can only manage simple files with ParsedFile, generally files with single lines of configuration like the/etc/hosts
file or the example we're going to examine. This is a type that manages the/etc/shells
file rather than multi-line configuration files.
To use a ParsedFile type and provider, we need to include its capabilities. Let's start with our/etc/shells
management type which we're going to callshells
. This file will be located in:
custom/lib/puppet/type/shells.rb.
Let's start with our type in
Listing 10-8
.
Listing 10-8.
The shells type
Puppet::Type.newtype(:shells) do
@doc = "Manage the contents of /etc/shells
shells { "/bin/newshell":
ensure => present,
}"
ensurable
newparam(:shell) do
desc "The shell to manage"
isnamevar
end
newproperty(:target) do
desc "Location of the shells file"
defaultto {
if @resource.class.defaultprovider.ancestors.include? (Puppet::Provider::ParsedFile)
@resource.class.defaultprovider.default_target
else
nil
end
}
end
end
In our type, we've created a block,Puppet::Type.newtype(:shells)
, that creates a new type, which we've calledshells
. Inside the block we've got a@doc
string. As we've already seen, this should contain the documentation for the type; in this case, we've included an example of theshells
resource in action.
We've also used theensurable
statement to create the basic create, delete and exists ensure structure we saw in our previous type.
We then defined a new parameter, calledshell,
that will contain the name of the shell we want to manage:
newparam(:shell) do
desc "The shell to manage"
isnamevar
end
We also used another piece of Puppet automagic that we saw earlier,isnamevar
, to make this parameter the name variable for this type.
Lastly, in our type we specified an optional parameter,target
, that allows us to override the default location of the shells file, usually/etc/shells
.
Thetarget
parameter is optional and would only be specified if theshells
file wasn't located in the/etc/
directory. It uses thedefaultto
structure to specify that the default value for the parameter is the value ofdefault_target
variable, which we will set in the provider.
Let's look at the shells provider now, in
Listing 10-9
.
Listing 10-9.
The shells provider
require 'puppet/provider/parsedfile'
shells = "/etc/shells"
Puppet::Type.type(:shells).provide(:parsed, :parent => Puppet::Provider::ParsedFile,
:default_target => shells, :filetype => :flat) do
desc "The shells provider that uses the ParsedFile class"
text_line :comment, :match => /^#/;
text_line :blank, :match => /^\s*$/;
record_line :parsed, :fields => %w{name}
end
Unlike other providers, ParsedFile providers are stored in a file calledparsed.rb
located in the provider's directory, here:
custom/lib/puppet/provider/shells/parsed.rb
The file needs to be namedparsed.rb
to allow Puppet to load the appropriate ParsedFile support (unlike other providers, which need to be named for the provider itself).
In our provider, we first need to include the ParsedFile provider code at the top of our provider using a Rubyrequire
statement:
require 'puppet/provider/parsedfile'
We then set a variable calledshells
to the location of the/etc/shells
file. We're going to use this variable shortly.
Then we tell Puppet that this is a provider calledshells
. We specify a:parent
value that tells Puppet that this provider should inherit the ParsedFile provider and make its functions available. We then specify the:default_target
variable to theshells
variable we just created. This tells the provider, that unless it is overridden by thetarget
attribute in a resource, that the file to act upon is/etc/shells
.
We then use adesc
method that allows us to add some documentation to our provider.
The next lines in the provider are the core of a ParsedFile provider. They tell the Puppet how to manipulate the target file to add or remove the required shell. The first two lines, both calledtext_line,
tell Puppet how to match comments and blank lines, respectively, in the configuration file. You should specify these for any file that might have blank lines or comments:
text_line :comment, :match => /^#/;
text_line :blank, :match => /^\s*$/;
We specify these to let Puppet know to ignore these lines as unimportant. Thetext_line
lines are constructed by specifying the type of line to match, a comment or a blank, then specifying a regular expression that specifies the actual content to be matched.
The next line performs the actual parsing of the relevant line of configuration in the/etc/shells
file:
record_line :parsed, :fields => %w{name}
Therecord_line
parses each line and divides it into fields. In our case, we only have one field, name. The name in this case is the shell we want to manage. So if we specify:
shells { "/bin/anothershell":
ensure => present,
}
Puppet would then use the provider to add the/bin/anothershell
by parsing each line of the/etc/shells
file and checking if the/bin/anothershell
shell is present. If it is, then Puppet will do nothing. If not, then Puppet will addanothershell
to the file.
If we changed theensure
attribute toabsent,
then Puppet would go through the file and remove theanothershell
shell if it is present.
This is quite a simple example of a ParsedFile provider. There are a number of others that ship with Puppet, for example thecron
type, that can demonstrate the sophisticated things you can do with the ParsedFile provider helper.