360 changes: 360 additions & 0 deletions doc/source/cli/generator.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,360 @@
=======================
oslo-config-generator
=======================

`oslo-config-generator` is a utility for generating sample config files in a
variety of formats. Sample config files list all of the available options,
along with their help string, type, deprecated aliases and defaults. These
sample files can be used as config files for `oslo.config` itself (``ini``) or
by configuration management tools (``json``, ``yaml``).

.. versionadded:: 1.4.0

.. versionchanged:: 4.3.0

The :option:`oslo-config-generator --format` parameter was added, which
allows outputting in additional formats.

Usage
-----

.. program:: oslo-config-generator

.. code-block:: shell
oslo-config-generator
--namespace <namespace> [--namespace <namespace> ...]
[--output-file <output-file>]
[--wrap-width <wrap-width>]
[--format <format>]
[--minimal]
[--summarize]
.. option:: --namespace <namespace>

Option namespace under ``oslo.config.opts`` in which to query for options.

.. option:: --output-file <output-file>

Path of the file to write to.

:Default: stdout

.. option:: --wrap-width <wrap-width>

The maximum length of help lines.

:Default: 70

.. option:: --format <format>

Desired format for the output. ``ini`` is the only format that can be used
directly with `oslo.config`. ``json`` and ``yaml`` are intended for
third-party tools that want to write config files based on the sample config
data. For more information, refer to :ref:`machine-readable-configs`.

:Choices: ini, json, yaml

.. option:: --minimal

Generate a minimal required configuration.

.. option:: --summarize

Only output summaries of help text to config files. Retain longer help text
for Sphinx documents.

For example, to generate a sample config file for `oslo.messaging` you would
run:

.. code-block:: shell
$ oslo-config-generator --namespace oslo.messaging > oslo.messaging.conf
To generate a sample config file for an application ``myapp`` that has its own
options and uses `oslo.messaging`, you would list both namespaces:

.. code-block:: shell
$ oslo-config-generator --namespace myapp \
--namespace oslo.messaging > myapp.conf
To generate a sample config file for `oslo.messaging` in `JSON` format, you
would run:

.. code-block:: shell
$ oslo-config-generator --namespace oslo.messaging \
--format json > oslo.messaging.conf
Defining Option Discovery Entry Points
--------------------------------------

The :option:`oslo-config-generator --namespace` option specifies an entry point
name registered under the ``oslo.config.opts`` entry point namespace. For
example, in the `oslo.messaging` ``setup.cfg`` we have:

.. code-block:: ini
[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, [opt_1, opt_2])`` tuples, where ``group`` is either a group
name as a string or an ``OptGroup`` object. Passing the ``OptGroup`` object
allows the consumer of the ``list_opts`` method to access and publish group
help. An example, using both styles:

.. code-block:: python
from oslo_config import cfg
opts1 = [
cfg.StrOpt('foo'),
cfg.StrOpt('bar'),
]
opts2 = [
cfg.StrOpt('baz'),
]
baz_group = cfg.OptGroup(name='baz_group'
title='Baz group options',
help='Baz group help text')
cfg.CONF.register_group(baz_group)
cfg.CONF.register_opts(opts1, group='blaa')
cfg.CONF.register_opts(opts2, group=baz_group)
def list_opts():
# Allows the generation of the help text for
# the baz_group OptGroup object. No help
# text is generated for the 'blaa' group.
return [('blaa', opts1), (baz_group, opts2)]
.. note::

You should return the original options, not a copy, because the
default update hooks depend on the original option object being
returned.

The module holding the entry point *must* be importable, even if the
dependencies of that module are not installed. For example, driver
modules that define options but have optional dependencies on
third-party modules must still be importable if those modules are not
installed. To accomplish this, the optional dependency can either be
imported using :func:`oslo.utils.importutils.try_import` or the option
definitions can be placed in a file that does not try to import the
optional dependency.

Modifying Defaults from Other Namespaces
----------------------------------------

Occasionally applications need to override the defaults for options
defined in libraries. At runtime this is done using an API within the
library. Since the config generator cannot guarantee the order in
which namespaces will be imported, we can't ensure that application
code can change the option defaults before the generator loads the
options from a library. Instead, a separate optional processing hook
is provided for applications to register a function to update default
values after *all* options are loaded.

The hooks are registered in a separate entry point namespace
(``oslo.config.opts.defaults``), using the same entry point name as
**the application's** ``list_opts()`` function.

.. code-block:: ini
[entry_points]
oslo.config.opts.defaults =
keystone = keystone.common.config:update_opt_defaults
.. warning::

Never, under any circumstances, register an entry point using a
name owned by another project. Doing so causes unexpected interplay
between projects within the config generator and will result in
failure to generate the configuration file or invalid values
showing in the sample.

In this case, the name of the entry point for the default override
function *must* match the name of one of the entry points defining
options for the application in order to be detected and
used. Applications that have multiple list_opts functions should use
one that is present in the inputs for the config generator where
the changed defaults need to appear. For example, if an application
defines ``foo.api`` to list the API-related options, and needs to
override the defaults in the ``oslo.middleware.cors`` library, the
application should register ``foo.api`` under
``oslo.config.opts.defaults`` and point to a function within the
application code space that changes the defaults for
``oslo.middleware.cors``.

The update function should take no arguments. It should invoke the
public :func:`set_defaults` functions in any libraries for which it
has option defaults to override, just as the application does during
its normal startup process.

.. code-block:: python
from oslo_log import log
def update_opt_defaults():
log.set_defaults(
default_log_levels=log.get_default_log_levels() + ['noisy=WARN'],
)
.. _machine-readable-configs:

Generating Machine Readable Configs
-----------------------------------

All deployment tools have to solve a similar problem: how to generate the
config files for each service at deployment time. To help with this problem,
`oslo-config-generator` can generate machine-readable sample config files that
output the same data as the INI files used by `oslo.config` itself, but in a
YAML or JSON format that can be more easily consumed by deployment tools.

.. important::

The YAML and JSON-formatted files generated by `oslo-config-generator`
cannot be used by `oslo.config` itself - they are only for use by other
tools.

For example, some YAML-formatted output might look like so:

.. code-block:: yaml
generator_options:
config_dir: []
config_file: []
format_: yaml
minimal: false
namespace:
- keystone
output_file: null
summarize: false
wrap_width: 70
options:
DEFAULT:
help: ''
opts:
- advanced: false
choices: []
default: null
deprecated_for_removal: false
deprecated_opts: []
deprecated_reason: null
deprecated_since: null
dest: admin_token
help: Using this feature is *NOT* recommended. Instead, use the `keystone-manage
bootstrap` command. The value of this option is treated as a "shared secret"
that can be used to bootstrap Keystone through the API. This "token" does
not represent a user (it has no identity), and carries no explicit authorization
(it effectively bypasses most authorization checks). If set to `None`, the
value is ignored and the `admin_token` middleware is effectively disabled.
However, to completely disable `admin_token` in production (highly recommended,
as it presents a security risk), remove `AdminTokenAuthMiddleware` (the `admin_token_auth`
filter) from your paste application pipelines (for example, in `keystone-paste.ini`).
max: null
metavar: null
min: null
mutable: false
name: admin_token
namespace: keystone
positional: false
required: false
sample_default: null
secret: true
short: null
type: string value
- ...
...
deprecated_options:
DEFAULT:
- name: bind_host
replacement_group: eventlet_server
replacement_name: public_bind_host
where the top-level keys are:

``generator_options``

The options passed to the :program:`oslo-config-generator` tool itself

``options``

