One of the most common questions I get from coworkers new to Puppet is "how can I avoid resource conflicts?".
The typical resource conflict I run across is usually caused by multiple modules attempting to manage a common folder or package dependency. (C:\temp
in particular seems to be a big hit for some reason.) If multiple modules manage the same resource, Puppet will throw a duplicate resource declaration error.
In an ideal world of roles and profiles and perfectly coded modules, resource conflicts should never occur. But, given that we don't live in an ideal world, there are a few helpful functions to avoid the issue.
Before we move on, It is important to note that the functions listed below are still impacted by evaluation order (explained in greater detail later). These functions are not magic "fix all" functions, and should be avoided if at all possible.
defined
The defined function is built into Puppet and will return true
if a given resource is defined within the catalog. The function may be used within a conditional to check if a particular resource exists (yet).
unless defined(File['/tmp/shared']) {
# do stuff ...
file { '/tmp/shared':
ensure => 'directory',
}
}
defined_with_params
This function (and every function hereafter) is provided by the puppetlabs/stdlib Forge module. As the name suggests, this function checks for a resource with specific attributes.
if defined_with_params(User['johndoe'], {'ensure' => 'present'}) {
# do stuff ...
} else {
user { 'johndoe':
ensure => absent,
}
}
Check the puppetlabs/stdlib README for more information.
ensure_resource
This function is pure gold. Given a resource type, title, and attribute hash, this function will create a resource in the catalog only if it does not already exist. If the resource does exist, but has different attributes, a duplicate resource definition error is thrown.
ensure_resource('file', '/tmp/shared', {'ensure' => 'directory'})
You can also use pass in arrays to create multiple resources with a single function call. Check out the README for more info.
ensure_resources
Essentially a souped-up version of the ensure_resource
function that allows you to pass in a hash of resources. In my opinion, this function quickly makes your code unreadable, and I tend to avoid it. However, it definitely has its place, particularly when describing resources within Hiera.
ensure_resources('cron', {'backupjob' => { 'command' => '/usr/bin/backup', 'user' => 'root', 'hour' => 0 }, 'copyjob' => { 'command' => '/usr/sbin/copystuff', 'minute' => 30 } }, { 'ensure' => 'present' })
ensure_packages
Similar to the previous function, but specifically for package
resources. I use it for dependencies when a node's package manager cannot sort it out on its own (namely Windows).
$my_pkgs = {
'libcurl3' => { 'ensure' => 'present' },
'zabbix' => { 'ensure' => '3.4.0' }
}
ensure_packages($my_pkgs)
Impact of evaluation order
How and when resources & functions are evaluated during catalog compilation may impact the result of these functions. Puppet does not always evaluate classes and resources in the order in which they are defined within a manifest. This evaluation-order independence has the potential to change the outcome of these functions and may make it seem like they are... well... broken.
Exactly how catalog compilation works gets into the nitty gritty of Puppet, and I won't pretend to completely understand how it works. All I can do is attempt to explain the issue as I understand it. (Feel free to yell at me if I get anything wrong.)
# Evaluated first
file { '/tmp/shared':
ensure => 'directory',
}
# Evaluated second
unless defined(File['/tmp/shared']) {
file { '/tmp/shared':
ensure => 'directory',
}
}
# This catalog will compile.
In the example above, the catalog will compile successfully, as the file
resource is evaluated before the ensure_resource
function. However, if the evaluation order is flipped, the catalog will fail to compile, as the ensure_resource
function already added the File['/tmp/shared']
resource to the catalog before the file
resource declaration is evaluated.
# Evaluated first
unless defined(File['/tmp/shared']) {
file { '/tmp/shared':
ensure => 'directory',
}
}
# Evaluated second
file { '/tmp/shared':
ensure => 'directory',
}
# This catalog will fail
Evaluation order can quickly become an issue when the same resources are added to the catalog in two completely different, unrelated classes. This issue can largely be avoided by using these handy functions in all classes that define the resource. That said, use some restriant to ensure these functions aren't used everywhere. If you have widespread use of these functions, chances are you aren't structuring your code base correctly.
Top comments (0)