When managing hundreds of servers with Puppet, we often face situations where each node requires a unique set of configuration files in a directory. The traditional approach of defining each file resource individually becomes impractical at scale. Let's explore smarter solutions.
The most straightforward approach uses Puppet's recurse parameter:
file { "/etc/someprogram/config":
ensure => directory,
recurse => remote,
source => "puppet:///modules/someprogram/files/${fqdn}",
purge => true,
owner => "root",
group => "root",
mode => "0764"
}
This works well when you want to mirror an entire directory, but lacks granular control over individual files.
For more control, we can use Puppet's DSL capabilities to generate file resources dynamically:
$config_files = file("someprogram/${fqdn}/filelist", "/dev/null").split("\n")
$config_files.each |$filename| {
file { "/etc/someprogram/config/${filename}":
ensure => file,
source => "puppet:///modules/someprogram/files/${fqdn}/${filename}",
owner => "root",
group => "root",
mode => "0764"
}
}
For complex environments, consider using Hiera or external node classifiers:
$custom_files = lookup('someprogram::custom_files', Array[String], 'unique', [])
$custom_files.each |$file| {
file { "/etc/someprogram/config/${file}":
ensure => file,
source => "puppet:///modules/someprogram/files/${fqdn}/${file}",
owner => "root",
group => "root",
mode => "0764"
}
}
When dealing with hundreds of files:
- Use
filebucketfor backups - Consider implementing a custom function to batch process files
- Evaluate using a module like
puppet-archivefor large deployments
Combine ERB templates with dynamic file management:
$template_files = file("someprogram/${fqdn}/templates", "/dev/null").split("\n")
$template_files.each |$tfile| {
file { "/etc/someprogram/config/${tfile}":
ensure => file,
content => template("someprogram/${fqdn}/${tfile}.erb"),
owner => "root",
group => "root",
mode => "0764"
}
}
Managing hundreds of one-off servers with unique configuration files presents a significant Puppet automation challenge. The traditional approach of declaring each file resource individually becomes unmaintainable at scale.
While Puppet's file resource is excellent for static file management, it lacks built-in directory enumeration capabilities. This creates friction when you need to manage dynamic sets of files per node.
We can leverage Puppet's file function to read directory contents and iterate through them:
class someprogram::config {
$config_dir = "/etc/puppet/modules/someprogram/files/${facts['fqdn']}"
$config_files = filesystem::list($config_dir)
$config_files.each |$file| {
file { "/etc/someprogram/config/${file}":
ensure => file,
owner => 'root',
group => 'root',
mode => '0764',
source => "puppet:///modules/someprogram/files/${facts['fqdn']}/${file}",
}
}
}
For more complex scenarios, consider using Hiera or external node classifiers:
$custom_files = lookup('someprogram::custom_files', Hash, 'deep', {})
$custom_files.each |$file, $attrs| {
file { "/etc/someprogram/config/${file}":
* => $attrs,
source => "puppet:///modules/someprogram/files/${facts['fqdn']}/${file}",
}
}
When dealing with hundreds of files:
- Use
recurse => remotefor directory synchronization - Consider file buckets for large binary files
- Implement a custom fact to detect needed files
Add validation to handle missing source directories:
if empty($config_files) {
notify { "No custom config files found for ${facts['fqdn']}": }
}