Ensure Connection can be made from keyword arguments

Although driving configuration from clouds.yaml and environment
variables is super handy for many use cases, there are also plenty where
avoiding config files or environment variables is important.

Add a flag, "load_envvars" which defaults to True but can be disabled.

Update the Connection constructor to set load_yaml_config and
load_envvars to False if a named cloud is not given.

Update the docs to make it clear which form should be used for which use
case. Also, move the profile backwards compat documentation to its own
document so that it doesn't present needless complexity to users.

Cherry-picked from I6e05da5e73aff4143550e1d18fb0f743d51f2b70

Change-Id: If8a8746a55b7ef5a3ff36bebd99fb1b600049a5c
This commit is contained in:
Monty Taylor 2018-01-30 06:50:18 -06:00
parent 14328ef14d
commit 740dbc12c9
No known key found for this signature in database
GPG Key ID: 7BAE94BC7141A594
10 changed files with 457 additions and 281 deletions

View File

@ -1,17 +1,19 @@
===========================================
Configuring os-client-config Applications
===========================================
.. _openstack-config:
========================================
Configuring OpenStack SDK Applications
========================================
.. _config-environment-variables:
Environment Variables
---------------------
`os-client-config` honors all of the normal `OS_*` variables. It does not
`openstacksdk` honors all of the normal `OS_*` variables. It does not
provide backwards compatibility to service-specific variables such as
`NOVA_USERNAME`.
If you have OpenStack environment variables set, `os-client-config` will
If you have OpenStack environment variables set, `openstacksdk` will
produce a cloud config object named `envvars` containing your values from the
environment. If you don't like the name `envvars`, that's ok, you can override
it by setting `OS_CLOUD_NAME`.
@ -29,7 +31,7 @@ for trove set
Config Files
------------
`os-client-config` will look for a file called `clouds.yaml` in the following
`openstacksdk` will look for a file called `clouds.yaml` in the following
locations:
* Current Directory
@ -58,7 +60,7 @@ Site Specific File Locations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In addition to `~/.config/openstack` and `/etc/openstack` - some platforms
have other locations they like to put things. `os-client-config` will also
have other locations they like to put things. `openstacksdk` will also
look in an OS specific config dir
* `USER_CONFIG_DIR`
@ -111,7 +113,7 @@ You may note a few things. First, since `auth_url` settings are silly
and embarrassingly ugly, known cloud vendor profile information is included and
may be referenced by name. One of the benefits of that is that `auth_url`
isn't the only thing the vendor defaults contain. For instance, since
Rackspace lists `rax:database` as the service type for trove, `os-client-config`
Rackspace lists `rax:database` as the service type for trove, `openstacksdk`
knows that so that you don't have to. In case the cloud vendor profile is not
available, you can provide one called `clouds-public.yaml`, following the same
location rules previously mentioned for the config files.
@ -129,7 +131,7 @@ Auth Settings
-------------
Keystone has auth plugins - which means it's not possible to know ahead of time
which auth settings are needed. `os-client-config` sets the default plugin type
which auth settings are needed. `openstacksdk` sets the default plugin type
to `password`, which is what things all were before plugins came about. In
order to facilitate validation of values, all of the parameters that exist
as a result of a chosen plugin need to go into the auth dict. For password
@ -167,7 +169,7 @@ file.
SSL Settings
------------
When the access to a cloud is done via a secure connection, `os-client-config`
When the access to a cloud is done via a secure connection, `openstacksdk`
will always verify the SSL cert by default. This can be disabled by setting
`verify` to `False`. In case the cert is signed by an unknown CA, a specific
cacert can be provided via `cacert`. **WARNING:** `verify` will always have
@ -195,7 +197,7 @@ Cache Settings
--------------
Accessing a cloud is often expensive, so it's quite common to want to do some
client-side caching of those operations. To facilitate that, `os-client-config`
client-side caching of those operations. To facilitate that, `openstacksdk`
understands passing through cache settings to dogpile.cache, with the following
behaviors:
@ -209,7 +211,7 @@ times on a per-resource basis by passing values, in seconds to an expiration
mapping keyed on the singular name of the resource. A value of `-1` indicates
that the resource should never expire.
`os-client-config` does not actually cache anything itself, but it collects
`openstacksdk` does not actually cache anything itself, but it collects
and presents the cache information so that your various applications that
are connecting to OpenStack can share a cache should you desire.

View File

