DEV Community

Bryan Woolsey
Bryan Woolsey

Posted on • Updated on

Avoiding Resource Conflicts in Puppet

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',
  }
}
Enter fullscreen mode Exit fullscreen mode

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,
  }
}
Enter fullscreen mode Exit fullscreen mode

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'})
Enter fullscreen mode Exit fullscreen mode

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' })
Enter fullscreen mode Exit fullscreen mode

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)

Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)