Bcfg2 uses an option parsing mechanism based on the Python argparse module. It does several very useful things that argparse does not:
One of the more important features of the option parser is its ability to automatically collect options from loaded components (e.g., Bcfg2 server plugins). Given the highly pluggable architecture of Bcfg2, this helps ensure two things:
For instance, assume a few plugins:
The plugins are used by the bcfg2-quux command, which itself takes two options: --plugins (which selects the plugins) and --test. The options would be selected at runtime, so for instance these would be valid:
bcfg2-quux --plugins Foo --foo --test
bcfg2-quux --plugins Foo,Bar --foo --bar --force
bcfg2-quux --plugins Bar --force
But this would not:
bcfg2-quux –plugins Foo –bar
The help message would reflect the options that are available to the default set of plugins. (For this reason, allowing component lists to be set in the config file is very useful; that way, usage messages reflect the components in the config file.)
Components (in this example, the plugins) can be classes or modules. There is no required interface for an option component. They may optionally have:
Options are collected through two primary mechanisms:
Since it is preferred to add components instead of just options, loading options at runtime is generally best accomplished by creating a container object whose only purpose is to hold options. For instance:
def foo():
# do stuff
class _OptionContainer(object):
options = [
Bcfg2.Options.BooleanOption("--foo", help="Enable foo")]
@staticmethod
def options_parsed_hook():
if Bcfg2.Options.setup.foo:
foo()
Bcfg2.Options.get_parser().add_component(_OptionContainer)
The base Bcfg2.Options.Option object represents an option. Unlike options in argparse, an Option object does not need to be associated with an option parser; it exists on its own.
Options can be grouped in various meaningful ways. This uses a variety of argparse functionality behind the scenes.
In all cases, options can be added to groups in-line by simply specifying them in the object group constructor:
options = [
Bcfg2.Options.ExclusiveOptionGroup(
Bcfg2.Options.Option(...),
Bcfg2.Options.Option(...),
required=True),
....]
Nesting object groups is supported in theory, but barely tested.
This library makes it easier to work with programs that have a large number of subcommands (e.g., bcfg2-info and bcfg2-admin).
The normal implementation pattern is this:
Bcfg2.Server.Admin provides a fairly simple implementation, where the CLI class is itself the command registry:
class CLI(Bcfg2.Options.CommandRegistry):
def __init__(self):
Bcfg2.Options.CommandRegistry.__init__(self)
Bcfg2.Options.register_commands(self.__class__,
globals().values(),
parent=AdminCmd)
parser = Bcfg2.Options.get_parser(
description="Manage a running Bcfg2 server",
components=[self])
parser.parse()
In this case, commands are collected from amongst all global variables (the most likely scenario), and they must be children of Bcfg2.Server.Admin.AdminCmd, which itself subclasses Bcfg2.Options.Subcommand.
Commands are defined by subclassing Bcfg2.Options.Subcommand. At a minimum, the Bcfg2.Options.Subcommand.run() method must be overridden, and a docstring written.
Several custom argparse actions provide some of the option collection magic of Bcfg2.Options.
Bcfg2.Options provides a number of useful types for use as the type keyword argument to the Bcfg2.Options.Option constructor.