@ -13,184 +13,13 @@ Connection Object
:members:
Transition from Profile
=======================
Transitioning from Profile
--------------------------
.. note:: This section describes migrating code from a previous interface of
python-openstacksdk and can be ignored by people writing new code.
Support exists for users coming from older releases of OpenStack SDK who have
been using the :class:`~openstack.profile.Profile` interface.
If you have code that currently uses the ``openstack.profile.Profile`` object
and/or an ``authenticator`` instance from an object based on
``openstack.auth.base.BaseAuthPlugin``, that code should be updated to use the
`openstack.config.cloud_region.CloudRegion` object instead.
.. toctree::
:maxdepth: 1
Writing Code that Works with Both
---------------------------------
These examples should all work with both the old and new interface, with one
caveat. With the old interface, the ``CloudConfig`` object comes from the
``os-client-config`` library, and in the new interface that has been moved
into the SDK. In order to write code that works with both the old and new
interfaces, use the following code to import the config namespace:
.. code-block:: python
try:
from openstack import config as occ
except ImportError:
from os_client_config import config as occ
The examples will assume that the config module has been imported in that
manner.
.. note:: Yes, there is an easier and less verbose way to do all of these.
These are verbose to handle both the old and new interfaces in the
same codebase.
Replacing authenticator
-----------------------
There is no direct replacement for ``openstack.auth.base.BaseAuthPlugin``.
``python-openstacksdk`` uses the `keystoneauth`_ library for authentication
and HTTP interactions. `keystoneauth`_ has `auth plugins`_ that can be used
to control how authentication is done. The ``auth_type`` config parameter
can be set to choose the correct authentication method to be used.
Replacing Profile
-----------------
The right way to replace the use of ``openstack.profile.Profile`` depends
a bit on what you're trying to accomplish. Common patterns are listed below,
but in general the approach is either to pass a cloud name to the
`openstack.connection.Connection` constructor, or to construct a
`openstack.config.cloud_region.CloudRegion` object and pass it to the
constructor.
All of the examples on this page assume that you want to support old and
new interfaces simultaneously. There are easier and less verbose versions
of each that are available if you can just make a clean transition.
Getting a Connection to a named cloud from clouds.yaml
------------------------------------------------------
If you want is to construct a `openstack.connection.Connection` based on
parameters configured in a ``clouds.yaml`` file, or from environment variables:
.. code-block:: python
import openstack.connection
conn = connection.from_config(cloud_name='name-of-cloud-you-want')
Getting a Connection from python arguments avoiding clouds.yaml
---------------------------------------------------------------
If, on the other hand, you want to construct a
`openstack.connection.Connection`, but are in a context where reading config
from a clouds.yaml file is undesirable, such as inside of a Service:
* create a `openstack.config.loader.OpenStackConfig` object, telling
it to not load yaml files. Optionally pass an ``app_name`` and
``app_version`` which will be added to user-agent strings.
* get a `openstack.config.cloud_region.CloudRegion` object from it
* get a `openstack.connection.Connection`
.. code-block:: python
try:
from openstack import config as occ
except ImportError:
from os_client_config import config as occ
from openstack import connection
loader = occ.OpenStackConfig(
load_yaml_files=False,
app_name='spectacular-app',
app_version='1.0')
cloud_region = loader.get_one_cloud(
region_name='my-awesome-region',
auth_type='password',
auth=dict(
auth_url='https://auth.example.com',
username='amazing-user',
user_domain_name='example-domain',
project_name='astounding-project',
user_project_name='example-domain',
password='super-secret-password',
))
conn = connection.from_config(cloud_config=cloud_region)
.. note:: app_name and app_version are completely optional, and auth_type
defaults to 'password'. They are shown here for clarity as to
where they should go if they want to be set.
Getting a Connection from python arguments and optionally clouds.yaml
---------------------------------------------------------------------
If you want to make a connection from python arguments and want to allow
one of them to optionally be ``cloud`` to allow selection of a named cloud,
it's essentially the same as the previous example, except without
``load_yaml_files=False``.
.. code-block:: python
try:
from openstack import config as occ
except ImportError:
from os_client_config import config as occ
from openstack import connection
loader = occ.OpenStackConfig(
app_name='spectacular-app',
app_version='1.0')
cloud_region = loader.get_one_cloud(
region_name='my-awesome-region',
auth_type='password',
auth=dict(
auth_url='https://auth.example.com',
username='amazing-user',
user_domain_name='example-domain',
project_name='astounding-project',
user_project_name='example-domain',
password='super-secret-password',
))
conn = connection.from_config(cloud_config=cloud_region)
Parameters to get_one_cloud
---------------------------
The most important things to note are:
* ``auth_type`` specifies which kind of authentication plugin to use. It
controls how authentication is done, as well as what parameters are required.
* ``auth`` is a dictionary containing the parameters needed by the auth plugin.
The most common information it needs are user, project, domain, auth_url
and password.
* The rest of the keyword arguments to
``openstack.config.loader.OpenStackConfig.get_one_cloud`` are either
parameters needed by the `keystoneauth Session`_ object, which control how
HTTP connections are made, or parameters needed by the
`keystoneauth Adapter`_ object, which control how services are found in the
Keystone Catalog.
For `keystoneauth Adapter`_ parameters, since there is one
`openstack.connection.Connection` object but many services, per-service
parameters are formed by using the official ``service_type`` of the service
in question. For instance, to override the endpoint for the ``compute``
service, the parameter ``compute_endpoint_override`` would be used.
``region_name`` in ``openstack.profile.Profile`` was a per-service parameter.
This is no longer a valid concept. An `openstack.connection.Connection` is a
connection to a region of a cloud. If you are in an extreme situation where
you have one service in one region and a different service in a different
region, you must use two different `openstack.connection.Connection` objects.
.. note:: service_type, although a parameter for keystoneauth1.adapter.Adapter,
is not a valid parameter for get_one_cloud. service_type is the key
by which services are referred, so saying
'compute_service_type="henry"' doesn't have any meaning.
.. _keystoneauth: https://docs.openstack.org/keystoneauth/latest/
.. _auth plugins: https://docs.openstack.org/keystoneauth/latest/authentication-plugins.html
.. _keystoneauth Adapter: https://docs.openstack.org/keystoneauth/latest/api/keystoneauth1.html#keystoneauth1.adapter.Adapter
.. _keystoneauth Session: https://docs.openstack.org/keystoneauth/latest/api/keystoneauth1.html#keystoneauth1.session.Session
transition_from_profile

