Posts Tagged ‘provider’

Puppet Module Repository isn’t just for modules

June 1st, 2010

You can store more than just your modules at the Forge. :)   I just added my types and providers to my collection of modules at the new Puppet Module Forge.  I’d love to all those people maintaining types and providers, functions, and facts add theirs to the Forge also.  It’s a cool way to share your (and the site allows you to provide links back to your and ticketing system so user’s can report bugs).  In time I hope most people’s environments will consist of the core types and providers bundled with Puppet and a selection of cool generated by the community and sourced from the Puppet Forge.

Puppet Forge in beta!

May 27th, 2010

The Puppet Forge AKA the Puppet Module Repository is live and operational.  It’s a store of Puppet modules (and types and providers) that allows you to share your awesome and modules with others.

It also comes with the puppet-module tool that allows you to build modules for, manage and install modules from the forge.  You can install puppet-module via a gem:

$ sudo gem install puppet-module

Both the site and tool are in public right now so hammer away at it and tell us what you think!

Puppet ParsedFile types and providers

February 13th, 2010

In a recent post I talked about how easy it is to generate Puppet types and providers. In that post I used the example of a very simple Subversion and , called repo. I’d like to show another example of a and , this one used to manage the contents of the /etc/ file. This and makes use of some built-in Puppet functionality that allows the simple parsing of files and the management of their contents. To do this Puppet has a called that can be included into your own providers to provide this functionality.

Let’s start with our :

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

So – pretty simple. We create a block Puppet::.newtype(:) do that creates a new , which we’ve called . Inside the block we’ve got a @doc string. This is the documentation for the . Add whatever level of detail and examples in here that is required.

We’ve also got the ensurable statement. Ensurable provides some “automagic” that creates a basic ensure property. Puppet types use the ensure property to determine the state of a configuration item. In our previous example, ensurable resulted in three methods in the : create, destroy, and exists?. In a we don’t use these methods at all as we’ll see shortly but rather specify how to handle each record in the file.

We’ve defined a new parameter – this one called shell.

newparam(:shell) do
        desc "The shell to manage"
 
        isnamevar
end

The shell parameter is the shell we’re going to manage in the /etc/ file. We’ve also used another piece of Puppet automagic, isnamevar, to make this parameter the “name” variable for this . In Puppet-speak, the value of this parameter is used as the name of the resource.

Lastly in our we’ve specified an optional parameter, target, that allows us to override the default location of the file, usually /etc/.

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

The target parameter is optional and would only be specified if the file wasn’t located in the /etc/ directory. It uses the defaultto structure to specify that the default value for the parameter is the value of default_target variable in the .

The for our is also very simple:

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

The is stored in a file called parsed.rb in a directory named for the in the directory, for example:

/usr/lib/ruby/site_ruby/1.8/puppet//.rb
/usr/lib/ruby/site_ruby/1.8/puppet///parsed.rb

The file needs to be named parsed.rb to allow Puppet to load the support.

We first include the at the top of our , require 'puppet//' and set a variable called to the location of the /etc/ file. We’re going to use this variable a bit later.

Then we tell Puppet that this is a called . We specify a :parent value that tells Puppet that this should inherit the and make its functions available. We then specify the :default_target value to the variable we’ve just created. This tells the , that unless it is overridden by the target attribute, that the file to act upon is /etc/.

Then we use a desc method that allows us to add some documentation to our .

The next lines are the core of the . They tell the Puppet how to manipulate the target file to add or remove the required shell. The first two lines, both text_lines, tell Puppet how to match comments and blank lines respectively.

    text_line :comment, :match => /^#/;
    text_line :blank, :match => /^\s*$/;

We specify these to let Puppet know to ignore these lines as unimportant.

The next line performs the actual parsing of the relevant line in the /etc/ file:

    record_line :parsed, :fields => %w{name}

The record_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/newshell":
    ensure => present,

Then Puppet would use the to add the /bin/newshell by parsing each line of the /etc/ file and checking if the newshell is present. If it is, then Puppet will do nothing. If not, then Puppet will add newshell to the file. If we changed the ensure attribute to absent then Puppet would go through the file and remove the newshell if it is present.

It is important to remember that providers do have some limitations, they aren’t good at managing complex files such as configuration files with multi-line options, they are best for simple files that contain single line lists of entries such as the cron file entries or the /etc/hosts and /etc/ files.

You can see the complete for this and its providers at my Puppet repository on GitHub. Quite a lot of the existing Puppet types and providers use providers (the cron for example) and you can use these as examples of how to create your own providers. You can also find further documentation (in a lot more detail!) creating your own types and providers at the Puppet wiki.

Creating Puppet types and providers is easy…

February 1st, 2010

Puppet types are used to manage individual configuration items.  Puppet has a package , a service , a user , etc.  Each has providers. Each handles the management of that configuration a different platform or tool, for example the package has aptitude, yum, RPM, and DMG providers (amongst 22 others – what is wrong with people that they need to invent new packaging systems… but I digress).

There are a lot of types, in fact I think Puppet covers a pretty good spectrum of configuration items that need to be managed.  I don’t know of anything in particular that is missing that I can’t live without.  But there are little gaps that are annoying, I’d like network and firewall types for example, but creating both these types in a generic enough way to support multiple platforms would be, IMHO, a non-trivial problem. 

