Bundler is used to describe groups of inter-dependent configuration entries, such as the combination of packages, configuration files, and service activations that comprise typical Unix daemons. Bundles are used to add groups of configuration entries to the inventory of client configurations, as opposed to describing particular versions of those entries. For example, a bundle could say that the configuration file /etc/passwd should be included in a configuration, but will not describe the particular version of /etc/passwd that a given client will receive.
Group and Client tags can be used inside of bundles to differentiate which entries particular clients will recieve; this is useful for the case where entries are named differently across systems; for example, one linux distro may have a package called openssh while another uses the name ssh. Configuration entries nested inside of Group elements only apply to clients who are a member of those groups; multiple nested groups must all apply. Also, groups may be negated; entries included in such groups will only apply to clients who are not a member of said group. The same applies to Client elements.
The following is an annotated copy of a bundle:
<Bundle name='ssh' version='2.0'>
<Path name='/etc/ssh/ssh_host_dsa_key'/>
<Path name='/etc/ssh/ssh_host_rsa_key'/>
<Path name='/etc/ssh/ssh_host_dsa_key.pub'/>
<Path name='/etc/ssh/ssh_host_rsa_key.pub'/>
<Path name='/etc/ssh/ssh_host_key'/>
<Path name='/etc/ssh/ssh_host_key.pub'/>
<Path name='/etc/ssh/sshd_config'/>
<Path name='/etc/ssh/ssh_config'/>
<Path name='/etc/ssh/ssh_known_hosts'/>
<Group name='rpm'>
<Package name='openssh'/>
<Package name='openssh-askpass'/>
<Service name='sshd'/>
<Group name='fedora' >
<Group name='fc14' negate='true'>
<Package name='openssh-clients'/>
</Group>
<Package name='openssh-server'/>
</Group>
</Group>
<Group name='deb'>
<Package name='ssh'/>
<Service name='ssh'/>
</Group>
<Client name='trust.example.com'>
<Path name='/etc/ssh/shosts.equiv'/>
</Client>
</Bundle>
In this bundle, most of the entries are common to all systems. Clients in group deb get one extra package and service, while clients in group rpm get two extra packages and an extra service. In addition, clients in group fedora and group rpm get one extra package entries, unless they are not in the fc14 group, in which case, they get an extra package. The client trust.example.com gets one extra file that is not distributed to any other clients. Notice that this file doesn’t describe which versions of these entries that clients should get, only that they should get them. (Admittedly, this example is slightly contrived, but demonstrates how group entries can be used in bundles)
Group/Hostname | Entry |
---|---|
all | /etc/ssh/ssh_host_dsa_key |
all | /etc/ssh/ssh_host_rsa_key |
all | /etc/ssh/ssh_host_dsa_key.pub |
all | /etc/ssh/ssh_host_rsa_key.pub |
all | /etc/ssh/ssh_host_key |
all | /etc/ssh/ssh_host_key.pub |
all | /etc/ssh/sshd_config |
all | /etc/ssh/ssh_config |
all | /etc/ssh/ssh_known_hosts |
rpm | Package openssh |
rpm | Package openssh-askpass |
rpm | Service sshd |
rpm and fedora | Package openssh-server |
rpm and fedora and not fc4 | Package openssh-clients |
deb | Package ssh |
deb | Service ssh |
trust.example.com | /etc/ssh/shosts.equiv |
Genshi XML templates allow you to use the Genshi templating system to dynamically generate a bundle. Genshi templates can be specified one of two ways:
.genshi and the associated namespace attribute.
bundle.
The top-level Bundle tag should look like the following:
<Bundle name="foo" xmlns:py="http://genshi.edgewall.org/">
Several variables are pre-defined inside templates:
Name | Description |
---|---|
metadata | Client metadata |
repo | The path to the Bcfg2 repository on the filesystem |
Note
<Group> and <Client> tags are allowed inside of Genshi templates as of Bcfg2 1.2. However, they do not behave the same as using a Genshi conditional, e.g.:
<py:if test="'groupname' in metadata.groups">
</py:if>
The conditional is evaluated when the template is rendered, so code inside the conditional is not executed if the conditional fails. A <Group> tag is evaluated after the template is rendered, so code inside the tag is always executed. This is an important distinction: if you have code that will fail on some groups, you must use a Genshi conditional, not a <Group> tag. The same caveats apply to <Client> tags.
See also the Genshi XML Template Reference.
To render a bundle for a given client, you can run:
bcfg2-info buildbundle <bundle name> <hostname>
This will render the template; it will not fully bind all of the entries in the bundle.
See bcfg2-info for more details.
In some cases, configuration files need to include the client’s hostname in their name. The following template produces such a config file entry.
<Bundle name='foo' xmlns:py="http://genshi.edgewall.org/">
<Path name='/etc/package-${metadata.hostname}'/>
</Bundle>
Depending on the circumstance, these configuration files can either be handled by individual entries in Cfg, or can be mapped to a single entry by using the altsrc feature.
In this example, configuration file names are built using probed results from the client. getmac is a probe that gathers client MAC addresses and returns them in a newline delimited string.
<Bundle name="networkinterfaces" xmlns:py="http://genshi.edgewall.org/">
<?python
files = metadata.Probes["getmacs"].split("\n")
?>
<Path py:for="file in files"
name="/etc/sysconfig/network/ifcfg-eth-${file}"
altsrc="/etc/ifcfg-template"/>
</Bundle>
Note
If you want a file to be only on a per-client basis, you can use an if declaration.
<Bundle name='bacula' xmlns:py="http://genshi.edgewall.org/">
<Path name="/etc/bacula/bconsole.conf"/>
<Path name="/etc/bacula/bacula-fd.conf"/>
<Path name="/etc/bacula/bacula-sd.conf"/>
<Path py:if="metadata.hostname == 'foo.bar.com'"
name="/etc/bacula/bacula-dir.conf"/>
</Bundle>
or alternately
<Bundle name='bacula' xmlns:py="http://genshi.edgewall.org/">
<Path name="/etc/bacula/bconsole.conf"/>
<Path name="/etc/bacula/bacula-fd.conf"/>
<Path name="/etc/bacula/bacula-sd.conf"/>
<py:if="metadata.hostname == 'foo.bar.com'">
<Path name="/etc/bacula/bacula-dir.conf"/>
</py:if>
</Bundle>
or yet another way
<Bundle name='bacula' xmlns:py="http://genshi.edgewall.org/">
<Path name="/etc/bacula/bconsole.conf"/>
<Path name="/etc/bacula/bacula-fd.conf"/>
<Path name="/etc/bacula/bacula-sd.conf"/>
<Client name="foo.bar.com">
<Path name="/etc/bacula/bacula-dir.conf"/>
</Client>
</Bundle>
The final form is preferred if there is no code inside the block that would fail on other clients.
While these examples are simple, the test in the if block can in fact be any python statement.