View File

@ -48,10 +48,11 @@ API Documentation
-----------------
Service APIs are exposed through a two-layered approach. The classes
exposed through our *Connection* interface are the place to start if you're
an application developer consuming an OpenStack cloud. The *Resource*
interface is the layer upon which the *Connection* is built, with
*Connection* methods accepting and returning *Resource* objects.
exposed through our `Connection Interface`_ are
the place to start if you're an application developer consuming an OpenStack
cloud. The `Resource Interface`_ is the layer upon which the
`Connection Interface`_ is built, with methods on `Service Proxies`_ accepting
and returning :class:`~openstack.resource.Resource` objects.
The Cloud Abstraction layer has a data model.
@ -61,26 +62,35 @@ The Cloud Abstraction layer has a data model.
model
Connection Interface
********************
~~~~~~~~~~~~~~~~~~~~
A *Connection* instance maintains your cloud config, session and authentication
information providing you with a set of higher-level interfaces to work with
OpenStack services.
A :class:`~openstack.connection.Connection` instance maintains your cloud
config, session and authentication information providing you with a set of
higher-level interfaces to work with OpenStack services.
.. toctree::
:maxdepth: 1
connection
Once you have a *Connection* instance, the following services may be exposed
to you via the :class:`~openstack.proxy.BaseProxy` interface.
Once you have a :class:`~openstack.connection.Connection` instance, services
are accessed through instances of :class:`~openstack.proxy.BaseProxy` or
subclasses of it that exist as attributes on the
:class:`~openstack.connection.Connection`.
.. autoclass:: openstack.proxy.BaseProxy
:members:
The combination of your ``CloudRegion`` and the catalog of the cloud
in question control which services are exposed, but listed below are the ones
provided by the SDK.
.. _service-proxies:
Service Proxies
~~~~~~~~~~~~~~~
The following service proxies exist on the
:class:`~openstack.connection.Connection`. The service proxies are all always
present on the :class:`~openstack.connection.Connection` object, but the
combination of your ``CloudRegion`` and the catalog of the cloud in question
control which services can be used.
.. toctree::
:maxdepth: 1
@ -103,16 +113,19 @@ provided by the SDK.
Workflow <proxies/workflow>
Resource Interface
******************
~~~~~~~~~~~~~~~~~~
The *Resource* layer is a lower-level interface to communicate with OpenStack
services. While the classes exposed by the *Connection* build a convenience
layer on top of this, *Resources* can be used directly. However, the most
common usage of this layer is in receiving an object from a class in the
*Connection* layer, modifying it, and sending it back into the *Connection*
layer, such as to update a resource on the server.
The *Resource* layer is a lower-level interface to
communicate with OpenStack services. While the classes exposed by the
`Service Proxies`_ build a convenience layer on top of
this, :class:`~openstack.resource.Resource` objects can be
used directly. However, the most common usage of this layer is in receiving
an object from a class in the `Connection Interface_`, modifying it, and
sending it back to the `Service Proxies`_ layer, such as to update a resource
on the server.
The following services have exposed *Resource* classes.
The following services have exposed :class:`~openstack.resource.Resource`
classes.
.. toctree::
:maxdepth: 1
@ -132,7 +145,7 @@ The following services have exposed *Resource* classes.
Workflow <resources/workflow/index>
Low-Level Classes
*****************
~~~~~~~~~~~~~~~~~
The following classes are not commonly used by application developers,
but are used to construct applications to talk to OpenStack APIs. Typically

