Puppet exported resources for .erb file variables?
Scenario: my configuration file is defined by a .erb
file which includes the snippet below.
<% backupclients.each do |backup_files| -%>
Job {
Name = "Server"
JobDefs = "DefaultJob"
Client = <%= backup_files %>-fd
Pool = TeraMonth
Write Bootstrap = "/var/lib/bacula/<%= backup_files %>.bsr"
}
<% end -%>
The server's configuration file needs a repeated entry for each client host. If I were to create a simple array, this would work without issue. What I want to do, however, is have each host register itself and then collect the data using the <<| |>>
pragma similar to what one would do with the nagios_*
types.
The standard example for this involves exporting a type.
class ssh {
@@sshkey { $hostname: type => dsa, key => $sshdsakey }
Sshkey <<| |>>
}
However, I can't quite figure out how to write a type or reference it in a way that would allow me to read that array of values from the .erb
template. Is there a way I can use exported resources in combination with a variable loop in a .erb
file?
So, to answer your question directly, I don't believe getting a list of exported resources directly from erb is possible. This is due to the nature of exported resources. To Puppet, they're just more resources that need to be created on the host.
But, there is a way to accomplish what you're looking to do. I do it in a few places in my environment.
Here we create a directory of files, one for each host we want to flag as a "bacula_client". We use the purge
, force
, and recurse
options to remove files that are not managed by Puppet (i.e. if you want to remove a system from this "list").
class bacula::client {
@@file { "/etc/bacula_clients/$fqdn":
ensure => present,
content => "",
require => File['/etc/bacula_clients'],
tag => "bacula_client",
}
}
class bacula::server {
#
# .. include whatever else the server requires, like package {} file {} service {}
#
file { "/etc/bacula_clients":
ensure => directory,
purge => true,
recurse => true,
force => true,
}
# Populate directory of client files.
File <<| tag == "bacula_client" |>>
}
Next, we use some Ruby code in the .erb to scan this directory for files, and act on them:
<%
bacula_clients_dir = '/etc/bacula_clients'
d = Dir.open(bacula_clients_dir)
# Remove directories from the list of entries in the directory (specifically '.' and '..'):
backupclients = d.entries.delete_if { |e| File.directory? "#{bacula_clients_dir}/#{e}" }
backupclients.each do |backup_files|
-%>
Job {
Name = "Server"
JobDefs = "DefaultJob"
Client = <%= backup_files %>-fd
Pool = TeraMonth
Write Bootstrap = "/var/lib/bacula/<%= backup_files %>.bsr"
}
<% end -%>
Well, first I gave up and set my @@
on the actual file type. The upside is that this is still using the variables on the client host.
class bacula-client ($database = false) {
@@file { "${hostname}-bacula-client.conf":
mode => 600,
owner => bacula,
group => root,
path => "/etc/bacula/conf.d/${hostname}-client.conf",
content => template("bacula-dir-cliententry.erb"),
tag => 'bacula-client',
notify => Service[bacula-director]
}
...
}
This lets me use entries in the erb file such as:
<% if has_variable?("database") and database== "true" %>
...
<% end -%>
and declarations in my site.pp files such as: class { bacula-client: database => "true" }
To handle the directory itself:
class bacula-director {
file { '/etc/bacula/conf.d':
ensure => directory,
owner => bacula,
group => root,
mode => 600,
purge => true,
recurse => true
}
...
}
Purge and recurse cleans out anything not defined. When I take a host offline, puppetstoredconfigclean $hostname
will clean the facts and the next run of puppet on the director will reset the configuration approrpiately.
Finally, the Bacula director software itself allows me to do the following at the end of my bacula-dir.conf file:
@|"sh -c 'for f in /etc/bacula/conf.d/*.conf ; do echo @${f} ; done'"
So, there still doesn't appear to be a direct way to use an ERB template to on a collected set of resources, but one can collect a types. That can include Augeas types to stuff everything into one file, or a hack of collecting files into a configuration. It doesn't yet include what I was looking for on the question, though.
I've hit on a method using the PuppetDB service that works fairly well for this situation, though is a bit hackish. To use this you will need to have PuppetDB operational (which you should already have as you're using exported resources) and the PuppetDB API will need to be quierable from the puppetmaster (localhost).
Then, you'll want to export all the resources that you wish to gather into your array in a dedicated directory on the filesystem. This directory path will be used to uniquely identify the target resources.
Then, in your template, do something like this:
require 'rest_client'
require 'json'
resources=JSON.parse(RestClient.get("http://localhost:8080/v2/nodes/#{nodename}/resources", {:accept => :json}))
retVal = Array.new
resources.each do |resource|
if resource["title"] =~ /^#{pathRegex}$/
retVal.push(resource["title"])
end
end
Where nodename is the FQDN of the server, pathRegex is the search path mentioned above, formatted as a Ruby Regex, and retVal is the completed array. This leverages that the template is processed on the puppetmaster so special API credentials are not required. This also assumes the resource namevar is the fully qualified path of the target files, if you have complex namevars and use the path attribute more complex logic will be required. Also note this is returning all resources, both exported and local. The returned data has many attributes that can be used for more complex logic if needed.
A bit hacky, but it works well.