All options registered in the provided namespace(s). These are grouped under
the ``OptGroup`` they are assigned to which defaults to ``DEFAULT`` if unset.

For information on the various attributes of each option, refer to
:class:`oslo_config.cfg.Opt` and its subclasses.

``deprecated_options``

All **deprecated** options registered in the provided namespace(s). Like
``options``, these options are grouped by ``OptGroup``.

Generating Multiple Sample Configs
----------------------------------

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:

.. code-block:: ini
[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:

.. code-block:: shell
$ 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:

.. code-block:: shell
$ 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.common
namespace = nova.compute
EOF
$ oslo-config-generator --config-file config-generator/api.conf
$ oslo-config-generator --config-file config-generator/compute.conf
Sample Default Values
---------------------
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, options can be supplied with a ``sample_default`` attribute:
.. code-block:: python
cfg.StrOpt('base_dir'
default=os.getcwd(),
sample_default='/usr/lib/myapp')
10 changes: 10 additions & 0 deletions doc/source/cli/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
================================
oslo.config Command Line Tools
================================

.. toctree::
:maxdepth: 2

generator
validator

231 changes: 231 additions & 0 deletions doc/source/cli/validator.rst

Large diffs are not rendered by default.

67 changes: 37 additions & 30 deletions doc/source/conf.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,39 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2020 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.

import os
import subprocess
import sys
import warnings

sys.path.insert(0, os.path.abspath('../..'))
# -- General configuration ----------------------------------------------------

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'oslosphinx',
'oslo_config.sphinxconfiggen',
'oslo_config.sphinxext']
extensions = [
'sphinx.ext.autodoc',
'openstackdocstheme',
'oslo_config.sphinxconfiggen',
'oslo_config.sphinxext',
'sphinxcontrib.apidoc',
'stevedore.sphinxext',
]

# openstackdocstheme options
openstackdocs_repo_name = 'openstack/oslo.config'
openstackdocs_bug_project = 'oslo.config'
openstackdocs_bug_tag = ''

config_generator_config_file = 'config-generator.conf'

Expand All @@ -30,8 +51,7 @@
master_doc = 'index'

# General information about the project.
project = u'oslo.config'
copyright = u'2013, OpenStack Foundation'
copyright = '2013, OpenStack Foundation'

# If true, '()' will be appended to :func: etc. cross-reference text.
add_function_parentheses = True
Expand All @@ -41,7 +61,7 @@
add_module_names = True

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
pygments_style = 'native'

# A list of ignored prefixes for module index sorting.
modindex_common_prefix = ['oslo_config.']
Expand All @@ -53,26 +73,13 @@
# Sphinx are currently 'default' and 'sphinxdoc'.
# html_theme_path = ["."]
# html_theme = '_theme'
html_static_path = ['static']

# Output file base name for HTML help builder.
htmlhelp_basename = '%sdoc' % project