View File

@ -0,0 +1,186 @@
Transition from Profile
=======================
.. note:: This section describes migrating code from a previous interface of
python-openstacksdk and can be ignored by people writing new code.
If you have code that currently uses the :class:`~openstack.profile.Profile`
object and/or an ``authenticator`` instance from an object based on
``openstack.auth.base.BaseAuthPlugin``, that code should be updated to use the
:class:`~openstack.config.cloud_region.CloudRegion` object instead.
.. important::
:class:`~openstack.profile.Profile` is going away. Existing code using it
should be migrated as soon as possible.
Writing Code that Works with Both
---------------------------------
These examples should all work with both the old and new interface, with one
caveat. With the old interface, the ``CloudConfig`` object comes from the
``os-client-config`` library, and in the new interface that has been moved
into the SDK. In order to write code that works with both the old and new
interfaces, use the following code to import the config namespace:
.. code-block:: python
try:
from openstack import config as occ
except ImportError:
from os_client_config import config as occ
The examples will assume that the config module has been imported in that
manner.
.. note:: Yes, there is an easier and less verbose way to do all of these.
These are verbose to handle both the old and new interfaces in the
same codebase.
Replacing authenticator
-----------------------
There is no direct replacement for ``openstack.auth.base.BaseAuthPlugin``.
``python-openstacksdk`` uses the `keystoneauth`_ library for authentication
and HTTP interactions. `keystoneauth`_ has `auth plugins`_ that can be used
to control how authentication is done. The ``auth_type`` config parameter
can be set to choose the correct authentication method to be used.
Replacing Profile
-----------------
The right way to replace the use of ``openstack.profile.Profile`` depends
a bit on what you're trying to accomplish. Common patterns are listed below,
but in general the approach is either to pass a cloud name to the
`openstack.connection.Connection` constructor, or to construct a
`openstack.config.cloud_region.CloudRegion` object and pass it to the
constructor.
All of the examples on this page assume that you want to support old and
new interfaces simultaneously. There are easier and less verbose versions
of each that are available if you can just make a clean transition.
Getting a Connection to a named cloud from clouds.yaml
------------------------------------------------------
If you want is to construct a `openstack.connection.Connection` based on
parameters configured in a ``clouds.yaml`` file, or from environment variables:
.. code-block:: python
import openstack.connection
conn = connection.from_config(cloud_name='name-of-cloud-you-want')
Getting a Connection from python arguments avoiding clouds.yaml
---------------------------------------------------------------
If, on the other hand, you want to construct a
`openstack.connection.Connection`, but are in a context where reading config
from a clouds.yaml file is undesirable, such as inside of a Service:
* create a `openstack.config.loader.OpenStackConfig` object, telling
it to not load yaml files. Optionally pass an ``app_name`` and
``app_version`` which will be added to user-agent strings.
* get a `openstack.config.cloud_region.CloudRegion` object from it
* get a `openstack.connection.Connection`
.. code-block:: python
try:
from openstack import config as occ
except ImportError:
from os_client_config import config as occ
from openstack import connection
loader = occ.OpenStackConfig(
load_yaml_files=False,
app_name='spectacular-app',
app_version='1.0')
cloud_region = loader.get_one_cloud(
region_name='my-awesome-region',
auth_type='password',
auth=dict(
auth_url='https://auth.example.com',
username='amazing-user',
user_domain_name='example-domain',
project_name='astounding-project',
user_project_name='example-domain',
password='super-secret-password',
))
conn = connection.from_config(cloud_config=cloud_region)
.. note:: app_name and app_version are completely optional, and auth_type
defaults to 'password'. They are shown here for clarity as to
where they should go if they want to be set.
Getting a Connection from python arguments and optionally clouds.yaml
---------------------------------------------------------------------
If you want to make a connection from python arguments and want to allow
one of them to optionally be ``cloud`` to allow selection of a named cloud,
it's essentially the same as the previous example, except without
``load_yaml_files=False``.
.. code-block:: python
try:
from openstack import config as occ
except ImportError:
from os_client_config import config as occ
from openstack import connection
loader = occ.OpenStackConfig(
app_name='spectacular-app',
app_version='1.0')
cloud_region = loader.get_one_cloud(
region_name='my-awesome-region',
auth_type='password',
auth=dict(
auth_url='https://auth.example.com',
username='amazing-user',
user_domain_name='example-domain',
project_name='astounding-project',
user_project_name='example-domain',
password='super-secret-password',
))
conn = connection.from_config(cloud_config=cloud_region)
Parameters to get_one_cloud
---------------------------
The most important things to note are:
* ``auth_type`` specifies which kind of authentication plugin to use. It
controls how authentication is done, as well as what parameters are required.
* ``auth`` is a dictionary containing the parameters needed by the auth plugin.
The most common information it needs are user, project, domain, auth_url
and password.
* The rest of the keyword arguments to
``openstack.config.loader.OpenStackConfig.get_one_cloud`` are either
parameters needed by the `keystoneauth Session`_ object, which control how
HTTP connections are made, or parameters needed by the
`keystoneauth Adapter`_ object, which control how services are found in the
Keystone Catalog.
For `keystoneauth Adapter`_ parameters, since there is one
`openstack.connection.Connection` object but many services, per-service
parameters are formed by using the official ``service_type`` of the service
in question. For instance, to override the endpoint for the ``compute``
service, the parameter ``compute_endpoint_override`` would be used.
``region_name`` in ``openstack.profile.Profile`` was a per-service parameter.
This is no longer a valid concept. An `openstack.connection.Connection` is a
connection to a region of a cloud. If you are in an extreme situation where
you have one service in one region and a different service in a different
region, you must use two different `openstack.connection.Connection` objects.
.. note:: service_type, although a parameter for keystoneauth1.adapter.Adapter,
is not a valid parameter for get_one_cloud. service_type is the key
by which services are referred, so saying
'compute_service_type="henry"' doesn't have any meaning.
.. _keystoneauth: https://docs.openstack.org/keystoneauth/latest/
.. _auth plugins: https://docs.openstack.org/keystoneauth/latest/authentication-plugins.html
.. _keystoneauth Adapter: https://docs.openstack.org/keystoneauth/latest/api/keystoneauth1.html#keystoneauth1.adapter.Adapter
.. _keystoneauth Session: https://docs.openstack.org/keystoneauth/latest/api/keystoneauth1.html#keystoneauth1.session.Session

