A collection of task oriented solutions in Puppet

 

Manage a package in multiple classes

Challenge

You want to manage a package from multiple places.

Solution

# Assumes you've already installed the puppetlabs-stdlib module

class strace {
  notify { 'In strace': }
  ensure_packages(['strace'], { ensure => 'present' })
}

class debug_tools {
  # install lots of debug tools, and then strace
  notify { 'In debug_tools': }
  ensure_packages(['strace'], { ensure => 'present' })
}

include strace
include debug_tools
Notice: /Stage[main]/Strace/Notify[In strace]/message: defined 'message' as 'In strace'
Notice: /Stage[main]/Strace/Package[strace]/ensure: created
Notice: /Stage[main]/Debug_tools/Notify[In debug_tools]/message: defined 'message' as 'In debug_tools'
Notice: Applied catalog in 19.46 seconds

Explanation

By default Puppet only allows you to manage a resource once. When you declare the same resource in additional locations an evaluation error will be raised.

class debug_tools {
  # lots of other debug tools and then...
  package { 'strace': ensure => 'present' }
}

class strace {
  package { 'strace': ensure => 'present' }
}

include debug_tools
include strace
Error: Evaluation Error: Error while evaluating a Resource Statement,
  Duplicate declaration: Package[strace] is already declared in file
  /mypath/multi-fail.pp:3; cannot redeclare at /mypath.pp:9 at
  /mypath/multi-fail.pp:9:3 on node puppet134

There are a few ways to work around this, and while historically virtual resources were used for even simple cases, such as managing a package in more than one place, the ensure_packages function provided by the Puppet stdlib module is an excellent, easier, alternative.

$ sudo /opt/puppetlabs/bin/puppet module install puppetlabs-stdlib

Notice: Preparing to install into /etc/puppetlabs/code/environments/production/site...
/etc/puppetlabs/code/environments/production/site
  puppetlabs-stdlib (v4.13.1)
# Assumes you've already installed the puppetlabs-stdlib module

class strace {
  notify { 'In strace': }
  ensure_packages(['strace'], { ensure => 'present' })
}

class debug_tools {
  # install lots of debug tools, and then strace
  notify { 'In debug_tools': }
  ensure_packages(['strace'], { ensure => 'present' })
}

include strace
include debug_tools
Notice: /Stage[main]/Strace/Notify[In strace]/message: defined 'message' as 'In strace'
Notice: /Stage[main]/Strace/Package[strace]/ensure: created
Notice: /Stage[main]/Debug_tools/Notify[In debug_tools]/message: defined 'message' as 'In debug_tools'
Notice: Applied catalog in 19.46 seconds

Adopting this approach does require all the modules and classes you use to cooperate in their declarations. Mixing package resources and an ensure_packages for example will still raise errors:

class strace {
  notify { 'In strace': }
  ensure_packages(['strace'], { ensure => 'present' })
}

class debug_tools {
  notify { 'In debug_tools': }
  package { 'strace': ensure => 'present' }
}

include strace
include debug_tools
Error: Evaluation Error: Error while evaluating a Resource Statement,
Duplicate declaration: Package[strace] is already declared in file
/mypath/multi-pass.pp:4; cannot redeclare at /mypath/multi-pass.pp:12
at /mypath/multi-pass.pp:12:3 on node puppet165

One slightly annoying edge case to be aware of is the value of the ensure property. Although Puppet allows you to use a number of values, most often 'installed' and 'present' ensure_packages is a little simpler and will consider these to be completely different parameters and cause the catalog to fail in the same ways as above.

# This example will fail as the ensure property is set differently
class strace {
  notify { 'In strace': }
  ensure_packages(['strace'], { ensure => 'present' })
}

class debug_tools {
  notify { 'In debug_tools': }
  ensure_packages(['strace'], { ensure => 'installed' })
}

include strace
include debug_tools

See also