Global Definitions and Relative Scope in Puppet

Posted by Ryan Uber | Puppet | Saturday 14 May 2011 11:52 am

After completing some big piece of code, before committing it is almost always helpful to take a step back and look at it at a high level. Look for patterns and ways it can be simplified. Using software like puppet, you will inevitably find many, many patterns to the things you normally do with Unix systems. For instance, file permissions and group ownership. Recently, I realized that I used the mode, owner, group, and notify tags far too often throughout my puppet manifests when dealing with individual files.

I realized that not all of the files need the same permissions, and not all of them need the same owner or group. Most manifests, however, will contain 3 basic types: a package, a file, and a service, or “PFS“. These 3 types are related to the same thing, and most likely will have the same owner and the same group at the minimum. My thinking behind this is, you should only need to specify what user / group is going to own these files and folders one time, and have it apply to everything else.

To demonstrate, here is what a typical puppet manifest might look like, writing everything out, plain and simple:

file {

    "file_1":
        path    => "/path/to/file_1",
        owner   => "someuser",
        group   => "somegroup",
        mode    => 0644,
        content => "This is a test.\n";

    "file_2":
        path    => "/path/to/file_2",
        owner   => "someuser",
        group   => "somegroup",
        mode    => 0644,
        content => "This is another test.\n";
}

You can already see the patterns emerging even though we have only defined two files so far. So lets say we have 5 of these files. With the above method of defining the files / attributes, your manifests might become quite long, especially if you are defining more attributes that are common, like a require or maybe a notify.

The following example achieves the exact same effect as specifying the owner, group, and mode in each file definition, saving us 15 lines of duplicate definitions in just 5 file statements:

File {
    owner   => "someuser",
    group   => "somegroup",
    mode    => 0644
}

file {

    "file_1":
        path    => "/path/to/file_1",
        content => "This is a test.\n";

    "file_2":
        path    => "/path/to/file_2",
        content => "This is another test.\n";

    "file_3":
        path    => "/path/to/file_3",
        content => "This is test 3.\n";

    "file_4":
        path    => "/path/to/file_4",
        content => "This is test 4.\n";

    "file_5":
        path    => "/path/to/file_5",
        content => "This is test 5.\n";
}

Now things might get a little trickier. You obviously won’t have every file you manage owned by the same user, or the same group, or with the same permissions. Two things come into play here:

  1. The fact that we are only defining the defaults, and
  2. Scope

Since what we have specified for owner, group, and mode already are only the defaults, you can still define those attributes per-file. For instance:

File {
    owner   => "someuser",
    group   => "somegroup",
    mode    => 0644
}

file {

    "file_1":
        path    => "/path/to/file_1",
        content => "This is a test.\n";

    "file_2":
        path    => "/path/to/file_2",
        owner   => "someotheruser",
        content => "This is another test.\n";
}

In the above example, “file_1″ will get the defaults for all 3 attributes, and therefore be owned by “someuser:somegroup”. “file_2″, however, overrides the default owner, and will thus have ownership of “someotheruser:somegroup”.

Now for scope. Suppose I have an Apache class and a MySQL class. These should not have the same ownership. However, if I have defined the files, services, and other things related to each piece of software in separate classes, then I am in luck.

Global defaults can be defined per-class, and are inherited.

File {
    owner   => "root",
    group   => "root",
    mode    => 0700;
}

class "mysql"
{
    File {
        owner   => "mysql",
        group   => "mysql",
        mode    => 0644
    }

    file { "my.cnf":
        path    => "/etc/my.cnf",
        content => "This is a test.\n";
    }
}

class "httpd"
{
    File {
        owner   => "apache",
        group   => "apache",
        mode    => 0644
    }

    file { "httpd.conf":
        path    => "/etc/httpd/conf/httpd.conf",
        content => "This is another test.\n";
    }
}

You can see how the global defaults are defined at the top here, as “root:root” with “0700″ permissions. Then, within each class, new defaults are set for the files, and therefore, within the scope of that class only, all file statements get the class-specific permissions.

Use this technique throughout your manifests, and you will notice they will start to appear much more simplistic and organized while accomplishing the same result. Also keep in mind that this makes it infinitely easier to modify attributes at a wide scale without re-keying the modifications again and again. Do be warned, however, adding a new attribute to a defaults definition will affect many files, so be sure that it will not negatively impact any one particular item in your manifests.

Simple and efficient cron management

Posted by Ryan Uber | MySQL,Puppet | Thursday 29 April 2010 1:57 am

Keeping track of scheduled cron jobs is not always the easiest thing to do with Puppet — especially if you are managing systems that other people may have added their own cron jobs to, whether that be within the cron configuration files or in an individual user’s crontab.

Equally difficult is managing a plethora of servers and being responsible for performing and maintaining the backups for all of them. A small part of this includes dumping MySQL databases into a portable format on a regular basis.

Puppet makes both of these tasks easy. So, in order to get the backups running via cron, let’s explore some options.

Managing cron jobs
Keeping tabs on your cron jobs (no pun intended) is actually a lot simpler than one would think with Puppet. However it took me a few go’s at implementation before I came to a sound solution.

The first thing I tried was using Puppet’s built-in cron class, like this:

cron { "dbdump":
    command => "/usr/local/sbin/dbdump",
    user    => "root",
    hour    => 0,
    minute  => 0
}

As you see, I created a script called dbdump to be run by this cron entry. It is a simple bash script that performs a “SHOW DATABASES;”, loops through them, and dumps them all to flat text files utilizing mysqldump.
(more…)

Conditionally create a file within a puppet manifest

Posted by Ryan Uber | Bourne-Again Shell (bash),Puppet | Saturday 17 April 2010 4:40 pm

I came up with this solution to conditionally create / initialize a configuration file using Puppet. What I wanted to do was create the file if it did not exist or if it was empty, add my header comments to it. As far as I know, and after an hour or so of poking around Puppet’s documentation,  I could not find an integrated way of doing this. I like to avoid using rickity methods of accomplishing something wherever possible, so I was about to scrap the idea altogether. However, I came up with a moderately elegant solution that I was satisfied with to accomplish just this, using an “unless” requirement. Here is the actual code, modified for this post:

class httpd {
    $httpd_extras_file = "/etc/httpd/conf.d/zz_extras.conf"
    $httpd_extras_text = "# ADDITONAL APACHE CONFIGURATION FILE"
    exec { "c_zzhttpdextras":
        command     => "/bin/echo '$httpd_extras_text' > $httpd_extras_file",
        unless      => "/usr/bin/test -s $httpd_extras_file",
        require     => [
                        File["dir_httpd_confd"],
                        Package["httpd"]
                       ];
    }
}

(more…)

« Previous Page