View File

@ -18,6 +18,42 @@ __all__ = [
]
from openstack._log import enable_logging # noqa
import openstack.config
import openstack.connection
connect = openstack.connection.Connection
def connect(
cloud=None,
app_name=None, app_version=None,
options=None,
load_yaml_config=True, load_envvars=True,
**kwargs):
"""Create a :class:`~openstack.connection.Connection`
:param string cloud:
The name of the configuration to load from clouds.yaml. Defaults
to 'envvars' which will load
:param argparse.Namespace options:
An argparse Namespace object. allows direct passing in of
argparse options to be added to the cloud config. Values
of None and '' will be removed.
:param bool load_yaml_config:
Whether or not to load config settings from clouds.yaml files.
Defaults to True.
:param bool load_envvars:
Whether or not to load config settings from environment variables.
Defaults to True.
:param kwargs:
Additional configuration options.
:returns: openstack.connnection.Connection
:raises: keystoneauth1.exceptions.MissingRequiredOptions
on missing required auth parameters
"""
cloud_region = openstack.config.get_cloud_region(
cloud=cloud,
app_name=app_name, app_version=app_version,
load_yaml_config=load_yaml_config,
load_envvars=load_envvars,
options=options, **kwargs)
return openstack.connection.Connection(config=cloud_region)

View File

@ -16,23 +16,20 @@ import sys
from openstack.config.loader import OpenStackConfig # noqa
_config = None
def get_cloud_region(
service_key=None, options=None,
app_name=None, app_version=None,
load_yaml_config=True,
load_envvars=True,
**kwargs):
load_yaml_config = kwargs.pop('load_yaml_config', True)
global _config
if not _config:
_config = OpenStackConfig(
load_yaml_config=load_yaml_config,
app_name=app_name, app_version=app_version)
config = OpenStackConfig(
load_yaml_config=load_yaml_config,
app_name=app_name, app_version=app_version)
if options:
_config.register_argparse_arguments(options, sys.argv, service_key)
config.register_argparse_arguments(options, sys.argv, service_key)
parsed_options = options.parse_known_args(sys.argv)
else:
parsed_options = None
return _config.get_one(options=parsed_options, **kwargs)
return config.get_one(options=parsed_options, **kwargs)

