1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
|
.. -*- mode: rst -*-
.. _development-option-parsing:
====================
Bcfg2 Option Parsing
====================
Bcfg2 uses an option parsing mechanism based on the Python
:mod:`argparse` module. It does several very useful things that
``argparse`` does not:
* Collects options from various places, which lets us easily specify
per-plugin options, for example;
* Automatically loads components (such as plugins);
* Synthesizes option values from the command line, config files, and
environment variables;
* Can dynamically create commands with many subcommands (e.g.,
bcfg2-info and bcfg2-admin); and
* Supports keeping documentation inline with the option declaration,
which will make it easier to generate man pages.
Collecting Options
==================
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:
#. We do not have to specify all options in all places, or even in
most places. Options are specified alongside the class(es) that use
them.
#. All options needed for a given script to run are guaranteed to be
loaded, without the need to specify all components that script uses
manually.
For instance, assume a few plugins:
* The ``Foo`` plugin takes one option, ``--foo``
* The ``Bar`` plugin takes two options, ``--bar`` and ``--force``
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:
.. code-block:: bash
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:
* An ``options`` attribute that is a list of
:class:`Bcfg2.Options.Options.Option` objects or option groups.
* A function or static method, ``options_parsed_hook``, that is called
when all options have been parsed. (This will be called again if
:func:`Bcfg2.Options.Parser.Parser.reparse` is called.)
Options are collected through two primary mechanisms:
#. The :class:`Bcfg2.Options.Actions.ComponentAction` class. When a
ComponentAction subclass is used as the action of an option, then
options contained in the classes (or modules) given in the option
value will be added to the parser.
#. Modules that are not loaded via a
:class:`Bcfg2.Options.Actions.ComponentAction` option may load
options at runtime.
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:
.. code-block:: python
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 Bcfg2.Options module
========================
.. currentmodule:: Bcfg2.Options
.. autodata:: setup
Options
-------
The base :class:`Bcfg2.Options.Option` object represents an option.
Unlike options in :mod:`argparse`, an Option object does not need to
be associated with an option parser; it exists on its own.
.. autoclass:: Option
.. autoclass:: PathOption
.. autoclass:: BooleanOption
.. autoclass:: PositionalArgument
The Parser
----------
.. autoclass:: Parser
.. autofunction:: get_parser
.. autoexception:: OptionParserException
Option Groups
-------------
Options can be grouped in various meaningful ways. This uses a
variety of :mod:`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:
.. code-block:: python
options = [
Bcfg2.Options.ExclusiveOptionGroup(
Bcfg2.Options.Option(...),
Bcfg2.Options.Option(...),
required=True),
....]
Nesting object groups is supported in theory, but barely tested.
.. autoclass:: OptionGroup
.. autoclass:: ExclusiveOptionGroup
.. autoclass:: Subparser
.. autoclass:: WildcardSectionGroup
Subcommands
-----------
This library makes it easier to work with programs that have a large
number of subcommands (e.g., :ref:`bcfg2-info <server-bcfg2-info>` and
:ref:`bcfg2-admin <server-admin-index>`).
The normal implementation pattern is this:
#. Define all of your subcommands as children of
:class:`Bcfg2.Options.Subcommand`.
#. Define a :class:`Bcfg2.Options.CommandRegistry` object that will be
used to register all of the commands. Registering a command
collect its options and adds it as a
:class:`Bcfg2.Options.Subparser` option group to the main option
parser.
#. Register your commands with
:func:`Bcfg2.Options.register_commands`, parse options, and run.
:mod:`Bcfg2.Server.Admin` provides a fairly simple implementation,
where the CLI class is itself the command registry:
.. code-block:: python
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
:class:`Bcfg2.Server.Admin.AdminCmd`, which itself subclasses
:class:`Bcfg2.Options.Subcommand`.
Commands are defined by subclassing :class:`Bcfg2.Options.Subcommand`.
At a minimum, the :func:`Bcfg2.Options.Subcommand.run` method must be
overridden, and a docstring written.
.. autoclass:: Subcommand
.. autoclass:: HelpCommand
.. autoclass:: CommandRegistry
.. autofunction:: register_commands
Actions
-------
Several custom argparse `actions
<http://docs.python.org/dev/library/argparse.html#action>`_ provide
some of the option collection magic of :mod:`Bcfg2.Options`.
.. autoclass:: ConfigFileAction
.. autoclass:: ComponentAction
.. autoclass:: PluginsAction
Option Types
------------
:mod:`Bcfg2.Options` provides a number of useful types for use as the `type
<http://docs.python.org/dev/library/argparse.html#type>`_ keyword
argument to
the :class:`Bcfg2.Options.Option` constructor.
.. autofunction:: Bcfg2.Options.Types.path
.. autofunction:: Bcfg2.Options.Types.comma_list
.. autofunction:: Bcfg2.Options.Types.colon_list
.. autofunction:: Bcfg2.Options.Types.octal
.. autofunction:: Bcfg2.Options.Types.username
.. autofunction:: Bcfg2.Options.Types.groupname
.. autofunction:: Bcfg2.Options.Types.timeout
.. autofunction:: Bcfg2.Options.Types.size
Common Options
--------------
.. autoclass:: Common
|