summaryrefslogtreecommitdiff
path: root/oslo/config/generator.py
diff options
context:
space:
mode:
Diffstat (limited to 'oslo/config/generator.py')
-rw-r--r--oslo/config/generator.py340
1 files changed, 340 insertions, 0 deletions
diff --git a/oslo/config/generator.py b/oslo/config/generator.py
new file mode 100644
index 0000000..9962030
--- /dev/null
+++ b/oslo/config/generator.py
@@ -0,0 +1,340 @@
+# Copyright 2012 SINA Corporation
+# Copyright 2014 Cisco Systems, Inc.
+# All Rights Reserved.
+# Copyright 2014 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+r"""
+A sample configuration file generator.
+
+oslo-config-generator is a utility for generating sample config files. For
+example, to generate a sample config file for oslo.messaging you would run::
+
+ $> oslo-config-generator --namespace oslo.messaging > oslo.messaging.conf
+
+This generated sample lists all of the available options, along with their help
+string, type, deprecated aliases and defaults.
+
+The --namespace option specifies an entry point name registered under the
+'oslo.config.opts' entry point namespace. For example, in oslo.messaging's
+setup.cfg we have::
+
+ [entry_points]
+ oslo.config.opts =
+ oslo.messaging = oslo.messaging.opts:list_opts
+
+The callable referenced by the entry point should take no arguments and return
+a list of (group_name, [opt_1, opt_2]) tuples. For example::
+
+ opts = [
+ cfg.StrOpt('foo'),
+ cfg.StrOpt('bar'),
+ ]
+
+ cfg.CONF.register_opts(opts, group='blaa')
+
+ def list_opts():
+ return [('blaa', opts)]
+
+You might choose to return a copy of the options so that the return value can't
+be modified for nefarious purposes::
+
+ def list_opts():
+ return [('blaa', copy.deepcopy(opts))]
+
+A single codebase might have multiple programs, each of which use a subset of
+the total set of options registered by the codebase. In that case, you can
+register multiple entry points::
+
+ [entry_points]
+ oslo.config.opts =
+ nova.common = nova.config:list_common_opts
+ nova.api = nova.config:list_api_opts
+ nova.compute = nova.config:list_compute_opts
+
+and generate a config file specific to each program::
+
+ $> oslo-config-generator --namespace oslo.messaging \
+ --namespace nova.common \
+ --namespace nova.api > nova-api.conf
+ $> oslo-config-generator --namespace oslo.messaging \
+ --namespace nova.common \
+ --namespace nova.compute > nova-compute.conf
+
+To make this more convenient, you can use config files to describe your config
+files::
+
+ $> cat > config-generator/api.conf <<EOF
+ [DEFAULT]
+ output_file = etc/nova/nova-api.conf
+ namespace = oslo.messaging
+ namespace = nova.common
+ namespace = nova.api
+ EOF
+ $> cat > config-generator/compute.conf <<EOF
+ [DEFAULT]
+ output_file = etc/nova/nova-compute.conf
+ namespace = oslo.messaging
+ namespace = nova.compute
+ namespace = nova.compute
+ EOF
+ $> oslo-config-generator --config-file config-generator/api.conf
+ $> oslo-config-generator --config-file config-generator/compute.conf
+
+The default runtime values of configuration options are not always the most
+suitable values to include in sample config files - for example, rather than
+including the IP address or hostname of the machine where the config file
+was generated, you might want to include something like '10.0.0.1'. To
+facilitate this, applications can supply their own 'sanitizer' function via
+the 'oslo.config.sanitizer' entry point namespace. For example::
+
+ def sanitize_default(self, opt, default_str):
+ if opt.dest == 'base_dir' and default_str == os.getcwd():
+ return '.'
+ else:
+ return default_str
+
+the callable is registered as a entry point::
+
+ [entry_points]
+ oslo.config.opts =
+ myapp = myapp.opts:list_opts
+
+ oslo.config.sanitizer
+ myapp = myapp.opts:sanitize_default
+
+before being passed via the --sanitizer command line option:
+
+ $> oslo-config-generator --namespace myapp \
+ --sanitizer myapp > myapp.conf
+"""
+
+import logging
+import operator
+import sys
+import textwrap
+
+from oslo.config import cfg
+import stevedore.named
+
+LOG = logging.getLogger(__name__)
+
+_generator_opts = [
+ cfg.StrOpt('output-file',
+ help='Path of the file to write to. Defaults to stdout.'),
+ cfg.IntOpt('wrap-width',
+ default=70,
+ help='The maximum length of help lines.'),
+ cfg.MultiStrOpt('namespace',
+ help='Option namespace under "oslo.config.opts" in which '
+ 'to query for options.'),
+ cfg.StrOpt('sanitizer',
+ help='An entry point name under "oslo.config.sanitizer" for a '
+ 'sanitize_default(opt, default_str) callable which will '
+ 'sanitize the stringified default value of an option '
+ 'before outputting it.'),
+]
+
+
+def register_cli_opts(conf):
+ """Register the formatter's CLI options with a ConfigOpts instance.
+
+ Note, this must be done before the ConfigOpts instance is called to parse
+ the configuration.
+
+ :param conf: a ConfigOpts instance
+ :raises: DuplicateOptError, ArgsAlreadyParsedError
+ """
+ conf.register_cli_opts(_generator_opts)
+
+
+class _OptFormatter(object):
+
+ """Format configuration option descriptions to a file."""
+
+ _TYPE_DESCRIPTIONS = {
+ cfg.StrOpt: 'string value',
+ cfg.BoolOpt: 'boolean value',
+ cfg.IntOpt: 'integer value',
+ cfg.FloatOpt: 'floating point value',
+ cfg.ListOpt: 'list value',
+ cfg.DictOpt: 'dict value',
+ cfg.MultiStrOpt: 'multi valued',
+ }
+
+ def __init__(self, output_file=None, sanitize_default=None, wrap_width=70):
+ """Construct an OptFormatter object.
+
+ :param output_file: a writeable file object
+ :param sanitize_default: a sanitize_default(opt, default_str) callable
+ :param wrap_width: The maximum length of help lines, 0 to not wrap
+ """
+ self.output_file = output_file or sys.stdout
+ self.sanitize_default = sanitize_default or (lambda o, d: d)
+ self.wrap_width = wrap_width
+
+ def format(self, opt):
+ """Format a description of an option to the output file.
+
+ :param opt: a cfg.Opt instance
+ """
+ if not opt.help:
+ LOG.warning('"%s" is missing a help string', opt.dest)
+
+ opt_type = self._TYPE_DESCRIPTIONS.get(type(opt), 'unknown type')
+
+ help_text = u'%s(%s)' % (opt.help + ' ' if opt.help else '', opt_type)
+ if self.wrap_width is not None and self.wrap_width > 0:
+ lines = [textwrap.fill(help_text,
+ self.wrap_width,
+ initial_indent='# ',
+ subsequent_indent='# ') + '\n']
+ else:
+ lines = ['# ' + help_text + '\n']
+
+ for d in opt.deprecated_opts:
+ lines.append('# Deprecated group/name - [%s]/%s\n' %
+ (d.group or 'DEFAULT', d.name or opt.dest))
+
+ if opt.default is None:
+ default_str = '<None>'
+ elif isinstance(opt, cfg.StrOpt):
+ default_str = opt.default
+ elif isinstance(opt, cfg.BoolOpt):
+ default_str = str(opt.default).lower()
+ elif (isinstance(opt, cfg.IntOpt) or
+ isinstance(opt, cfg.FloatOpt)):
+ default_str = str(opt.default)
+ elif isinstance(opt, cfg.ListOpt):
+ default_str = ','.join(opt.default)
+ elif isinstance(opt, cfg.DictOpt):
+ sorted_items = sorted(opt.default.items(),
+ key=operator.itemgetter(0))
+ default_str = ','.join(['%s:%s' % i for i in sorted_items])
+ elif isinstance(opt, cfg.MultiStrOpt):
+ default_str = str(opt.default)
+ else:
+ LOG.warning('Unknown option type: %s', repr(opt))
+ default_str = str(opt.default)
+
+ defaults = [default_str]
+ if isinstance(opt, cfg.MultiStrOpt) and opt.default:
+ defaults = opt.default
+
+ for default_str in defaults:
+ default_str = self.sanitize_default(opt, default_str)
+ if default_str.strip() != default_str:
+ default_str = '"%s"' % default_str
+ lines.append('#%s = %s\n' % (opt.dest, default_str))
+
+ self.writelines(lines)
+
+ def write(self, s):
+ """Write an arbitrary string to the output file.
+
+ :param s: an arbitrary string
+ """
+ self.output_file.write(s)
+
+ def writelines(self, l):
+ """Write an arbitrary sequence of strings to the output file.
+
+ :param l: a list of arbitrary strings
+ """
+ self.output_file.writelines(l)
+
+
+def _get_sanitizer(name):
+ """Look up a sanitizer entry point name.
+
+ Look up the supplied name under the 'oslo.config.sanitizer' entry point
+ namespace and return the callable found there.
+
+ :param name: the entry point name, or None
+ :returns: the callable found, or None
+ """
+ if name is None:
+ return None
+ return stevedore.driver.DriverManager('oslo.config.sanitizer',
+ name=name).driver
+
+
+def _list_opts(namespaces):
+ """List the options available via the given namespaces.
+
+ :param namespaces: a list of namespaces registered under 'oslo.config.opts'
+ :returns: a list of (namespace, [(group, [opt_1, opt_2])]) tuples
+ """
+ mgr = stevedore.named.NamedExtensionManager('oslo.config.opts',
+ names=namespaces,
+ invoke_on_load=True)
+ return [(ep.name, ep.obj) for ep in mgr]
+
+
+def generate(conf):
+ """Generate a sample config file.
+
+ List all of the options available via the namespaces specified in the given
+ configuration and write a description of them to the specified output file.
+
+ :param conf: a ConfigOpts instance containing the generator's configuration
+ """
+ conf.register_opts(_generator_opts)
+
+ output_file = (open(conf.output_file, 'w')
+ if conf.output_file else sys.stdout)
+
+ sanitizer = _get_sanitizer(conf.sanitizer)
+
+ formatter = _OptFormatter(output_file=output_file,
+ sanitize_default=sanitizer,
+ wrap_width=conf.wrap_width)
+
+ groups = {'DEFAULT': []}
+ for namespace, listing in _list_opts(conf.namespace):
+ for group, opts in listing:
+ if not opts:
+ continue
+ namespaces = groups.setdefault(group or 'DEFAULT', [])
+ namespaces.append((namespace,
+ dict((opt.dest, opt) for opt in opts)))
+
+ def _output_opts(f, group, namespaces):
+ f.write('[%s]\n' % group)
+ for (namespace, opts_by_dest) in sorted(namespaces,
+ key=operator.itemgetter(0)):
+ f.write('\n#\n# From %s\n#\n' % namespace)
+ for opt in sorted(opts_by_dest.values(),
+ key=operator.attrgetter('dest')):
+ f.write('\n')
+ f.format(opt)
+
+ _output_opts(formatter, 'DEFAULT', groups.pop('DEFAULT'))
+ for group, namespaces in sorted(groups.items(),
+ key=operator.itemgetter(0)):
+ formatter.write('\n\n')
+ _output_opts(formatter, group, namespaces)
+
+
+def main(args=None):
+ """The main function of oslo-config-generator."""
+ logging.basicConfig(level=logging.WARN)
+ conf = cfg.ConfigOpts()
+ register_cli_opts(conf)
+ conf(args)
+ generate(conf)
+
+
+if __name__ == '__main__':
+ main()