Viper - Adding configuration values

The primary host configuration location is a host's LDAP entry, always found at cn=HOSTNAME,ou=hosts,o=DOM.AIN,ou=clients.

The host entry itself contains only a small number of most important, host-specific attributes (hostname, macAddress, ipHostNumber) and the list of classes the host belongs to.

Some other information is also added to the entry by various dynamic settings, but that is only used to include useful host properties (such as name of the client it belongs to) and not real configuration data.
(In the current default setup, this other information includes DHCP parameters, but that data is not important when considering hosts in the context of ou=clients LDAP suffix, and might be removed in the future).

The real configuration data is stored in a one-level subtree under the host entry. All values (Debian or custom) are saved as Debconf keys, following the naming pattern of cn=KEY,cn=HOST,ou=hosts,o=DOM.AIN,ou=clients.

When the configuration value is asked for, and is not found in the host's tree, Viper looks for a fallback value in site-wide and global defaults tree.

Note that the default value can be dynamically-produced (just like any other value), and can evaluate to different data in different contexts.

For example, you might want to store the location of the Web server's DocumentRoot in Viper, and you might want it to be /var/www/CLIENT_NAME. That can be achieved with a single default value that expands to the corresponding CLIENT_NAME every time it is read.

Existing configuration data and defaults

The default Viper setup contains a set of default Debconf keys that suffice to perform an automated install using preseeding.

All those keys are defined on the global level (ou=defaults tree), where they are queried if not found at any of the higher priority levels (site level at ou=defaults,o=DOM.AIN,ou=clients, or host level at cn=HOST,ou=hosts,o=DOM.AIN,ou=clients).

So you should go over the defaults in ldifs/0-defaults.ldif and verify the values (search for occurrences of "value:"). You can modify the defaults, or override them on site- or host-level by copying over to your client's LDIF, adjusting the DN and changing the value.

After every change in ldifs/*.ldif files, run make in that directory to reload new data. ('make' will delete all Viper data and re-load it from LDIF files. This will cause data loss if you were editing anything in LDAP directly, avoiding LDIF files).

Locations for adding configuration values

The three overarching ideas of the whole Viper system are to first, avoid all repetition when data can be derived from already existing information, second, keep all configuration in one place, and third, have no distinction between Debconf values and our own configuration values.

As as result, we use Debconf format for all configuration, and we save each config value to some of the three levels:

  • Host-specific level (i.e. cn=KEY,cn=HOST,ou=hosts,o=DOM.AIN,ou=clients)
  • Site-wide level (i.e. cn=KEY,ou=hosts,ou=defaults,o=DOM.AIN,ou=clients)
  • Global level (i.e. cn=KEY,ou=hosts,ou=defaults)
(Note that each Debconf question consists of a question and template. Templates are saved to basically the same places as shown above, just replacing ou=hosts with ou=templates.)

Accessing configuration values

Debconf will not mind the added questions at all, but it will not query them as it doesn't know about them, of course.

There are a few ways you can take advantage of the configuration values you add. There are general and Viper-specific approaches. The general approaches are of little interest to us, but we list them here for completeness. The real interest lies in Viper-specific methods.

General methods:

  • Modify Debconf script for a package to query new Debconf values
  • Read values from your scripts
Viper-specific methods:
  • Make the value used during constructing of some other value (i.e. define key called "client/name", then configure webserver root directory to be /var/www/client_name)
  • Flag value with "flags: puppet" to have it automatically appear as Puppet variable, which you can then automatically use in Puppet manifests, modules and ERB templates (i.e. Debconf key ntp/servers, flagged with 'flags: preseed' would appear as variable $ntp_servers in manifests and ntp_servers in ERB templates).

Specific example

Here's an example of how to add a Debconf key ntp/servers. Its value will always expand to the list of NTP servers in the client's domain, and we'll use it in constructing the host's ntp.conf file.

Debconf template and question for ntp/servers:

dn: cn=ntp/servers,ou=templates,ou=defaults
objectClass: top
objectClass: debConfDbEntry
cn: ntp/servers
description: List of NTP servers within a domain
type: string

dn: cn=ntp/servers,ou=hosts,ou=defaults
objectClass: top
objectClass: debConfDbEntry
cn: ntp/servers
owners: viper
flags: puppet
template: ntp/servers
value: find $ ... 2 0 500 3600 \
  (&(objectClass=puppetClient)(puppetClass=ntp::server*)) \
  0 ipHostNumber \0 \0

Excerpt from ERB template for Puppet module 'ntp':
<% ntp_servers.each do |s| -%>
server <%= s %>
<% end -%>

Final, expanded value in ntp.conf template may look like this:
server 10.0.1.8
server 10.0.1.9
server 10.0.1.10

(If you're familiar with Puppet configuration management system, you might notice that this type of dynamic values using Viper eliminates the need to use Puppet's exported resources and stored configs.)