8

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?

1
  • Do they really need to end up in the same file? I like to have each host in a seperate file. Something like /etc/bacula/clientdefs/*.conf. This should be easier to deal with.
    – Zoredache
    Mar 12, 2012 at 18:53

3 Answers 3

5

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 -%>
2
  • Now I feel bad because I finished writing my scripts two days ago and it's very close in format... that said, I'll let you choose whether you feel your answer or my answer is more appropriate for me to accept. Mar 30, 2012 at 19:13
  • I'd say your answer is more appropriate if the application has support for a config directory or an include by wildcard (as bacula appears to). I've used this in my environment for scripts which move files around to a set of target hosts. So the bash scripts simply do ls /path/to/flag/files|while read hostname; do ssh $hostname ..; done.
    – Kyle Smith
    Mar 30, 2012 at 19:34
4

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.

1

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.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .