Dynamic File Management in Puppet: Deploying Multiple Config Files Without Explicit Definitions


2 views

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 filebucket for backups
  • Consider implementing a custom function to batch process files
  • Evaluate using a module like puppet-archive for 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 => remote for 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']}": }
}