# 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.

"""
The :class:`~openstack.connection.Connection` class is the primary interface
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.

While the overall system is very flexible, there are four main use cases
for different ways to create a :class:`~openstack.connection.Connection`.

* 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 constructor:

.. 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(
        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 oslo.conf CONF object
--------------------------

For applications that have an oslo.config ``CONF`` object that has been
populated with ``keystoneauth1.loading.register_adapter_conf_options`` in
groups named by the OpenStack service's project name, it is possible to
construct a Connection with the ``CONF`` object and an authenticated Session.

.. note::

    This is primarily intended for use by OpenStack services to talk amongst
    themselves.

.. code-block:: python

    from openstack import connection

    conn = connection.Connection(
        session=session,
        oslo_config=CONF)

From existing CloudRegion
-------------------------

If you already have an :class:`~openstack.config.cloud_region.CloudRegion`
you can pass it in instead:

.. code-block:: python

    from openstack import connection
    import openstack.config

    config = openstack.config.get_cloud_region(
        cloud='example', region_name='earth')
    conn = connection.Connection(config=config)

Using the Connection
--------------------

Services are accessed through an attribute named after the service's official
service-type.

List
~~~~

An iterator containing a list of all the projects is retrieved in this manner:

.. 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")

Additional information about the services can be found in the
:ref:`service-proxies` documentation.
"""
import warnings

import keystoneauth1.exceptions
import requestsexceptions
import six

from openstack import _log
from openstack._meta import connection as _meta
from openstack.cloud import openstackcloud as _cloud
from openstack.cloud import _baremetal
from openstack.cloud import _block_storage
from openstack.cloud import _compute
from openstack.cloud import _clustering
from openstack.cloud import _coe
from openstack.cloud import _dns
from openstack.cloud import _floating_ip
from openstack.cloud import _identity
from openstack.cloud import _image
from openstack.cloud import _network
from openstack.cloud import _network_common
from openstack.cloud import _object_store
from openstack.cloud import _orchestration
from openstack.cloud import _security_group
from openstack import config as _config
from openstack.config import cloud_region
from openstack import exceptions
from openstack import service_description

__all__ = [
    'from_config',
    'Connection',
]

if requestsexceptions.SubjectAltNameWarning:
    warnings.filterwarnings(
        'ignore', category=requestsexceptions.SubjectAltNameWarning)

_logger = _log.setup_logging('openstack')


def from_config(cloud=None, config=None, options=None, **kwargs):
    """Create a Connection using openstack.config

    :param str cloud:
        Use the `cloud` configuration details when creating the Connection.
    :param openstack.config.cloud_region.CloudRegion config:
        An existing CloudRegion configuration. If no `config` is provided,
        `openstack.config.OpenStackConfig` will be called, and the provided
        `name` will be used in determining which cloud's configuration
        details will be used in creation of the `Connection` instance.
    :param argparse.Namespace options:
        Allows direct passing in of options to be added to the cloud config.
        This does not have to be an actual instance of argparse.Namespace,
        despite the naming of the
        `openstack.config.loader.OpenStackConfig.get_one` argument to which
        it is passed.

    :rtype: :class:`~openstack.connection.Connection`
    """
    # TODO(mordred) Backwards compat while we transition
    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, **kwargs)

    return Connection(config=config)


class Connection(six.with_metaclass(_meta.ConnectionMeta,
                                    _cloud._OpenStackCloudMixin,
                                    _baremetal.BaremetalCloudMixin,
                                    _block_storage.BlockStorageCloudMixin,
                                    _compute.ComputeCloudMixin,
                                    _clustering.ClusteringCloudMixin,
                                    _coe.CoeCloudMixin,
                                    _dns.DnsCloudMixin,
                                    _floating_ip.FloatingIPCloudMixin,
                                    _identity.IdentityCloudMixin,
                                    _image.ImageCloudMixin,
                                    _network.NetworkCloudMixin,
                                    _network_common.NetworkCommonCloudMixin,
                                    _object_store.ObjectStoreCloudMixin,
                                    _orchestration.OrchestrationCloudMixin,
                                    _security_group.SecurityGroupCloudMixin
                                    )):

    def __init__(self, cloud=None, config=None, session=None,
                 app_name=None, app_version=None,
                 extra_services=None,
                 strict=False,
                 use_direct_get=False,
                 task_manager=None,
                 rate_limit=None,
                 oslo_conf=None,
                 service_types=None,
                 global_request_id=None,
                 **kwargs):
        """Create a connection to a cloud.

        A connection needs information about how to connect, how to
        authenticate and how to select the appropriate services to use.

        The recommended way to provide this information is by referencing
        a named cloud config from an existing `clouds.yaml` file. The cloud
        name ``envvars`` may be used to consume a cloud configured via ``OS_``
        environment variables.

        A pre-existing :class:`~openstack.config.cloud_region.CloudRegion`
        object can be passed in lieu of a cloud name, for cases where the user
        already has a fully formed CloudRegion and just wants to use it.

        Similarly, if for some reason the user already has a
        :class:`~keystoneauth1.session.Session` and wants to use it, it may be