Puppet, Parameterized classes .vs. Definitions

Firstly, a little background on this topic. During PuppetConf this year I attended a very interesting talk by Digant C. Kasundra about the Puppet implementation at Stanford University. At one point he asked, “Who uses definitions?”, and I raised my hand. The next question was, “Who uses parameterized classes?”, and I also raised my hand, this was followed by “Who uses both?”, and I was one of a small minority of people who raised a hand again. The next question was, “Who knows what the difference between a parameterized class and a definition is?”. I took a second or two to think about this and the talk moved on after no-one in the audience raised their hand, and I didn’t get a chance to answer this in the Q&A due to running out of time, but I’ve been thinking about it since. Digant’s view was that the two are very similar and he advocates the use of definitions, which is certainly not a bad point of view, but I don’t think you should be using one or the other, but rather, use either one appropriately, and given the fact that no-one could answer Digant’s question in his talk, I felt it worth expanding on the issue in a blog post and would really welcome any feedback.

So, what is the core differences in how they are used? Well, the answer is, as Digant rightly pointed out, not a lot, but the small difference between them that does exist is very important, and should easily dictate which one you use in a given situation. Firstly, let’s look at what they actually are;

A class, parameterized or not, is a grouping of resources (including resources provided by definitions) that can be included into a manifest with one statement. If you make your classs parameterized then you can include that class with dynamic parameters that you can override depending on how your catalog is compiled. A definition is a template that defines what effectively is no different from any other resource type and gives you a boiler plate solution for applying a series of resources in a certain way.

So now it’s fairly clear that these two things are actually quite different, but you may be thinking, “sure, they’re different but there is nothing that a parameterized class does that you can’t do with a definition, right?” – well, yes, that’s right, but there is plenty that a definition does that you may not want it to do, mainly, puppet allowing it to be instantiated multiple times.

As an example of this, at the BBC we have a core application, let’s call it acmeapp for semantics sake. The core application takes almost 50 different parameters for configuration and can get deployed differently depending on what type of server profile we are deploying to. We only ever want acmeapp defined once as it is responsible for some core resources such as creating the base install directories, therefore we use a parameterized class for this and sub classes can inherit of this and override any number of variables. Either way, I’m only ever going to apply the acmeapp class once in my catalog. The end result looks something like;

class profiles::acmeapp  { 
    class { "::acmeapp" : } 
}
 
.....
 
class profiles::acmeapp::sometype inherits profiles::acmeapp { 
    Class["::acmeapp"] { 
        somevar    => "customvalue",
    }
}

Whilst the above is possible with a definition, it would allow it to be defined multiple times, which means the resources within the definition would end up duplicated and my catalog failing. I would rather Puppet fails because I’ve tried to use my class twice, rather than fail because I’ve duplicated a resource, such as the file type that creates the installation directory. It makes it very clear to anyone working with my Puppet code that this class should only be applied once.

Now, lets look at another example. At multiple points in our manifests we need to set up MySQL grants – to initiate an Exec for every grant would be bad practice, as we’ll end up in a lot of code re-use. This is where definitions come in. At the BBC we have a mysql class that not only provides the MySQL packages and services, but also exposes some useful functions to manage your databases and grants through a series of definitions, this is the code to the definition that controls MySQL grants….

class mysql { 
 
...
 
        # Definition: mysql::grant
        # 
        # Description: Add a grant/user to MySQL
        # 
        # Usage: 
        #    mysql::grant { "username":
        #               db              => "databasename",
        #               password        => "password"
        #    }
        # 
        # Options:
        #       db              - Name of the database
        #       password        - Password for the grant
        #       tables          - Tables to grant privileges (default: *)
        #       privs           - List of privileges to grant (default: ALL PRIVILEGES)
        #       host            - Host to grant privs to (default: localhost)
        #       user            - Specify username here to override the resource title default
        #       dbhost          - Specify the mysql server to connect to, different to "host"
        # 
        # 
        # 
        define grant (
                        $db,
                        $password,
                        $privs          = "ALL PRIVILEGES",
                        $tables         = "*",
                        $host           = "localhost",
                        $user           = false,
                        $dbhost         = false,
                        $grantopt       = false
 
 
        ) {
 
                $sqlgrantopt = $grantopt ? {
                        false   => "",
                        true    => " WITH GRANT OPTION",
                        default => ""
                }
 
                $myuser = $user ? {
                        false   => $title,
                        default => $user
                }
 
                # Determine if we need to pass a -h argument to mysql client
                # to tell it where to set the database up - grant root permissions
                # on the mysql server must be set up before this is run
                # If no host is given MySQL will assume localhost
                #
                $hostflag = $dbhost ? {
                        false   => "",
                        default => "-h $dbhost"
                }
 
 
                $sql = "GRANT $privs ON $db.$tables TO $myuser@'$host' IDENTIFIED BY '$password' $sqlgrantopt"
                $grantsql = "SHOW GRANTS FOR '$myuser'@'$host'"
                $grantgrep = "grep \"GRANT $privs ON \\`$db\\`.$tables TO '$myuser'@'$host'$sqlgrantopt\""
 
                $mysql = "mysql -u root -p$mysql::mysql_root_pw $hostflag"
                exec { "mysql::grant::$db::$title":
                        path    => "/usr/bin:/sbin:/usr/sbin:/bin",
                        command => "$mysql -e \"$sql\"",
                        unless  => "$mysql -e \"$grantsql\" | $grantgrep",
                        require => Package["mysql"],
                }
        }
}

As you can see, this is very different to our usage or a parameterized class, here we’ve templated a significant amount of functionality into one easy-to-use defined resource type. We can re-use this functionality as many times as we need, in the above example we set up two grants for the acmeapp application, one for a VIP IP address and one for an application IP range by specifying something like:

    ## Setup grants.
        mysql::grant {
                        "acmeapp_vip":
                                user            => "acmeapp",
                                host            => $vip_ip,
                                db              => "acmeapp",
                                dbhost          => $dbhost,
                                password        => "acmepass",
                                grantopt        => true,
                                require         => Mysql::Database["acmeapp"];
 
                        "acmeapp_ip_range":
                                user            => "acmeapp",
                                host            => $app_ip_range,
                                db              => "acmeapp",
                                dbhost          => "$dbhost",
                                password        => "acmepass",
                                grantopt        => true,
                                require         => Mysql::Database["acmeapp"];
        }

Hopefully this gives you a good idea of the subtle differences between parameterized classes and definitions, but also that they are both very independent features of Puppet that have their own uses.

Subscribe to Craig Dunn

Sign up now to get access to the library of members-only issues.
Jamie Larson
Subscribe