diff options
Diffstat (limited to 'oslo/config/generator.py')
-rw-r--r-- | oslo/config/generator.py | 340 |
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() |