Puppet - Possible to use software design patterns in modules?
- by Mike Purcell
As I work with puppet, I find myself wanting to automate more complex setups, for example vhosts for X number of websites. As my puppet manifests get more complex I find it difficult to apply the DRY (don't repeat yourself) principle. Below is a simplified snippet of what I am after, but doesn't work because puppet throws various errors depending up whether I use classes or defines. I'd like to get some feed back from some seasoned puppetmasters on how they might approach this solution.
# site.pp
import 'nodes'
# nodes.pp
node nodes_dev {
$service_env = 'dev'
}
node nodes_prod {
$service_env = 'prod'
}
import 'nodes/dev'
import 'nodes/prod'
# nodes/dev.pp
node 'service1.ownij.lan' inherits nodes_dev {
httpd::vhost::package::site { 'foo': }
httpd::vhost::package::site { 'bar': }
}
# modules/vhost/package.pp
class httpd::vhost::package {
class manage($port) {
# More complex stuff goes here like ensuring that conf paths and uris exist
# As well as log files, which is I why I want to do the work once and use many
notify { $service_env: }
notify { $port: }
}
define site {
case $name {
'foo': {
class 'httpd::vhost::package::manage':
port => 20000
}
}
'bar': {
class 'httpd::vhost::package::manage':
port => 20001
}
}
}
}
}
That code snippet gives me a Duplicate declaration: Class[Httpd::Vhost::Package::Manage] error, and if I switch the manage class to a define, and attempt to access a global or pass in a variable common to both foo and bar, I get a Duplicate declaration: Notify[dev] error.
Any suggestions how I can implement the DRY principle and still get puppet to work?
-- UPDATE --
I'm still having a problem trying to ensure that some of my vhosts, which may share a parent directory, are setup correctly. Something like this:
node 'service1.ownij.lan' inherits nodes_dev {
httpd::vhost::package::site { 'foo_sitea': }
httpd::vhost::package::site { 'foo_siteb': }
httpd::vhost::package::site { 'bar': }
}
What I need to happen is that sitea and siteb have the same parent "foo" folder. The problem I am having is when I call a define to ensure the "foo" folder exists. Below is the site define as I have it, hopefully it will make sense what I am trying to accomplish.
class httpd::vhost::package {
File {
owner => root,
group => root,
mode => 0660
}
define site() {
$app_parts = split($name, '[_]')
$app_primary = $app_parts[0]
if ($app_parts[1] == '') {
$tpl_path_partial_app = "${app_primary}"
$app_sub = ''
} else {
$tpl_path_partial_app = "${app_primary}/${app_parts[1]}"
$app_sub = $app_parts[1]
}
include httpd::vhost::log::base
httpd::vhost::log::app { $name:
app_primary => $app_primary,
app_sub => $app_sub
}
}
}
class httpd::vhost::log {
class base {
$paths = [ '/tmp', '/tmp/var', '/tmp/var/log', '/tmp/var/log/httpd', "/tmp/var/log/httpd/${service_env}" ]
file { $paths:
ensure => directory
}
}
define app($app_primary, $app_sub) {
$paths = [ "/tmp/var/log/httpd/${service_env}/${app_primary}", "/tmp/var/log/httpd/${service_env}/${app_primary}/${app_sub}" ]
file { $paths:
ensure => directory
}
}
}
The include httpd::vhost::log::base works fine, because it is "included", which means it is only implemented once, even though site is called multiple times. The error I am getting is: Duplicate declaration: File[/tmp/var/log/httpd/dev/foo]. I looked into using exec, but not sure this is the correct route, surely others have had to deal with this before and any insight is appreciated as I have been grappling with this for a few weeks. Thanks.