Generators Generators are modules are are loaded by the Bcfg2 server, based on directives in /etc/bcfg2.conf. They provide concrete, fully-specified configuration entries for clients. This chapter documents the function and usage of generators bundled with Bcfg2 releases. It also describes the interface used to communicate with generators; modeles implementing this interface can provide configuration elements for clients based on any representation or requirements that may exist.
Bundled Generators This section describes the generators that come bundled with Bcfg2. As a general rule, generators requiring more than one configuration file will use a generator specific directory in the configuration repository.
Cfg The Cfg generator provides a configuration file repository that uses literal file contents to provide client-tailored configuration file entries. The Cfg generator chooses which data to provide for a given client based on the aspect-based metadata system used for high-level client configuration. The Cfg repository is structured much like the filesystem hierarchy being configured. Each configuration file being served has a corresponding directory in the configuration repository. These directories have the same relative path as the absolute path of the configuration file on the target system. For example, if Cfg was serving data for the configuration file /etc/services, then its directory would be in the relative path ./etc/services inside of the Cfg repository. Inside of this file-specific directory, three types of files may exist. Base files are complete instances of configuration file. Deltas are differences between a base file and the target file contents. Base files and deltas are tagged with metadata specifiers, which describe which groups of clients the fragment pertains to. Configuration files are constructed by finding the most specific base file and applying any more specific deltas. Specifiers are embedded in fragment filenames. For example, in the fragment services.C99_webserver, "C99_webserver" is the specifier. This specifier applies to the class (C) webserver with a priority of 99. Other metadata categories which can be used include bundle (B), profile (P), hostname (H), attribute (A), and image (I). These are ordered from least to most specific: image, profile, class, bundle, and hostname. Global files are the least specific. Priorities are used as to break ties. Info files, named :info are used to specify target configuration file metadata, such as owner, group and permissions. If no :info is provided, targets are installed with default information. Default metadata is root ownership, root group memberships, and 0644 file permissions. Cfg generator :info files owner:root group:root perms:0755 Cfg file repository example $ ls :info passwd passwd.C99_chiba-login passwd.H_bio-debian passwd.H_cvstest passwd.H_foxtrot passwd.H_reboot passwd.H_rudy2 passwd.C99_netserv passwd.B99_tacacs-server.cat passwd.H_adenine In the previous example, there exists files with each of the characteristics mentioned above. All files ending in ".cat" are deltas; ones with ".H_" are host specific files. There exists a base file, a :info file, two class-specified base files, and a bundle-specified base file.
Pkgmgr
The Generator API The Bcfg2 core has a well-formed API used to call generators. This mechanism allows all stock generators to be runtime selected; no stock generators are required. The generator API has two main functions. The first is communication to the Bcfg2 core: the list of entries a particular generator can bind must be communicated to the core so that the proper generator can be called. The second function is the actual production of client-specific configuration element data; this data is then included in client configurations. The inventory function is provided by a python dictionary, called __provides__ in each generator object. This dictionary has a key for each type of configuration entry (ConfigFile, Package, Directory, SymLink, Service), whose value is a dictionary indexed by configuration element name. For example, the data path to information about the service "sshd" could be reached at __provides__['Service']['sshd']. The value of each of these keys is a function that can be called to bind client-specific values to a configuration entry. This function is used in the next section. The handler function located by the __provides__ dictionary is called with a static API. The function prototype for each of these handlers is: The Generator handler API def Handler(self, entry, metadata): generator logic here The data supplied upon handler invokation includes two parts. The first is the entry. This is a ElementTree.Element object, which already contains the configuration element type (ie Service) and name. All other data is bound into this object in this function. The range of data bound depends on the data type. The other data provided to handlers is client metadata, information about the current client, including hostname, image, profile, classes and bundles. The metadata is typically used to choose entry contents.
Writing a Generator Writing a generator is a fairly straightforward task. At a high level, generators are instantiated by the Bcfg2 core, and then used to provide configuration entry contents. This means that the two points where control passes into a generator from Bcfg2 are during initial object instantiation, and every time a generator-provided configuration entry is bound. Currently, generators must be written in python. They can perform arbitrary operations, hence, a generator could be written that executed logic in another language, but this functionality is currently not implemented. Simple Generator from socket import gethostbyname, gaierror from syslog import syslog, LOG_ERR from Bcfg2.Server.Generator import Generator, DirectoryBacked, SingleXMLFileBacked, GeneratorError class Chiba(Generator): '''the Chiba generator builds the following files: -> /etc/network/interfaces''' __name__ = 'Chiba' __version__ = '$Id: Chiba.py 1.12 05/01/15 11:05:02-06:00 desai@topaz.mcs.anl.gov $' __author__ = 'bcfg-dev@mcs.anl.gov' __provides__ = {'ConfigFile':{}} def __init__(self, core, datastore): Generator.__init__(self, core, datastore) self.repo = DirectoryBacked(self.data, self.core.fam) self.__provides__['ConfigFile']['/etc/network/interfaces'] = self.build_interfaces def build_interfaces(self, entry, metadata): '''build network configs for clients''' entry.attrib['owner'] = 'root' entry.attrib['group'] = 'root' entry.attrib['perms'] = '0644' try: myriaddr = gethostbyname("%s-myr" % metadata.hostname) except gaierror: syslog(LOG_ERR, "Failed to resolve %s-myr"% metadata.hostname) raise GeneratorError, ("%s-myr" % metadata.hostname, 'lookup') entry.text = self.repo.entries['interfaces-template'].data % myriaddr Generators must subclass the Bcfg2.Server.Generator.Generator class. Generator constructors must take two arguments: an instance of a Bcfg2.Core object, and a location for a datastore. __name__, __version__, __author__, and __provides__ are used to describe what the generator is and how it works. __provides__ describes a set of configuration entries that can be provided by the generator, and a set of handlers that can bind in the proper data. build_interfaces is an example of a handler. It gets client metadata and an configuration entry passed in, and binds data into entry as appropriate.