git_cmd = ["git", "log", "--pretty=format:'%ad, commit %h'", "--date=local",
"-n1"]
try:
html_last_updated_fmt = subprocess.Popen(
git_cmd, stdout=subprocess.PIPE).communicate()[0].decode('utf-8')
except Exception:
warnings.warn('Cannot get last updated time from git repository. '
'Not setting "html_last_updated_fmt".')

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass
# [howto/manual]).
latex_documents = [
('index',
'%s.tex' % project,
'%s Documentation' % project,
'OpenStack Foundation', 'manual'),
# html_static_path = ['static']
html_theme = 'openstackdocs'

# -- sphinxcontrib.apidoc configuration --------------------------------------

apidoc_module_dir = '../../oslo_config'
apidoc_output_dir = 'reference/api'
apidoc_excluded_paths = [
'tests',
]
8 changes: 0 additions & 8 deletions doc/source/configopts.rst

This file was deleted.

10 changes: 10 additions & 0 deletions doc/source/configuration/drivers.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
==============================
Configuration Source Drivers
==============================

In addition to command line options and configuration files,
oslo.config can access configuration settings in other locations using
*drivers* to define new *sources*.

.. list-plugins:: oslo.config.driver
:detailed:
188 changes: 188 additions & 0 deletions doc/source/configuration/format.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
=========================
Configuration file format
=========================

OpenStack uses the INI file format for configuration files.
An INI file is a simple text file that specifies options as
``key=value`` pairs, grouped into sections.
The ``DEFAULT`` section contains most of the configuration options.
Lines starting with a hash sign (``#``) are comment lines.
For example:

.. code-block:: ini
[DEFAULT]
# Print debugging output (set logging level to DEBUG instead
# of default WARNING level). (boolean value)
debug = true
[database]
# The SQLAlchemy connection string used to connect to the
# database (string value)
connection = mysql+pymysql://keystone:KEYSTONE_DBPASS@controller/keystone
Options can have different types for values.
The comments in the sample config files always mention these and the
tables mention the ``Opt`` value as first item like ``(BoolOpt) Toggle...``.
The following types are used by OpenStack:

boolean value (``BoolOpt``)
Enables or disables an option. The allowed values are ``true`` and ``false``.

.. code-block:: ini
# Enable the experimental use of database reconnect on
# connection lost (boolean value)
use_db_reconnect = false
floating point value (``FloatOpt``)
A floating point number like ``0.25`` or ``1000``.

.. code-block:: ini
# Sleep time in seconds for polling an ongoing async task
# (floating point value)
task_poll_interval = 0.5
integer value (``IntOpt``)
An integer number is a number without fractional components,
like ``0`` or ``42``.

.. code-block:: ini
# The port which the OpenStack Compute service listens on.
# (integer value)
compute_port = 8774
IP address (``IPOpt``)
An IPv4 or IPv6 address.

.. code-block:: ini
# Address to bind the server. Useful when selecting a particular network
# interface. (ip address value)
bind_host = 0.0.0.0
key-value pairs (``DictOpt``)
A key-value pairs, also known as a dictionary. The key value pairs are
separated by commas and a colon is used to separate key and value.
Example: ``key1:value1,key2:value2``.

.. code-block:: ini
# Parameter for l2_l3 workflow setup. (dict value)
l2_l3_setup_params = data_ip_address:192.168.200.99, \
data_ip_mask:255.255.255.0,data_port:1,gateway:192.168.200.1,ha_port:2
list value (``ListOpt``)
Represents values of other types, separated by commas.
As an example, the following sets ``allowed_rpc_exception_modules``
to a list containing the four elements ``oslo.messaging.exceptions``,
``nova.exception``, ``cinder.exception``, and ``exceptions``:

.. code-block:: ini
# Modules of exceptions that are permitted to be recreated
# upon receiving exception data from an rpc call. (list value)
allowed_rpc_exception_modules = oslo.messaging.exceptions,nova.exception
multi valued (``MultiStrOpt``)
A multi-valued option is a string value and can be given
more than once, all values will be used.

.. code-block:: ini
# Driver or drivers to handle sending notifications. (multi valued)
notification_driver = nova.openstack.common.notifier.rpc_notifier
notification_driver = ceilometer.compute.nova_notifier
port value (``PortOpt``)
A TCP/IP port number. Ports can range from 1 to 65535.

.. code-block:: ini
# Port to which the UDP socket is bound. (port value)
# Minimum value: 1
# Maximum value: 65535
udp_port = 4952
string value (``StrOpt``)
Strings can be optionally enclosed with single or double quotes.

.. code-block:: ini
# The format for an instance that is passed with the log message.
# (string value)
instance_format = "[instance: %(uuid)s] "
Sections
~~~~~~~~

Configuration options are grouped by section.
Most configuration files support at least the following sections:

[DEFAULT]
Contains most configuration options.
If the documentation for a configuration option does not
specify its section, assume that it appears in this section.

[database]
Configuration options for the database that stores
the state of the OpenStack service.

Substitution
~~~~~~~~~~~~

The configuration file supports variable substitution.
After you set a configuration option, it can be referenced
in later configuration values when you precede it with
a ``$``, like ``$OPTION``.

The following example uses the values of ``rabbit_host`` and
``rabbit_port`` to define the value of the ``rabbit_hosts``
option, in this case as ``controller:5672``.

.. code-block:: ini
# The RabbitMQ broker address where a single node is used.
# (string value)
rabbit_host = controller
# The RabbitMQ broker port where a single node is used.
# (integer value)
rabbit_port = 5672
# RabbitMQ HA cluster host:port pairs. (list value)
rabbit_hosts = $rabbit_host:$rabbit_port
To avoid substitution, escape the ``$`` with ``$$`` or ``\$``.
For example, if your LDAP DNS password is ``$xkj432``, specify it, as follows:

.. code-block:: ini
ldap_dns_password = $$xkj432
The code uses the Python ``string.Template.safe_substitute()``
method to implement variable substitution.
For more details on how variable substitution is resolved, see
https://docs.python.org/2/library/string.html#template-strings
and `PEP 292 <https://www.python.org/dev/peps/pep-0292/>`_.

Whitespace
~~~~~~~~~~

To include whitespace in a configuration value, use a quoted string.
For example:

.. code-block:: ini
ldap_dns_password='a password with spaces'
Define an alternate location for a config file
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Most services and the ``*-manage`` command-line clients load
the configuration file.
To define an alternate location for the configuration file,
pass the ``--config-file CONFIG_FILE`` parameter
when you start a service or call a ``*-manage`` command.
12 changes: 12 additions & 0 deletions doc/source/configuration/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
=====================
Configuration Guide
=====================

.. toctree::
:maxdepth: 2

quickstart
format
mutable
options
drivers
27 changes: 27 additions & 0 deletions doc/source/configuration/mutable.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
==========================
Changing config at runtime
==========================

OpenStack Newton introduces the ability to reload (or 'mutate') certain
configuration options at runtime without a service restart. The following
projects support this:

* Compute (nova)

Check individual options to discover if they are mutable.


In practice
~~~~~~~~~~~

A common use case is to enable debug logging after a failure. Use the mutable
config option called 'debug' to do this (providing ``log_config_append``
has not been set). An admin user may perform the following steps:

#. Log onto the compute node.
#. Edit the config file (EG ``nova.conf``) and change 'debug' to ``True``.
#. Send a SIGHUP signal to the nova process (For example, ``pkill -HUP nova``).

A log message will be written out confirming that the option has been changed.
If you use a CMS like Ansible, Chef, or Puppet, we recommend scripting these
steps through your CMS.
24 changes: 24 additions & 0 deletions doc/source/configuration/options.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
========================================
Configuration Options from oslo.config
========================================

When loading values from the sources defined by the following options, the
precedence is as follows:

#. Command Line
#. Environment Variables
#. Config Files from ``--config-dir`` [1]_
#. Config Files from ``--config-file``
#. Pluggable Config Sources

If a value is specified in multiple locations, the location used will be the
one higher in the list. For example, if a value is specified both on the
command line and in an environment variable, the value from the command line
will be the one returned.

.. [1] Files in a config dir are parsed in alphabetical order. Later files
take precedence over earlier ones.
.. show-options::

oslo.config
78 changes: 78 additions & 0 deletions doc/source/configuration/quickstart.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
==========================
oslo.config Quick Start!
==========================

Are you brand new to oslo.config? This brief tutorial will get you started
understanding some of the fundamentals.

Prerequisites
-------------
* A plain text editor or Python-enabled IDE
* A Python interpreter
* A command shell from which the interpreter can be invoked
* The oslo_config library in your Python path.

Test Script
-----------
Put this in a file called ``oslocfgtest.py``.

.. code:: python
# The sys module lets you get at the command line arguments.
import sys
# Load up the cfg module, which contains all the classes and methods
# you'll need.
from oslo_config import cfg
# Define an option group
grp = cfg.OptGroup('mygroup')
# Define a couple of options
opts = [cfg.StrOpt('option1'),
cfg.IntOpt('option2', default=42)]
# Register your config group
cfg.CONF.register_group(grp)
# Register your options within the config group
cfg.CONF.register_opts(opts, group=grp)
# Process command line arguments. The arguments tell CONF where to
# find your config file, which it loads and parses to populate itself.
cfg.CONF(sys.argv[1:])
# Now you can access the values from the config file as
# CONF.<group>.<opt>
print("The value of option1 is %s" % cfg.CONF.mygroup.option1)
print("The value of option2 is %d" % cfg.CONF.mygroup.option2)
Conf File
---------
Put this in a file called ``oslocfgtest.conf`` in the same directory as
``oslocfgtest.py``.

.. code:: ini
[mygroup]
option1 = foo
# Comment out option2 to test the default value
# option2 = 123
Run It!
-------
From your command shell, in the same directory as your script and conf, invoke:

.. code:: shell
python oslocfgtest.py --config-file oslocfgtest.conf
Revel in the output being exactly as expected. If you've done everything
right, you should see:

.. code:: shell
The value of option1 is foo
The value of option2 is 42
Now go play with some more advanced option settings!
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
Contributing
==============

.. include:: ../../CONTRIBUTING.rst
.. include:: ../../../CONTRIBUTING.rst
20 changes: 0 additions & 20 deletions doc/source/exceptions.rst

This file was deleted.

8 changes: 0 additions & 8 deletions doc/source/fixture.rst

This file was deleted.

195 changes: 0 additions & 195 deletions doc/source/generator.rst

This file was deleted.

8 changes: 0 additions & 8 deletions doc/source/helpers.rst

This file was deleted.

2 changes: 0 additions & 2 deletions doc/source/history.rst

This file was deleted.

34 changes: 10 additions & 24 deletions doc/source/index.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
oslo.config
===========
=============
oslo.config
=============

An OpenStack library for parsing configuration options from the command
line and configuration files.
Expand All @@ -10,32 +11,17 @@ Contents
.. toctree::
:maxdepth: 2

cfg
opts
types
configopts
cfgfilter
helpers
fixture
parser
exceptions
namespaces
styleguide
mutable
generator
builtins
sphinxext
sphinxconfiggen
faq
contributing
configuration/index
reference/index
cli/index
contributor/index


Release Notes
=============

.. toctree::
:maxdepth: 1

history
Read also the `oslo.config Release Notes
<https://docs.openstack.org/releasenotes/oslo.config/>`_.

Indices and tables
==================
Expand Down
22 changes: 0 additions & 22 deletions doc/source/opts.rst

This file was deleted.

11 changes: 0 additions & 11 deletions doc/source/parser.rst

This file was deleted.

16 changes: 16 additions & 0 deletions doc/source/reference/accessing.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
======================================
Accessing Option Values In Your Code
======================================

Option values in the default group are referenced as attributes/properties on
the config manager; groups are also attributes on the config manager, with
attributes for each of the options associated with the group:

.. code-block:: python
server.start(app, conf.bind_port, conf.bind_host, conf)
self.connection = kombu.connection.BrokerConnection(
hostname=conf.rabbit.host,
port=conf.rabbit.port,
...)
41 changes: 41 additions & 0 deletions doc/source/reference/command-line.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
======================
Command Line Options
======================

Positional Command Line Arguments
---------------------------------

Positional command line arguments are supported via a 'positional' Opt
constructor argument:

.. code-block:: console
>>> conf = cfg.ConfigOpts()
>>> conf.register_cli_opt(cfg.MultiStrOpt('bar', positional=True))
True
>>> conf(['a', 'b'])
>>> conf.bar
['a', 'b']
By default, positional arguments are also required. You may opt-out of this
behavior by setting ``required=False``, to have an optional positional
argument.

Sub-Parsers
-----------

It is also possible to use argparse "sub-parsers" to parse additional
command line arguments using the SubCommandOpt class:

.. code-block:: console
>>> def add_parsers(subparsers):
... list_action = subparsers.add_parser('list')
... list_action.add_argument('id')
...
>>> conf = cfg.ConfigOpts()
>>> conf.register_cli_opt(cfg.SubCommandOpt('action', handler=add_parsers))
True
>>> conf(args=['list', '10'])
>>> conf.action.name, conf.action.id
('list', '10')
87 changes: 87 additions & 0 deletions doc/source/reference/configuration-files.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
=============================
Loading Configuration Files
=============================

The config manager has two CLI options defined by default, ``--config-file``
and ``--config-dir``:

.. code-block:: python
class ConfigOpts:
def __call__(self, ...):
opts = [
MultiStrOpt('config-file',
...),
StrOpt('config-dir',
...),
]
self.register_cli_opts(opts)
Option values are parsed from any supplied config files using
oslo_config.iniparser. If none are specified, a default set is used
for example glance-api.conf and glance-common.conf:

.. code-block:: text
glance-api.conf:
[DEFAULT]
bind_port = 9292
glance-common.conf:
[DEFAULT]
bind_host = 0.0.0.0
Lines in a configuration file should not start with whitespace. A
configuration file also supports comments, which must start with '#' or ';'.
Option values in config files and those on the command line are parsed
in order. The same option (includes deprecated option name and current
option name) can appear many times, in config files or on the command line.
Later values always override earlier ones.

The order of configuration files inside the same configuration directory is
defined by the alphabetic sorting order of their file names. Files in a
configuration directory are parsed after any individual configuration files,
so values that appear in both a configuration file and configuration directory
will use the value from the directory.

The parsing of CLI args and config files is initiated by invoking the config
manager for example:

.. code-block:: python
conf = cfg.ConfigOpts()
conf.register_opt(cfg.BoolOpt('verbose', ...))
conf(sys.argv[1:])
if conf.verbose:
...
Option Value Interpolation
--------------------------

Option values may reference other values using PEP 292 string substitution:

.. code-block:: python
opts = [
cfg.StrOpt('state_path',
default=os.path.join(os.path.dirname(__file__), '../'),
help='Top-level directory for maintaining nova state.'),
cfg.StrOpt('sqlite_db',
default='nova.sqlite',
help='File name for SQLite.'),
cfg.StrOpt('sql_connection',
default='sqlite:///$state_path/$sqlite_db',
help='Connection string for SQL database.'),
]
.. note::

Interpolation can be avoided by using `$$`.

.. note::

You can use `.` to delimit option from other groups, e.g.
${mygroup.myoption}.
320 changes: 320 additions & 0 deletions doc/source/reference/defining.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,320 @@
==================
Defining Options
==================

Configuration options may be set on the command line, in the
:mod:`environment <oslo_config.sources._environment>`, or in config files.
Options are processed in that order.

The schema for each option is defined using the
:class:`Opt` class or its sub-classes, for example:

.. code-block:: python
from oslo_config import cfg
from oslo_config import types
PortType = types.Integer(1, 65535)
common_opts = [
cfg.StrOpt('bind_host',
default='0.0.0.0',
help='IP address to listen on.'),
cfg.Opt('bind_port',
type=PortType,
default=9292,
help='Port number to listen on.')
]
Option Types
------------

Options can have arbitrary types via the `type` parameter to the :class:`Opt`
constructor. The `type` parameter is a callable object that takes a string and
either returns a value of that particular type or raises :class:`ValueError` if
the value can not be converted.

For convenience, there are predefined option subclasses in
:mod:`oslo_config.cfg` that set the option `type` as in the following table:

====================================== ======
Type Option
====================================== ======
:class:`oslo_config.types.String` :class:`oslo_config.cfg.StrOpt`
:class:`oslo_config.types.String` :class:`oslo_config.cfg.SubCommandOpt`
:class:`oslo_config.types.Boolean` :class:`oslo_config.cfg.BoolOpt`
:class:`oslo_config.types.Integer` :class:`oslo_config.cfg.IntOpt`
:class:`oslo_config.types.Float` :class:`oslo_config.cfg.FloatOpt`
:class:`oslo_config.types.Port` :class:`oslo_config.cfg.PortOpt`
:class:`oslo_config.types.List` :class:`oslo_config.cfg.ListOpt`
:class:`oslo_config.types.Dict` :class:`oslo_config.cfg.DictOpt`
:class:`oslo_config.types.IPAddress` :class:`oslo_config.cfg.IPOpt`
:class:`oslo_config.types.Hostname` :class:`oslo_config.cfg.HostnameOpt`
:class:`oslo_config.types.HostAddress` :class:`oslo_config.cfg.HostAddressOpt`
:class:`oslo_config.types.URI` :class:`oslo_config.cfg.URIOpt`
====================================== ======

For :class:`oslo_config.cfg.MultiOpt` the `item_type` parameter defines
the type of the values. For convenience, :class:`oslo_config.cfg.MultiStrOpt`
is :class:`~oslo_config.cfg.MultiOpt` with the `item_type` parameter set to
:class:`oslo_config.types.MultiString`.

The following example defines options using the convenience classes:

.. code-block:: python
enabled_apis_opt = cfg.ListOpt('enabled_apis',
default=['ec2', 'osapi_compute'],
help='List of APIs to enable by default.')
DEFAULT_EXTENSIONS = [
'nova.api.openstack.compute.contrib.standard_extensions'
]
osapi_compute_extension_opt = cfg.MultiStrOpt('osapi_compute_extension',
default=DEFAULT_EXTENSIONS)
Registering Options
-------------------

Option schemas are registered with the config manager at runtime, but before
the option is referenced:

.. code-block:: python
class ExtensionManager:
enabled_apis_opt = cfg.ListOpt(...)
def __init__(self, conf):
self.conf = conf
self.conf.register_opt(enabled_apis_opt)
...
def _load_extensions(self):
for ext_factory in self.conf.osapi_compute_extension:
....
A common usage pattern is for each option schema to be defined in the module or
class which uses the option:

.. code-block:: python
opts = ...
def add_common_opts(conf):
conf.register_opts(opts)
def get_bind_host(conf):
return conf.bind_host
def get_bind_port(conf):
return conf.bind_port
An option may optionally be made available via the command line. Such options
must be registered with the config manager before the command line is parsed
(for the purposes of --help and CLI arg validation).

Note that options registered for CLI use do not need to be registered again for
use from other config sources, such as files. CLI options can be read from
either the CLI or from the other enabled config sources.

.. code-block:: python
cli_opts = [
cfg.BoolOpt('verbose',
short='v',
default=False,
help='Print more verbose output.'),
cfg.BoolOpt('debug',
short='d',
default=False,
help='Print debugging output.'),
]
def add_common_opts(conf):
conf.register_cli_opts(cli_opts)
Option Groups
-------------

Options can be registered as belonging to a group:

.. code-block:: python
rabbit_group = cfg.OptGroup(name='rabbit',
title='RabbitMQ options')
rabbit_host_opt = cfg.StrOpt('host',
default='localhost',
help='IP/hostname to listen on.'),
rabbit_port_opt = cfg.PortOpt('port',
default=5672,
help='Port number to listen on.')
def register_rabbit_opts(conf):
conf.register_group(rabbit_group)
# options can be registered under a group in either of these ways:
conf.register_opt(rabbit_host_opt, group=rabbit_group)
conf.register_opt(rabbit_port_opt, group='rabbit')
If no group attributes are required other than the group name, the group
need not be explicitly registered for example:

.. code-block:: python
def register_rabbit_opts(conf):
# The group will automatically be created, equivalent calling:
# conf.register_group(OptGroup(name='rabbit'))
conf.register_opt(rabbit_port_opt, group='rabbit')
If no group is specified, options belong to the 'DEFAULT' section of config
files:

.. code-block:: text
glance-api.conf:
[DEFAULT]
bind_port = 9292
...
[rabbit]
host = localhost
port = 5672
use_ssl = False
userid = guest
password = guest
virtual_host = /
Command-line options in a group are automatically prefixed with the
group name:

.. code-block:: console
--rabbit-host localhost --rabbit-port 9999
Dynamic Groups
--------------

Groups can be registered dynamically by application code. This
introduces a challenge for the sample generator, discovery mechanisms,
and validation tools, since they do not know in advance the names of
all of the groups. The ``dynamic_group_owner`` parameter to the
constructor specifies the full name of an option registered in another
group that controls repeated instances of a dynamic group. This option
is usually a MultiStrOpt.

For example, Cinder supports multiple storage backend devices and
services. To configure Cinder to communicate with multiple backends,
the ``enabled_backends`` option is set to the list of names of
backends. Each backend group includes the options for communicating
with that device or service.

Driver Groups
-------------

Groups can have dynamic sets of options, usually based on a driver
that has unique requirements. This works at runtime because the code
registers options before it uses them, but it introduces a challenge
for the sample generator, discovery mechanisms, and validation tools
because they do not know in advance the correct options for a group.

To address this issue, the driver option for a group can be named
using the ``driver_option`` parameter. Each driver option should
define its own discovery entry point namespace to return the set of
options for that driver, named using the prefix
``"oslo.config.opts."`` followed by the driver option name.

In the Cinder case described above, a ``volume_backend_name`` option
is part of the static definition of the group, so ``driver_option``
should be set to ``"volume_backend_name"``. And plugins should be
registered under ``"oslo.config.opts.volume_backend_name"`` using the
same names as the main plugin registered with
``"oslo.config.opts"``. The drivers residing within the Cinder code
base have an entry point named ``"cinder"`` registered.

Special Handling Instructions
-----------------------------

Options may be declared as required so that an error is raised if the user
does not supply a value for the option:

.. code-block:: python
opts = [
cfg.StrOpt('service_name', required=True),
cfg.StrOpt('image_id', required=True),
...
]
Options may be declared as secret so that their values are not leaked into
log files:

.. code-block:: python
opts = [
cfg.StrOpt('s3_store_access_key', secret=True),
cfg.StrOpt('s3_store_secret_key', secret=True),
...
]
Dictionary Options
------------------

If you need end users to specify a dictionary of key/value pairs, then you can
use the DictOpt:

.. code-block:: python
opts = [
cfg.DictOpt('foo',
default={})
]
The end users can then specify the option foo in their configuration file
as shown below:

.. code-block:: ini
[DEFAULT]
foo = k1:v1,k2:v2
Advanced Option
---------------

Use if you need to label an option as advanced in sample files, indicating the
option is not normally used by the majority of users and might have a
significant effect on stability and/or performance:

.. code-block:: python
from oslo_config import cfg
opts = [
cfg.StrOpt('option1', default='default_value',
advanced=True, help='This is help '
'text.'),
cfg.PortOpt('option2', default='default_value',
help='This is help text.'),
]
CONF = cfg.CONF
CONF.register_opts(opts)
This will result in the option being pushed to the bottom of the
namespace and labeled as advanced in the sample files, with a notation
about possible effects:

.. code-block:: ini
[DEFAULT]
...
# This is help text. (string value)
# option2 = default_value
...
<pushed to bottom of section>
...
# This is help text. (string value)
# Advanced Option: intended for advanced users and not used
# by the majority of users, and might have a significant
# effect on stability and/or performance.
# option1 = default_value
47 changes: 47 additions & 0 deletions doc/source/reference/deprecating.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
====================
Option Deprecation
====================

If you want to rename some options, move them to another group or remove
completely, you may change their declarations using `deprecated_name`,
`deprecated_group` and `deprecated_for_removal` parameters to the :class:`Opt`
constructor:

.. code-block:: python
from oslo_config import cfg
conf = cfg.ConfigOpts()
opt_1 = cfg.StrOpt('opt_1', default='foo', deprecated_name='opt1')
opt_2 = cfg.StrOpt('opt_2', default='spam', deprecated_group='DEFAULT')
opt_3 = cfg.BoolOpt('opt_3', default=False, deprecated_for_removal=True)
conf.register_opt(opt_1, group='group_1')
conf.register_opt(opt_2, group='group_2')
conf.register_opt(opt_3)
conf(['--config-file', 'config.conf'])
assert conf.group_1.opt_1 == 'bar'
assert conf.group_2.opt_2 == 'eggs'
assert conf.opt_3
Assuming that the file config.conf has the following content:

.. code-block:: ini
[group_1]
opt1 = bar
[DEFAULT]
opt_2 = eggs
opt_3 = True
the script will succeed, but will log three respective warnings about the
given deprecated options.

There are also `deprecated_reason` and `deprecated_since` parameters for
specifying some additional information about a deprecation.

All the mentioned parameters can be mixed together in any combinations.
16 changes: 16 additions & 0 deletions doc/source/reference/drivers.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

---------------
Backend Drivers
---------------

Refer to :py:mod:`oslo_config.sources`


Known Backend Drivers
---------------------

.. NOTE(bnemec): These are private modules, so we need to explicitly
document them
.. automodule:: oslo_config.sources._uri
.. automodule:: oslo_config.sources._environment
File renamed without changes.
21 changes: 21 additions & 0 deletions doc/source/reference/globals.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
===================
Global ConfigOpts
===================

This module also contains a global instance of the ConfigOpts class
in order to support a common usage pattern in OpenStack:

.. code-block:: python
from oslo_config import cfg
opts = [
cfg.StrOpt('bind_host', default='0.0.0.0'),
cfg.PortOpt('bind_port', default=9292),
]
CONF = cfg.CONF
CONF.register_opts(opts)
def start(server, app):
server.start(app, CONF.bind_port, CONF.bind_host)
20 changes: 20 additions & 0 deletions doc/source/reference/helpers.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
----------------
Helper Functions
----------------

Showing detailed locations for configuration settings
-----------------------------------------------------

``oslo.config`` can track the location in application and library code
where an option is defined, defaults are set, or values are
overridden. This feature is disabled by default because it is
expensive and incurs a significant performance penalty, but it can be
useful for developers tracing down issues with configuration option
definitions.

To turn on detailed location tracking, set the environment variable
``OSLO_CONFIG_SHOW_CODE_LOCATIONS`` to any non-empty value (for
example, ``"1"`` or ``"yes, please"``) before starting the
application, test suite, or script. Then use
:func:`ConfigOpts.get_location` to access the location data for the
option.
23 changes: 23 additions & 0 deletions doc/source/reference/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
=============================
oslo.config Reference Guide
=============================

.. toctree::
:maxdepth: 2

API <api/modules>
defining
naming
accessing
configuration-files
command-line
deprecating
globals
helpers
styleguide
mutable
locations
sphinxext
sphinxconfiggen
drivers
faq
69 changes: 69 additions & 0 deletions doc/source/reference/locations.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
==========================
Option Setting Locations
==========================

.. currentmodule:: oslo_config.cfg

The :func:`~ConfigOpts.get_location` method of :class:`ConfigOpts` can
be used to determine where the value for an option was set, either by
the user or by the application code. The return value is a
:class:`LocationInfo` instance, which includes 2 fields: ``location``
and ``detail``.

The ``location`` value is a member of the :class:`Locations` enum,
which has 5 possible values. The ``detail`` value is a string
describing the location. Its value depends on the ``location``.

.. list-table::
:header-rows: 1
:widths: 15 15 35 35

* - Value
- ``is_user_controlled``
- Description
- ``detail``
* - ``opt_default``
- ``False``
- The original default set when the option was defined.
- The source file name where the option is defined.
* - ``set_default``
- ``False``
- A default value set by the application as an override of the
original default. This usually only applies to options defined
in libraries.
- The source file name where :func:`~ConfigOpts.set_default` or
:func:`set_defaults` was called.
* - ``set_override``
- ``False``
- A forced value set by the application.
- The source file name where :func:`~ConfigOpts.set_override` was
called.
* - ``user``
- ``True``
- A value set by the user through a configuration backend such as
a file.
- The configuration file where the option is set.
* - ``command_line``
- ``True``
- A value set by the user on the command line.
- Empty string.
* - ``environment``
- ``True``
- A value set by the user in the process environment.
- The name of the environment variable.

Did a user set a configuration option?
======================================

Each :class:`Locations` enum value has a boolean property indicating
whether that type of location is managed by the user. This eliminates
the need for application code to track which types of locations are
user-controlled separately.

.. code-block:: python
loc = CONF.get_location('normal_opt').location
if loc.is_user_controlled:
print('normal_opt was set by the user')
else:
print('normal_opt was set by the application')
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ The help strings are parsed from the code to appear in sample
configuration files, such as ``etc/cinder/cinder.conf`` in the
cinder repository. They are also displayed in the `OpenStack
Configuration Reference
<http://docs.openstack.org/trunk/config-reference/content/index.html>`_.
<https://docs.openstack.org/oslo.config/latest/reference/index.html>`_.

Examples::

Expand All @@ -29,7 +29,7 @@ Style Guide

2. Only use single spaces, no double spaces.

3. Properly capitalize words. If in doubt check the `OpenStack Glossary <http://docs.openstack.org/glossary/>`_.
3. Properly capitalize words. If in doubt check the `OpenStack Glossary <https://docs.openstack.org/oslo.config/latest/reference/styleguide.html#style-guide>`_.

4. End each segment with a period and write complete sentences if
possible. Examples::
Expand Down
6 changes: 0 additions & 6 deletions doc/source/types.rst

This file was deleted.

14 changes: 2 additions & 12 deletions oslo_config/_i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

"""oslo.i18n integration module.
See http://docs.openstack.org/developer/oslo.i18n/usage.html .
See https://docs.openstack.org/oslo.i18n/latest/user/index.html.
"""

Expand All @@ -35,16 +35,6 @@
# requires oslo.i18n >=2.1.0
_P = _translators.plural_form

# Translators for log levels.
#
# The abbreviated names are meant to reflect the usual use of a short
# name like '_'. The "L" is for "log" and the other letter comes from
# the level.
_LI = _translators.log_info
_LW = _translators.log_warning
_LE = _translators.log_error
_LC = _translators.log_critical


def get_available_languages():
return oslo_i18n.get_available_languages(DOMAIN)
return oslo_i18n.get_available_languages(DOMAIN)
51 changes: 49 additions & 2 deletions oslo_config/_list_opts.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@
# License for the specific language governing permissions and limitations
# under the License.

import copy
import inspect

from oslo_config import cfg

import stevedore


def list_opts():
default_config_files = [
Expand All @@ -20,6 +25,48 @@ def list_opts():
'/etc/project/project.conf',
'/etc/project.conf',
]
return [
(None, cfg.ConfigOpts._make_config_options(default_config_files)),
default_config_dirs = [
'~/.project/project.conf.d/',
'~/project.conf.d/',
'/etc/project/project.conf.d/',
'/etc/project.conf.d/',
]
options = [(None, cfg.ConfigOpts._list_options_for_discovery(
default_config_files,
default_config_dirs,
))]

ext_mgr = stevedore.ExtensionManager(
"oslo.config.driver",
invoke_on_load=True)

source_names = ext_mgr.names()
for source_name in source_names:
source = ext_mgr[source_name].obj
source_options = copy.deepcopy(source.list_options_for_discovery())
source_description = inspect.getdoc(source)
source_options.insert(
0,
cfg.StrOpt(
name='driver',
sample_default=source_name,
help=cfg._SOURCE_DRIVER_OPTION_HELP,
)
)
group_name = 'sample_{}_source'.format(source_name)
group_help = 'Example of using a {} source'.format(source_name)
if source_description:
group_help = '{}\n\n{}: {}'.format(
group_help,
source_name,
source_description,
)
group = cfg.OptGroup(
name=group_name,
help=group_help,
driver_option='driver',
dynamic_group_owner='config_source',
)
options.append((group, source_options))

return options
1,706 changes: 983 additions & 723 deletions oslo_config/cfg.py

Large diffs are not rendered by default.

383 changes: 0 additions & 383 deletions oslo_config/cfgfilter.py

This file was deleted.

52 changes: 42 additions & 10 deletions oslo_config/fixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
# under the License.

import fixtures
import six

from oslo_config import cfg

Expand All @@ -37,17 +36,23 @@ def setUp(self):
# reset is because cleanup works in reverse order of registered items,
# and a reset must occur before unregistering options can occur.
self.addCleanup(self._reset_default_config_files)
self.addCleanup(self._reset_default_config_dirs)
self.addCleanup(self._unregister_config_opts)
self.addCleanup(self.conf.reset)
self._registered_config_opts = {}

# Grab an old copy of the default config files - if it exists - for
# subsequent cleanup.
# Grab an old copy of the default config files/dirs - if it exists -
# for subsequent cleanup.
if hasattr(self.conf, 'default_config_files'):
self._default_config_files = self.conf.default_config_files
else:
self._default_config_files = None

if hasattr(self.conf, 'default_config_dirs'):
self._default_config_dirs = self.conf.default_config_dirs
else:
self._default_config_dirs = None

def config(self, **kw):
"""Override configuration values.
Expand All @@ -58,14 +63,11 @@ def config(self, **kw):
the specified configuration option group, otherwise the overrides
are applied to the ``default`` group.
If a `enforce_type` is supplied, will convert the override
value to the option's type before overriding.
"""

group = kw.pop('group', None)
enforce_type = kw.pop('enforce_type', False)
for k, v in six.iteritems(kw):
self.conf.set_override(k, v, group, enforce_type=enforce_type)
for k, v in kw.items():
self.conf.set_override(k, v, group)

def _unregister_config_opts(self):
for group in self._registered_config_opts:
Expand All @@ -83,6 +85,17 @@ def _reset_default_config_files(self):
# being unset.
self.conf.default_config_files = None

def _reset_default_config_dirs(self):
if not hasattr(self.conf, 'default_config_dirs'):
return

if self._default_config_dirs:
self.conf.default_config_dirs = self._default_config_dirs
else:
# Delete, because we could conceivably begin with the property
# being unset.
self.conf.default_config_dirs = None

def register_opt(self, opt, group=None):
"""Register a single option for the test run.
Expand Down Expand Up @@ -157,11 +170,12 @@ def load_raw_values(self, group=None, **kwargs):

raw_config = dict()
raw_config[group] = dict()
for key, value in six.iteritems(kwargs):
for key, value in kwargs.items():
# Parsed values are an array of raw strings.
raw_config[group][key] = [str(value)]

self.conf._namespace._add_parsed_config_file(raw_config, raw_config)
self.conf._namespace._add_parsed_config_file(
'<memory>', raw_config, raw_config)

def set_config_files(self, config_files):
"""Specify a list of config files to read.
Expand All @@ -181,6 +195,24 @@ def set_config_files(self, config_files):
self.conf.default_config_files = config_files
self.conf.reload_config_files()

def set_config_dirs(self, config_dirs):
"""Specify a list of config dirs to read.
This method allows you to predefine the list of configuration dirs
that are loaded by oslo_config. It will ensure that your tests do not
attempt to autodetect, and accidentally pick up config files from
locally installed services.
"""
if not isinstance(config_dirs, list):
raise AttributeError("Please pass a list() to set_config_dirs()")

# Make sure the namespace exists for our tests.
if not self.conf._namespace:
self.conf([])

self.conf.default_config_dirs = config_dirs
self.conf.reload_config_files()

def set_default(self, name, default, group=None):
"""Set a default value for an option.
Expand Down
493 changes: 422 additions & 71 deletions oslo_config/generator.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion oslo_config/iniparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def __str__(self):
return 'at line %d, %s: %r' % (self.lineno, self.msg, self.line)


class BaseParser(object):
class BaseParser:
lineno = 0
parse_exc = ParseError

Expand Down
121 changes: 121 additions & 0 deletions oslo_config/sources/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# 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"""
Oslo.config's primary source of configuration data are plaintext, INI-like
style, configuration files. With the addition of backend drivers support,
it is possible to store configuration data in different places and with
different formats, as long as there exists a proper driver to connect to those
external sources and provide a way to fetch configuration values from them.
A backend driver implementation is divided in two main classes, a driver
class of type :class:`ConfigurationSourceDriver` and a configuration source
class of type :class:`ConfigurationSource`.
**IMPORTANT:** At this point, all backend drivers are only able to provide
immutable values. This protects applications and services from having options
in external sources mutated when they reload configuration files.
"""

import abc


# We cannot use None as a sentinel indicating a missing value because it
# may be a valid value or default, so we use a custom singleton instead.
_NoValue = object()


class ConfigurationSourceDriver(metaclass=abc.ABCMeta):
"""A backend driver option for oslo.config.
For each group name listed in **config_source** in the **DEFAULT** group,
a :class:`ConfigurationSourceDriver` is responsible for creating one new
instance of a :class:`ConfigurationSource`. The proper driver class to be
used is selected based on the **driver** option inside each one of the
groups listed in config_source and loaded through entry points managed by
stevedore using the namespace oslo.config.driver::
[DEFAULT]
config_source = source1
config_source = source2
...
[source1]
driver = remote_file
...
[source2]
driver = castellan
...
Each specific driver knows all the available options to properly instatiate
a ConfigurationSource using the values comming from the given group in the
open_source_from_opt_group() method and is able to generate sample config
through the list_options_for_discovery() method.
"""

@abc.abstractmethod
def open_source_from_opt_group(self, conf, group_name):
"""Return an open configuration source.
Uses group_name to find the configuration settings for the new
source and then open the configuration source and return it.
If a source cannot be open, raises an appropriate exception.
:param conf: The active configuration option handler from which
to seek configuration values.
:type conf: ConfigOpts
:param group_name: The configuration option group name where the
options for the source are stored.
:type group_name: str
:returns: an instance of a subclass of ConfigurationSource
"""

@abc.abstractmethod
def list_options_for_discovery(self):
"""Return the list of options available to configure a new source.
Drivers should advertise all supported options in this method
for the purpose of sample generation by oslo-config-generator.
For an example on how to implement this method
you can check the **remote_file** driver at
oslo_config.sources._uri.URIConfigurationSourceDriver.
:returns: a list of supported options of a ConfigurationSource.
"""


class ConfigurationSource(metaclass=abc.ABCMeta):
"""A configuration source option for oslo.config.
A configuration source is able to fetch configuration values based on
a (group, option) key from an external source that supports key-value
mapping such as INI files, key-value stores, secret stores, and so on.
"""

@abc.abstractmethod
def get(self, group_name, option_name, opt):
"""Return the value of the option from the group.
:param group_name: Name of the group.
:type group_name: str
:param option_name: Name of the option.
:type option_name: str
:param opt: The option definition.
:type opt: Opt
:returns: A tuple (value, location) where value is the option value
or oslo_config.sources._NoValue if the (group, option) is
not present in the source, and location is a LocationInfo.
"""
99 changes: 99 additions & 0 deletions oslo_config/sources/_environment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# 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"""
Environment
-----------
The **environment** backend driver provides a method of accessing
configuration data in environment variables. It is enabled by default
and requires no additional configuration to use. The environment is
checked after command line options, but before configuration files.
Environment variables are checked for any configuration data. The variable
names take the form:
* A prefix of ``OS_``
* The group name, uppercased
* Separated from the option name by a `__` (double underscore)
* Followed by the name
For an option that looks like this in the usual INI format::
[placement_database]
connection = sqlite:///
the corresponding environment variable would be
``OS_PLACEMENT_DATABASE__CONNECTION``.
The Driver Class
================
.. autoclass:: EnvironmentConfigurationSourceDriver
The Configuration Source Class
==============================
.. autoclass:: EnvironmentConfigurationSource
"""

import os

# Avoid circular import
import oslo_config.cfg
from oslo_config import sources


# In current practice this class is not used because the
# EnvironmentConfigurationSource is loaded by default, but we keep it
# here in case we choose to change that behavior in the future.
class EnvironmentConfigurationSourceDriver(sources.ConfigurationSourceDriver):
"""A backend driver for environment variables.
This configuration source is available by default and does not need special
configuration to use. The sample config is generated automatically but is
not necessary.
"""

def list_options_for_discovery(self):
"""There are no options for this driver."""
return []

def open_source_from_opt_group(self, conf, group_name):
return EnvironmentConfigurationSource()


class EnvironmentConfigurationSource(sources.ConfigurationSource):
"""A configuration source for options in the environment."""

@staticmethod
def get_name(group_name, option_name):
"""Return the expected environment variable name for the given option.
:param group_name: The group name or None. Defaults to 'DEFAULT' if
None.
:param option_name: The option name.
:returns: Th expected environment variable name.
"""
group_name = group_name or 'DEFAULT'
return 'OS_{}__{}'.format(group_name.upper(), option_name.upper())

def get(self, group_name, option_name, opt):
env_name = self.get_name(group_name, option_name)
try:
value = os.environ[env_name]
loc = oslo_config.cfg.LocationInfo(
oslo_config.cfg.Locations.environment, env_name)
return (value, loc)
except KeyError:
return (sources._NoValue, None)
181 changes: 181 additions & 0 deletions oslo_config/sources/_uri.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# 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"""
Remote File
-----------
The **remote_file** backend driver is the first driver implemented by
oslo.config. It extends the previous limit of only accessing local files
to a new scenario where it is possible to access configuration data over
the network. The **remote_file** driver is based on the **requests** module
and is capable of accessing remote files through **HTTP** or **HTTPS**.
To definition of a remote_file configuration data source can be as minimal as::
[DEFAULT]
config_source = external_config_group
[external_config_group]
driver = remote_file
uri = http://mydomain.com/path/to/config/data.conf
Or as complete as::
[DEFAULT]
config_source = external_config_group
[external_config_group]
driver = remote_file
uri = https://mydomain.com/path/to/config/data.conf
ca_path = /path/to/server/ca.pem
client_key = /path/to/my/key.pem
client_cert = /path/to/my/cert.pem
On the following sessions, you can find more information about this driver's
classes and its options.
The Driver Class
================
.. autoclass:: URIConfigurationSourceDriver
The Configuration Source Class
==============================
.. autoclass:: URIConfigurationSource
"""

import requests
import tempfile

from oslo_config import cfg
from oslo_config import sources


class URIConfigurationSourceDriver(sources.ConfigurationSourceDriver):
"""A backend driver for remote files served through http[s].
Required options:
- uri: URI containing the file location.
Non-required options:
- ca_path: The path to a CA_BUNDLE file or directory with
certificates of trusted CAs.
- client_cert: Client side certificate, as a single file path
containing either the certificate only or the
private key and the certificate.
- client_key: Client side private key, in case client_cert is
specified but does not includes the private key.
"""

_uri_driver_opts = [
cfg.URIOpt(
'uri',
schemes=['http', 'https'],
required=True,
sample_default='https://example.com/my-configuration.ini',
help=('Required option with the URI of the '
'extra configuration file\'s location.'),
),
cfg.StrOpt(
'ca_path',
sample_default='/etc/ca-certificates',
help=('The path to a CA_BUNDLE file or directory '
'with certificates of trusted CAs.'),
),
cfg.StrOpt(
'client_cert',
sample_default='/etc/ca-certificates/service-client-keystore',
help=('Client side certificate, as a single file path '
'containing either the certificate only or the '
'private key and the certificate.'),
),
cfg.StrOpt(
'client_key',
help=('Client side private key, in case client_cert is '
'specified but does not includes the private key.'),
),
cfg.StrOpt(
'timeout',
default=60,
help=('Timeout is the number of seconds the request will wait '
'for your client to establish a connection to a remote '
'machine call on the socket.'),
),
]

def list_options_for_discovery(self):
return self._uri_driver_opts

def open_source_from_opt_group(self, conf, group_name):
conf.register_opts(self._uri_driver_opts, group_name)

return URIConfigurationSource(
conf[group_name].uri,
conf[group_name].ca_path,
conf[group_name].client_cert,
conf[group_name].client_key,
conf[group_name].timeout)


class URIConfigurationSource(sources.ConfigurationSource):
"""A configuration source for remote files served through http[s].
:param uri: The Uniform Resource Identifier of the configuration to be
retrieved.
:param ca_path: The path to a CA_BUNDLE file or directory with
certificates of trusted CAs.
:param client_cert: Client side certificate, as a single file path
containing either the certificate only or the
private key and the certificate.
:param client_key: Client side private key, in case client_cert is
specified but does not includes the private key.
"""

def __init__(self, uri, ca_path=None, client_cert=None, client_key=None,
timeout=60):
self._uri = uri
self._namespace = cfg._Namespace(cfg.ConfigOpts())

data = self._fetch_uri(uri, ca_path, client_cert, client_key, timeout)

with tempfile.NamedTemporaryFile() as tmpfile:
tmpfile.write(data.encode("utf-8"))
tmpfile.flush()

cfg.ConfigParser._parse_file(tmpfile.name, self._namespace)

def _fetch_uri(self, uri, ca_path, client_cert, client_key,
timeout):
verify = ca_path if ca_path else True
cert = (client_cert, client_key) if client_cert and client_key else \
client_cert

with requests.get(uri, verify=verify, cert=cert,
timeout=timeout) as response:
response.raise_for_status() # raises only in case of HTTPError

return response.text

def get(self, group_name, option_name, opt):
try:
return self._namespace._get_value(
[(group_name, option_name)],
multi=opt.multi)
except KeyError:
return (sources._NoValue, None)
51 changes: 51 additions & 0 deletions oslo_config/sources/templates/bash-completion.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/bin/bash completion for {scriptname}

_{scriptname}(){{
local cur prev
local -A ARGS MAP FORCE OPTS OPTS_SUB MULTI

COMPREPLY=()
cur="${{COMP_WORDS[COMP_CWORD]}}"
prev="${{COMP_WORDS[COMP_CWORD-1]}}"

OPTS=({opts})
OPTS_SUB=({opts_sub})
ARGS=({args})
MAP=({map})
MULTI=({multi})

if [ ! -z "$prev" ]; then
# if is an argument complete with list of choice if define
prev_key=${{MAP[$prev]}}
if [ ! -z $prev_key ] && [ ! -z "${{ARGS[$prev_key]}}" ]; then
COMPREPLY=($(compgen -W "${{ARGS[$prev_key]}}" -- "${{cur}}"))
return 0
fi
fi
for in_use in ${{COMP_WORDS[@]:1}}; do
key=${{MAP[$in_use]}}
IFS='|'
if [[ -v OPTS_SUB[$key] ]];then
# If is a subcommand redefine completion
unset OPTS
local -A OPTS
for el in ${{OPTS_SUB[$key]}}; do
IFS='='
read k v <<< ${{el}}
IFS='|'
OPTS+=( [${{k}}]="${{v}}" )
done
fi
unset IFS
# Unset option that is already use
if [[ -z "MULTI[$key]" ]]; then
unset OPTS[$key]
unset ARGS[$key]
fi
done
compl="${{OPTS[@]}}"
COMPREPLY=($(compgen -W "${{compl}}" -- "${{cur}}"))
return 0
}}

complete -F _{scriptname} {scriptname}
27 changes: 27 additions & 0 deletions oslo_config/sources/templates/zsh-completion.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#compdef _{scriptname} {scriptname}

_{scriptname}_commands(){{
#Script used only if subcommand
local -a _{scriptname}_cmds

# Add subcommands list
_{scriptname}_cmds=(
{commands_list}
)

if (( CURRENT == 1 )); then
_describe -t commands '{scriptname} command' _{scriptname}_cmds || compadd "$@"
else
local curcontext="$curcontext"
#Check if subcommand and redefine completion
case "$words[1]" in
{commands_opts}
esac
fi
}}

_{scriptname}(){{
local curcontext="$curcontext" state line
_arguments -s \
{opts}
}}
14 changes: 11 additions & 3 deletions oslo_config/sphinxconfiggen.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,18 @@

import os

from sphinx.util import logging

from oslo_config import generator

LOG = logging.getLogger(__name__)


def generate_sample(app):

if not app.config.config_generator_config_file:
app.warn("No config_generator_config_file is specified, "
"skipping sample config generation")
LOG.warning("No config_generator_config_file is specified, "
"skipping sample config generation")
return

# Decided to update the existing config option
Expand Down Expand Up @@ -52,7 +56,7 @@ def _get_default_basename(config_file):
def _generate_sample(app, config_file, base_name):

def info(msg):
app.info('[%s] %s' % (__name__, msg))
LOG.info('[%s] %s' % (__name__, msg))

# If we are given a file that isn't an absolute path, look for it
# in the source directory if it doesn't exist.
Expand Down Expand Up @@ -87,3 +91,7 @@ def setup(app):
app.add_config_value('config_generator_config_file', None, 'env')
app.add_config_value('sample_config_basename', None, 'env')
app.connect('builder-inited', generate_sample)
return {
'parallel_read_safe': True,
'parallel_write_safe': True,
}
Loading