Another gap is VCS/DVCS management. A lot of people use source in repositories to do things with (including install stuff from you bad people – package things … it’s healthier). Puppet currently relies creating and removing these repositories with the exec (which executes scripts or binaries), for example:

exec { "svn co http://core.svn.wordpress.org/trunk/ /var/www/wp":
    creates => "/var/www/wp",
}

This is a bit ugly and it’d be a lot easier to write a Puppet to manage repositories. But Puppet types and providers are written in Ruby and really, really complex and hard to develop. Right? Right?

No. No, they are not… and I’m going to create a simple and to show you. :)

Here’s a very (very!) simple Puppet , called repo, for managing repositories. I’ve created providers for SVN and as examples also. The first part of the repo is the itself – these are usually stored in lib/puppet/ or distributed via modules (see the PluginsInModules page in the Puppet wiki). I’ll create a file called repo.rb.

$ touch repo.rb

And then populate the file:

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

So – pretty simple. We create a block Puppet::.newtype(:repo) do that creates a new , which we’ve called repo.

Inside the block we’ve got a @doc string. This is the documentation for the . Add whatever level of detail and examples in here that is required.

We’ve also got the ensurable statement. Ensurable provides some “automagic” that creates a basic ensure property. Puppet types use the ensure property to determine the state of a configuration item.

service { "sshd":
    ensure => present,
}

The ensurable statement tells Puppet to expect three methods: create, destroy and exists? in our . These methods, allow, respectively:

  • A command to create the resource
  • A command to delete the resource, and
  • 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 but more this when we look at our providers.

Next, we’ve defined a new parameter – this one 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 where to go to retrieve/clone/checkout our source .

In this parameter we’re also using a hook called validate. Normally used to check the value for appropriateness here we’re using it to take a guess at what to use. Our says, if the source parameter starts with then use the , if not default to the Subversion . This is obviously fairly crude as a default and we can override this by defining the attribute in our resources:

provider => git,

We’ve also used another piece of Puppet automagic, isnamevar, to make this parameter the “name” variable for this . In Puppet-speak, the value of this parameter is used as the name of the resource.

(Types have two kinds of values – properties and parameters. Properties “do things”. They tell us HOW the works. We’ve only defined one property, ensure, by using the ensurable statement. Parameters are more like variables, they contain information relevant to configuring the resource the manages rather than “doing things”.)

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 variable value that specifies where the repo should put the cloned/checked-out . In this parameter we’ve again used the validate hook to create a block that checks the value for appropriateness. Here 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 the source parameter to confirm a valid source URL/location was being provided.

(You can also use another hook called munge to adjust the value of the parameter rather than validating it before passing it to the .)

And that is it for the .

Next, we need to create a for our . Let’s start with a Subversion like so:

require 'fileutils'
 
Puppet::Type.type(:repo).provide(:svn) do
    desc "SVN Support"
 
    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

Up front we’ve required the fileutils library, which we’re going to use a method from. Next, we’ve defined the as a block:

Puppet::Type.type(:repo).provide(:svn) do

We tell Puppet that this is a called svn for the called repo.

Then we use a desc method that allows us to add some documentation to our .

Next, we define the commands that this will use, here the svn and svnadmin binaries, to manipulate our resource’s configuration.

    commands :svncmd => "svn"
    commands :svnadmin => "svnadmin"

Puppet uses these commands to determine if the is appropriate to use a client, if Puppet can’t find these commands in the local path then it will disable the .

Next, we’ve defined three methods – create, destroy and exists?. Sounds familiar? Yep, these are the methods that the ensurable statement expects to find in the :

The create method ensures our resource is created. It uses the svn command to create a with a source of resource[:name] (remember the source parameter in our is also the name variable of the – we could also specify resource[:source] here too) and a destination of resource[:path] (the value of the path attribute).

The delete method ensures the deletion of the resource. In this case, it deletes the directory and files specified in the resource[:path] parameter.

Lastly, the exists? 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:

  • If exists? is false and ensure is present, then create method will be called.
  • If exists? is true and ensure is set to absent, then the destroy method will be called.

In this case the exists? method checks if there is already a directory at the location specified in the resource[:path] parameter.

So, let’s put all this together and create a resource with our new . I’ve assumed you’ve already distributed your and providers to Puppet. We can then create a resource like:

repo { "wp":
    source => "http://core.svn.wordpress.org/trunk/",
    path => "/var/www/wp",
    ensure => present,
}

Simple eh? We specify a repo resource, the source we wish to check out or clone from, the destination path and the ensure attribute (present or absent) and that’s it.

You can see the complete for this and its providers at my Puppet repository on GitHub. It’s obviously very basic but should be easy to extend to provide additional capabilities (and currently has no tests – my bad). You can find further documentation (in a lot more detail!) creating your own types and providers at the Puppet wiki.

Contractually obligated

June 17th, 2004

Well the big day has arrived. Tonight we sign a contract with a builder. Then lodge a construction certificate with the council. Once that’s approved we start work. In a week or so we have another house. A 2 bedroom place round the corner from us – thankfully really, really close. Perfect for visits to the house to check progress.

The builder thinks 70 days of work. Which would be awesome and quick. Of course this being said I am sure it’ll take longer. :) Which is not all a bad thing since I have spoken to the ’s of the ‘Kitchen ‘o wonder’ mentioned previously. They need 3 months to prep the kitchen. Which means we might have a house with no kitchen for a while. Well we can live with that. One step at a time.

Now how to break the move to the cats. They really don’t like change. :)