summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Options
diff options
context:
space:
mode:
authorChris St. Pierre <chris.a.st.pierre@gmail.com>2014-10-22 11:03:48 -0500
committerChris St. Pierre <chris.a.st.pierre@gmail.com>2014-11-10 17:35:43 -0600
commit4ec92cb9e7d1eb2f90d36e5255ee8814ca0a8513 (patch)
treef375d640c4f4e004600a1c3f7316a7d167e2f328 /src/lib/Bcfg2/Options
parent72201ddac165e45da09521b77660b2e155ca36cd (diff)
downloadbcfg2-4ec92cb9e7d1eb2f90d36e5255ee8814ca0a8513.tar.gz
bcfg2-4ec92cb9e7d1eb2f90d36e5255ee8814ca0a8513.tar.bz2
bcfg2-4ec92cb9e7d1eb2f90d36e5255ee8814ca0a8513.zip
Options: ensure <repository> macros are always fixed up
This fixes several cases in which <repository> macros would not be properly processed: options that are not added to the parser yet when early options are parsed; and config file options whose default value is used.
Diffstat (limited to 'src/lib/Bcfg2/Options')
-rw-r--r--src/lib/Bcfg2/Options/Options.py59
-rw-r--r--src/lib/Bcfg2/Options/Parser.py77
2 files changed, 96 insertions, 40 deletions
diff --git a/src/lib/Bcfg2/Options/Options.py b/src/lib/Bcfg2/Options/Options.py
index bd1a72fc7..36148c279 100644
--- a/src/lib/Bcfg2/Options/Options.py
+++ b/src/lib/Bcfg2/Options/Options.py
@@ -17,7 +17,7 @@ from Bcfg2.Compat import ConfigParser
__all__ = ["Option", "BooleanOption", "PathOption", "PositionalArgument",
"_debug"]
-unit_test = False
+unit_test = False # pylint: disable=C0103
def _debug(msg):
@@ -46,7 +46,7 @@ def _get_action_class(action_name):
on. So we just instantiate a dummy parser, add a dummy argument,
and determine the class that way. """
if (isinstance(action_name, type) and
- issubclass(action_name, argparse.Action)):
+ issubclass(action_name, argparse.Action)):
return action_name
if action_name not in _action_map:
action = argparse.ArgumentParser().add_argument(action_name,
@@ -159,9 +159,10 @@ class Option(object):
sources.append("%s.%s" % self.cf)
if self.env:
sources.append("$" + self.env)
- spec = ["sources=%s" % sources, "default=%s" % self.default]
- spec.append("%d parsers" % (len(self.parsers)))
- return 'Option(%s: %s)' % (self.dest, ", ".join(spec))
+ spec = ["sources=%s" % sources, "default=%s" % self.default,
+ "%d parsers" % len(self.parsers)]
+ return '%s(%s: %s)' % (self.__class__.__name__,
+ self.dest, ", ".join(spec))
def list_options(self):
""" List options contained in this option. This exists to
@@ -228,7 +229,7 @@ class Option(object):
rv = self._type_func(self.get_config_value(cfp))
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
rv = None
- _debug("Setting %s from config file(s): %s" % (self, rv))
+ _debug("Getting value of %s from config file(s): %s" % (self, rv))
return rv
def get_config_value(self, cfp):
@@ -326,12 +327,11 @@ class Option(object):
class PathOption(Option):
- """ Shortcut for options that expect a path argument. Uses
- :meth:`Bcfg2.Options.Types.path` to transform the argument into a
- canonical path.
+ """Shortcut for options that expect a path argument.
- The type of a path option can also be overridden to return a
- file-like object. For example:
+ Uses :meth:`Bcfg2.Options.Types.path` to transform the argument
+ into a canonical path. The type of a path option can also be
+ overridden to return a file-like object. For example:
.. code-block:: python
@@ -339,25 +339,50 @@ class PathOption(Option):
Bcfg2.Options.PathOption(
"--input", type=argparse.FileType('r'),
help="The input file")]
+
+ PathOptions also do translation of ``<repository>`` macros on the
+ fly. It's done this way instead of just fixing up all values at
+ the end of parsing because macro expansion needs to be done before
+ path canonicalization and other stuff.
"""
+ repository = None
def __init__(self, *args, **kwargs):
self._original_type = kwargs.pop('type', lambda x: x)
kwargs['type'] = self._type
kwargs.setdefault('metavar', '<path>')
Option.__init__(self, *args, **kwargs)
- self.repository = None
def early_parsing_hook(self, early_opts):
- self.repository = early_opts.repository
+ if hasattr(early_opts, "repository"):
+ if self.__class__.repository is None:
+ _debug("Setting repository to %s for PathOptions" %
+ early_opts.repository)
+ self.__class__.repository = early_opts.repository
+ else:
+ _debug("Repository is already set for %s" % self.__class__)
+
+ def _get_default(self):
+ """ Getter for the ``default`` property """
+ if self.__class__.repository is None or self._default is None:
+ return self._default
+ else:
+ return self._default.replace("<repository>",
+ self.__class__.repository)
+
+ default = property(_get_default, Option._set_default)
def _type(self, value):
"""Type function that fixes up <repository> macros."""
- if self.repository is None:
- rv = value
+ if self.__class__.repository is None:
+ _debug("Cannot fix up <repository> macros yet for %s" % self)
+ return value
else:
- rv = value.replace("<repository>", self.repository)
- return self._original_type(Types.path(rv))
+ rv = self._original_type(Types.path(
+ value.replace("<repository>", self.__class__.repository)))
+ _debug("Fixing up <repository> macros in %s: %s -> %s" %
+ (self, value, rv))
+ return rv
class _BooleanOptionAction(argparse.Action):
diff --git a/src/lib/Bcfg2/Options/Parser.py b/src/lib/Bcfg2/Options/Parser.py
index ec470f8d3..6715d90f9 100644
--- a/src/lib/Bcfg2/Options/Parser.py
+++ b/src/lib/Bcfg2/Options/Parser.py
@@ -168,6 +168,8 @@ class Parser(argparse.ArgumentParser):
(opt, value))
action(self, self.namespace, value)
else:
+ _debug("Setting config file-only option %s to %s" %
+ (opt, value))
setattr(self.namespace, opt.dest, value)
def _finalize(self):
@@ -191,6 +193,53 @@ class Parser(argparse.ArgumentParser):
_debug("Deleting %s" % attr)
delattr(self.namespace, attr)
+ def _parse_early_options(self):
+ """Parse early options.
+
+ Early options are options that need to be parsed before other
+ options for some reason. These fall into two basic cases:
+
+ 1. Database options, which need to be parsed so that Django
+ modules can be imported, since Django configuration is all
+ done at import-time;
+ 2. The repository (``-Q``) option, so that ``<repository>``
+ macros in other options can be resolved.
+ """
+ _debug("Option parsing phase 2: Parse early options")
+ early_opts = argparse.Namespace()
+ early_parser = Parser(add_help=False, namespace=early_opts,
+ early=True)
+
+ # add the repo option so we can resolve <repository>
+ # macros
+ early_parser.add_options([repository])
+
+ early_components = []
+ for component in self.components:
+ if getattr(component, "parse_first", False):
+ early_components.append(component)
+ early_parser.add_component(component)
+ early_parser.parse(self.argv)
+
+ _debug("Fixing up <repository> macros in early options")
+ for attr_name in dir(early_opts):
+ if not attr_name.startswith("_"):
+ attr = getattr(early_opts, attr_name)
+ if hasattr(attr, "replace"):
+ setattr(early_opts, attr_name,
+ attr.replace("<repository>",
+ early_opts.repository))
+
+ _debug("Early parsing complete, calling hooks")
+ for component in early_components:
+ if hasattr(component, "component_parsed_hook"):
+ _debug("Calling component_parsed_hook on %s" % component)
+ getattr(component, "component_parsed_hook")(early_opts)
+ _debug("Calling early parsing hooks; early options: %s" %
+ early_opts)
+ for option in self.option_list:
+ option.early_parsing_hook(early_opts)
+
def add_config_file(self, dest, cfile, reparse=True):
""" Add a config file, which triggers a full reparse of all
options. """
@@ -207,10 +256,11 @@ class Parser(argparse.ArgumentParser):
def reparse(self, argv=None):
""" Reparse options after they have already been parsed.
- :param argv: The argument list to parse. By default,
+ :param argv: The argument list to parse. By default,
:attr:`Bcfg2.Options.Parser.argv` is reused.
(I.e., the argument list that was initially
- parsed.) :type argv: list
+ parsed.)
+ :type argv: list
"""
_debug("Reparsing all options")
self._reset_namespace()
@@ -249,26 +299,7 @@ class Parser(argparse.ArgumentParser):
# phase 2: re-parse command line for early options; currently,
# that's database options
if not self._early:
- _debug("Option parsing phase 2: Parse early options")
- early_opts = argparse.Namespace()
- early_parser = Parser(add_help=False, namespace=early_opts,
- early=True)
- # add the repo option so we can resolve <repository>
- # macros
- early_parser.add_options([repository])
- early_components = []
- for component in self.components:
- if getattr(component, "parse_first", False):
- early_components.append(component)
- early_parser.add_component(component)
- early_parser.parse(self.argv)
- _debug("Early parsing complete, calling hooks")
- for component in early_components:
- if hasattr(component, "component_parsed_hook"):
- _debug("Calling component_parsed_hook on %s" % component)
- getattr(component, "component_parsed_hook")(early_opts)
- for option in self.option_list:
- option.early_parsing_hook(early_opts)
+ self._parse_early_options()
else:
_debug("Skipping parsing phase 2 in early mode")
@@ -299,13 +330,13 @@ class Parser(argparse.ArgumentParser):
remaining = []
while not self.parsed:
self.parsed = True
- self._set_defaults_from_config()
_debug("Parsing known arguments")
try:
_, remaining = self.parse_known_args(args=self.argv,
namespace=self.namespace)
except OptionParserException:
self.error(sys.exc_info()[1])
+ self._set_defaults_from_config()
self._parse_config_options()
self._finalize()
if len(remaining) and not self._early: