The Roles and Profiles pattern by Craig Dunn is a very common pattern used by Puppet practitioners. I’ve written about it before. One of the most common questions I see is, what goes into a Role or Profile class? Craig’s article provides some guidelines, specifically these two:
- A role includes one or more profiles to define the type of server
- A profile includes and manages modules to define a logical technical stack
Those are pretty helpful, but it’s not an exhaustive list, nor does it describe what is prohibited in each type of class. While the main goal of the pattern is composition, I have my own guidelines I follow that may help others:
Roles
- No parameters
- Includes profile classes
- [Rarely] Ordering of resources that come from two separate profiles
- Contains nothing else.
Here’s an example role for an application server:
role appX {
include profile::base
include profile::apache
include profile::appX
Package<| tag == 'profile::apache' |> -> Package <| tag == 'profile::appX' |>
}
Profiles
- Optional parameters
- Includes component modules
- Includes basic resource types (built-in or from component modules)
- Calls functions, include
hiera_*()
and lookup()
- Traverses and manipulates variables to process their data
- Conditionals (limited)
- Ordering of resources, within the profile
- May call other profiles, but should be used sparingly
- If the code is >100 lines, consider separating the profile class into its own module, or finding an existing component module that include the functionality (100 is a very arbitrary number, feel free to adjust it to a number indicating when you want to start thinking about this option)
Here’s an example of a profile that calls other profiles:
class profile::base {
# Include OS specific base profiles.
case $::kernel {
'linux': {
include profile::base::linux
}
'windows': {
include profile::base::windows
}
'JUNOS': {
include profile::base::junos
}
default: {
fail ("Kernel: ${::kernel} not supported in ${module_name}")
}
}
}
Here’s an example of a more complex profile that has parameters and includes other component modules, basic resources, functions, iteration, conditionals, and even another profile:
class profile::base::linux (
$yumrepo_url,
$cron_purge = true,
$domain_join = false,
$metadata_expire = 21600, # Default value for yum is 6 hours = 21,600 seconds
$sudo_confs = {},
$manage_firewall = true, # Manage iptables
$manage_puppet_agent = true, # Manage the puppet-agent including upgrades
) {
# Manage the basics, but allow users to override some management components with flags
if $manage_firewall {
include profile::linuxfw
}
if $manage_puppet_agent {
include puppet_agent
}
include ntp
include rsyslog::client
include motd
# SSH server and client
include ssh::server
include ssh::client
# Sudo setup
include sudo
$sudo_confs.each |$group, $config| {
sudo::conf{ $group:
* => $config,
}
}
yumrepo {'local-el':
ensure => present,
descr => 'Local EL - x86_64',
baseurl => $yumrepo_url,
enabled => 1,
gpgcheck => 0,
metadata_expire => $metadata_expire,
}
Yumrepo['local-el'] -> Package<| |>
# Ensure unmanaged cron entries are removed
resources { 'cron':
purge => $cron_purge,
}
if $domain_join {
include profile::domain_join
}
}
Summary
The Roles and Profiles pattern is all about composition. The Style Guide helps you with layout and semantic choices. It doesn’t hurt to add your own rules about content and design, too. There’s no defined Best Practice here, but I hope these guidelines help you shape your own practice. Enjoy!