View File

@ -35,7 +35,9 @@ def _make_key(key, service_type):
return "_".join([service_type, key])
def from_session(session, name=None, config=None, **kwargs):
def from_session(session, name=None, region_name=None,
force_ipv4=False,
app_name=None, app_version=None, **kwargs):
"""Construct a CloudRegion from an existing `keystoneauth1.session.Session`
When a Session already exists, we don't actually even need to go through
@ -43,20 +45,30 @@ def from_session(session, name=None, config=None, **kwargs):
The only parameters that are really needed are adapter/catalog related.
:param keystoneauth1.session.session session:
An existing Session to use.
An existing authenticated Session to use.
:param str name:
A name to use for this cloud region in logging. If left empty, the
hostname of the auth_url found in the Session will be used.
:param dict config:
:param str region_name:
The region name to connect to.
:param bool force_ipv4:
Whether or not to disable IPv6 support. Defaults to False.
:param str app_name:
Name of the application to be added to User Agent.
:param str app_version:
Version of the application to be added to User Agent.
:param kwargs:
Config settings for this cloud region.
"""
# If someone is constructing one of these from a Session, then they are
# not using a named config. Use the hostname of their auth_url instead.
name = name or urllib.parse.urlparse(session.auth.auth_url).hostname
config_dict = config_defaults.get_defaults()
config_dict.update(config or {})
config_dict.update(**kwargs)
return CloudRegion(
name=name, session=session, config=config_dict, **kwargs)
name=name, session=session, config=config_dict,
region_name=region_name, force_ipv4=force_ipv4,
app_name=app_name, app_version=app_version)
class CloudRegion(object):

View File

@ -183,11 +183,12 @@ class OpenStackConfig(object):
envvar_prefix=None, secure_files=None,
pw_func=None, session_constructor=None,
app_name=None, app_version=None,
load_yaml_config=True):
load_yaml_config=True, load_envvars=True):
self.log = _log.setup_logging('openstack.config')
self._session_constructor = session_constructor
self._app_name = app_name
self._app_version = app_version
self._load_envvars = load_envvars
if load_yaml_config:
self._config_files = config_files or CONFIG_FILES
@ -198,11 +199,11 @@ class OpenStackConfig(object):
self._secure_files = []
self._vendor_files = []
config_file_override = os.environ.get('OS_CLIENT_CONFIG_FILE')
config_file_override = self._get_envvar('OS_CLIENT_CONFIG_FILE')
if config_file_override:
self._config_files.insert(0, config_file_override)
secure_file_override = os.environ.get('OS_CLIENT_SECURE_FILE')
secure_file_override = self._get_envvar('OS_CLIENT_SECURE_FILE')
if secure_file_override:
self._secure_files.insert(0, secure_file_override)
@ -231,12 +232,12 @@ class OpenStackConfig(object):
else:
# Get the backwards compat value
prefer_ipv6 = get_boolean(
os.environ.get(
self._get_envvar(
'OS_PREFER_IPV6', client_config.get(
'prefer_ipv6', client_config.get(
'prefer-ipv6', True))))
force_ipv4 = get_boolean(
os.environ.get(
self._get_envvar(
'OS_FORCE_IPV4', client_config.get(
'force_ipv4', client_config.get(
'broken-ipv6', False))))
@ -248,7 +249,7 @@ class OpenStackConfig(object):
self.force_ipv4 = True
# Next, process environment variables and add them to the mix
self.envvar_key = os.environ.get('OS_CLOUD_NAME', 'envvars')
self.envvar_key = self._get_envvar('OS_CLOUD_NAME', 'envvars')
if self.envvar_key in self.cloud_config['clouds']:
raise exceptions.OpenStackConfigException(
'"{0}" defines a cloud named "{1}", but'
@ -257,13 +258,14 @@ class OpenStackConfig(object):
' file-based clouds.'.format(self.config_filename,
self.envvar_key))
self.default_cloud = os.environ.get('OS_CLOUD')
self.default_cloud = self._get_envvar('OS_CLOUD')
envvars = _get_os_environ(envvar_prefix=envvar_prefix)
if envvars:
self.cloud_config['clouds'][self.envvar_key] = envvars
if not self.default_cloud:
self.default_cloud = self.envvar_key
if load_envvars:
envvars = _get_os_environ(envvar_prefix=envvar_prefix)
if envvars:
self.cloud_config['clouds'][self.envvar_key] = envvars
if not self.default_cloud:
self.default_cloud = self.envvar_key
if not self.default_cloud and self.cloud_config['clouds']:
if len(self.cloud_config['clouds'].keys()) == 1:
@ -320,6 +322,11 @@ class OpenStackConfig(object):
# password = self._pw_callback(prompt="Password: ")
self._pw_callback = pw_func
def _get_envvar(self, key, default=None):
if not self._load_envvars:
return default
return os.environ.get(key, default)
def get_extra_config(self, key, defaults=None):
"""Fetch an arbitrary extra chunk of config, laying in defaults.
@ -700,7 +707,7 @@ class OpenStackConfig(object):
p.add_argument(
'--os-cloud',
metavar='<name>',
default=os.environ.get('OS_CLOUD', None),
default=self._get_envvar('OS_CLOUD', None),
help='Named cloud to connect to')
# we need to peek to see if timeout was actually passed, since

View File

@ -12,67 +12,148 @@
"""
The :class:`~openstack.connection.Connection` class is the primary interface
to the Python SDK it maintains a context for a connection to a cloud provider.
The connection has an attribute to access each supported service.
Examples
--------
to the Python SDK. It maintains a context for a connection to a region of
a cloud provider. The :class:`~openstack.connection.Connection` has an
attribute to access each OpenStack service.
At a minimum, the :class:`~openstack.connection.Connection` class needs to be
created with a config or the parameters to build one.
Create a connection
~~~~~~~~~~~~~~~~~~~
While the overall system is very flexible, there are four main use cases
for different ways to create a :class:`~openstack.connection.Connection`.
The preferred way to create a connection is to manage named configuration
settings in your clouds.yaml file and refer to them by name.::
* Using config settings and keyword arguments as described in
:ref:`openstack-config`
* Using only keyword arguments passed to the constructor ignoring config files
and environment variables.
* Using an existing authenticated `keystoneauth1.session.Session`, such as
might exist inside of an OpenStack service operational context.
* Using an existing :class:`~openstack.config.cloud_region.CloudRegion`.
Using config settings
---------------------
For users who want to create a :class:`~openstack.connection.Connection` making
use of named clouds in ``clouds.yaml`` files, ``OS_`` environment variables
and python keyword arguments, the :func:`openstack.connect` factory function
is the recommended way to go:
.. code-block:: python
import openstack
conn = openstack.connect(cloud='example', region_name='earth1')
If the application in question is a command line application that should also
accept command line arguments, an `argparse.Namespace` can be passed to
:func:`openstack.connect` that will have relevant arguments added to it and
then subsequently consumed by the construtor:
.. code-block:: python
import argparse
import openstack
options = argparse.ArgumentParser(description='Awesome OpenStack App')
conn = openstack.connect(options=options)
Using Only Keyword Arguments
----------------------------
If the application wants to avoid loading any settings from ``clouds.yaml`` or
environment variables, use the :class:`~openstack.connection.Connection`
constructor directly. As long as the ``cloud`` argument is omitted or ``None``,
the :class:`~openstack.connection.Connection` constructor will not load
settings from files or the environment.
.. note::
This is a different default behavior than the :func:`~openstack.connect`
factory function. In :func:`~openstack.connect` if ``cloud`` is omitted
or ``None``, a default cloud will be loaded, defaulting to the ``envvars``
cloud if it exists.
.. code-block:: python
from openstack import connection
conn = connection.Connection(cloud='example', region_name='earth1')
conn = connection.Connection(
region_name='example-region',
auth=dict(
auth_url='https://auth.example.com',
username='amazing-user',
password='super-secret-password',
project_id='33aa1afc-03fe-43b8-8201-4e0d3b4b8ab5',
user_domain_id='054abd68-9ad9-418b-96d3-3437bb376703'),
compute_api_version='2',
identity_interface='internal')
Per-service settings as needed by `keystoneauth1.adapter.Adapter` such as
``api_version``, ``service_name``, and ``interface`` can be set, as seen
above, by prefixing them with the official ``service-type`` name of the
service. ``region_name`` is a setting for the entire
:class:`~openstack.config.cloud_region.CloudRegion` and cannot be set per
service.
From existing authenticated Session
-----------------------------------
For applications that already have an authenticated Session, simply passing
it to the :class:`~openstack.connection.Connection` constructor is all that
is needed:
.. code-block:: python
from openstack import connection
conn = connection.Connection(
session=session,
region_name='example-region',
compute_api_version='2',
identity_interface='internal')
From existing CloudRegion
-------------------------
If you already have an :class:`~openstack.config.cloud_region.CloudRegion`
you can pass it in instead.::
you can pass it in instead:
.. code-block:: python
from openstack import connection
import openstack.config
config = openstack.config.OpenStackConfig.get_one(
config = openstack.config.get_cloud_region(
cloud='example', region_name='earth')
conn = connection.Connection(config=config)
It's also possible to pass in parameters directly if needed. The following
example constructor uses the default identity password auth
plugin and provides a username and password.::
Using the Connection
--------------------
from openstack import connection
auth_args = {
'auth_url': 'http://172.20.1.108:5000/v3',
'project_name': 'admin',
'user_domain_name': 'default',
'project_domain_name': 'default',
'username': 'admin',
'password': 'admin',
}
conn = connection.Connection(**auth_args)
Services are accessed through an attribute named after the service's official
service-type.
List
~~~~
Services are accessed through an attribute named after the service's official
service-type. A list of all the projects is retrieved in this manner::
An iterator containing a list of all the projects is retrieved in this manner:
projects = [project for project in conn.identity.projects()]
.. code-block:: python
projects = conn.identity.projects()
Find or create
~~~~~~~~~~~~~~
If you wanted to make sure you had a network named 'zuul', you would first
try to find it and if that fails, you would create it::
network = conn.network.find_network("zuul")
if network is None:
network = conn.network.create_network({"name": "zuul"})
network = conn.network.create_network(name="zuul")
Additional information about the services can be found in the
:ref:`service-proxies` documentation.
"""
__all__ = [
'from_config',
@ -88,6 +169,7 @@ import six
from openstack import _log
from openstack import _meta
from openstack import config as _config
from openstack.config import cloud_region
from openstack import exceptions
from openstack import service_description
from openstack import task_manager
@ -119,11 +201,11 @@ def from_config(cloud=None, config=None, options=None, **kwargs):
:rtype: :class:`~openstack.connection.Connection`
"""
# TODO(mordred) Backwards compat while we transition
cloud = cloud or kwargs.get('cloud_name')
config = config or kwargs.get('cloud_config')
cloud = kwargs.pop('cloud_name', cloud)
config = kwargs.pop('cloud_config', config)
if config is None:
config = _config.OpenStackConfig().get_one(
cloud=cloud, argparse=options)
cloud=cloud, argparse=options, **kwargs)
return Connection(config=config)
@ -167,11 +249,12 @@ class Connection(six.with_metaclass(_meta.ConnectionMeta)):
User Agent.
:param authenticator: DEPRECATED. Only exists for short-term backwards
compatibility for python-openstackclient while we
transition. See `Transition from Profile`_ for
details.
transition. See :doc:`transition_from_profile`
for details.
:param profile: DEPRECATED. Only exists for short-term backwards
compatibility for python-openstackclient while we
transition. See `Transition from Profile`_ for details.
transition. See :doc:`transition_from_profile`
for details.
:param extra_services: List of
:class:`~openstack.service_description.ServiceDescription`
objects describing services that openstacksdk otherwise does not
@ -193,12 +276,20 @@ class Connection(six.with_metaclass(_meta.ConnectionMeta)):
# python-openstackclient to not use the profile interface.
self.config = openstack.profile._get_config_from_profile(
profile, authenticator, **kwargs)
else:
openstack_config = _config.OpenStackConfig(
elif session:
self.config = cloud_region.from_session(
session=session,
app_name=app_name, app_version=app_version,
load_yaml_config=profile is None)
self.config = openstack_config.get_one(
cloud=cloud, validate=session is None, **kwargs)
load_yaml_config=False,
load_envvars=False,
**kwargs)
else:
self.config = _config.get_cloud_region(
cloud=cloud,
app_name=app_name, app_version=app_version,
load_yaml_config=cloud is not None,
load_envvars=cloud is not None,
**kwargs)
if self.config.name:
tm_name = ':'.join([

View File

@ -78,8 +78,11 @@ class TestConnection(base.RequestsMockTestCase):
def test_session_provided(self):
mock_session = mock.Mock(spec=session.Session)
mock_session.auth = mock.Mock()
mock_session.auth.auth_url = 'https://auth.example.com'
conn = connection.Connection(session=mock_session, cert='cert')
self.assertEqual(mock_session, conn.session)
self.assertEqual('auth.example.com', conn.config.name)
def test_create_session(self):
conn = connection.Connection(cloud='sample')