pax_global_header00006660000000000000000000000064126626425620014525gustar00rootroot0000000000000052 comment=03d5659d8b45b0450a86956147cc2d1f27be66ac os-client-config-1.16.0/000077500000000000000000000000001266264256200147525ustar00rootroot00000000000000os-client-config-1.16.0/.coveragerc000066400000000000000000000002121266264256200170660ustar00rootroot00000000000000[run] branch = True source = os_client_config omit = os_client_config/tests/*,os_client_config/openstack/* [report] ignore_errors = True os-client-config-1.16.0/.gitignore000066400000000000000000000007231266264256200167440ustar00rootroot00000000000000*.py[cod] .venv # C extensions *.so # Packages *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 # Installer logs pip-log.txt # Unit test / coverage reports cover .coverage .tox nosetests.xml .testrepository # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject # Complexity output/*.html output/*/index.html # Sphinx doc/build # pbr generates these AUTHORS ChangeLog # Editors *~ .*.swp .*sw? os-client-config-1.16.0/.gitreview000066400000000000000000000001251266264256200167560ustar00rootroot00000000000000[gerrit] host=review.openstack.org port=29418 project=openstack/os-client-config.git os-client-config-1.16.0/.mailmap000066400000000000000000000001301266264256200163650ustar00rootroot00000000000000# Format is: # # os-client-config-1.16.0/.testr.conf000066400000000000000000000004761266264256200170470ustar00rootroot00000000000000[DEFAULT] test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--listos-client-config-1.16.0/CONTRIBUTING.rst000066400000000000000000000010421266264256200174100ustar00rootroot00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps in this page: http://docs.openstack.org/infra/manual/developers.html Once those steps have been completed, changes to OpenStack should be submitted for review via the Gerrit tool, following the workflow documented at: http://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. Bugs should be filed on Launchpad, not GitHub: https://bugs.launchpad.net/os-client-configos-client-config-1.16.0/HACKING.rst000066400000000000000000000002471266264256200165530ustar00rootroot00000000000000os-client-config Style Commandments =============================================== Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/os-client-config-1.16.0/LICENSE000066400000000000000000000236361266264256200157710ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. os-client-config-1.16.0/MANIFEST.in000066400000000000000000000001351266264256200165070ustar00rootroot00000000000000include AUTHORS include ChangeLog exclude .gitignore exclude .gitreview global-exclude *.pycos-client-config-1.16.0/README.rst000066400000000000000000000314071266264256200164460ustar00rootroot00000000000000================ os-client-config ================ `os-client-config` is a library for collecting client configuration for using an OpenStack cloud in a consistent and comprehensive manner. It will find cloud config for as few as 1 cloud and as many as you want to put in a config file. It will read environment variables and config files, and it also contains some vendor specific default values so that you don't have to know extra info to use OpenStack * If you have a config file, you will get the clouds listed in it * If you have environment variables, you will get a cloud named `envvars` * If you have neither, you will get a cloud named `defaults` with base defaults Environment Variables --------------------- `os-client-config` 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 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`. Service specific settings, like the nova service type, are set with the default service type as a prefix. For instance, to set a special service_type for trove set .. code-block:: bash export OS_DATABASE_SERVICE_TYPE=rax:database Config Files ------------ `os-client-config` will look for a file called `clouds.yaml` in the following locations: * Current Directory * ~/.config/openstack * /etc/openstack The first file found wins. You can also set the environment variable `OS_CLIENT_CONFIG_FILE` to an absolute path of a file to look for and that location will be inserted at the front of the file search list. The keys are all of the keys you'd expect from `OS_*` - except lower case and without the OS prefix. So, region name is set with `region_name`. Service specific settings, like the nova service type, are set with the default service type as a prefix. For instance, to set a special service_type for trove (because you're using Rackspace) set: .. code-block:: yaml database_service_type: 'rax:database' 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 look in an OS specific config dir * `USER_CONFIG_DIR` * `SITE_CONFIG_DIR` `USER_CONFIG_DIR` is different on Linux, OSX and Windows. * Linux: `~/.config/openstack` * OSX: `~/Library/Application Support/openstack` * Windows: `C:\\Users\\USERNAME\\AppData\\Local\\OpenStack\\openstack` `SITE_CONFIG_DIR` is different on Linux, OSX and Windows. * Linux: `/etc/openstack` * OSX: `/Library/Application Support/openstack` * Windows: `C:\\ProgramData\\OpenStack\\openstack` An example config file is probably helpful: .. code-block:: yaml clouds: mtvexx: profile: vexxhost auth: username: mordred@inaugust.com password: XXXXXXXXX project_name: mordred@inaugust.com region_name: ca-ymq-1 dns_api_version: 1 mordred: region_name: RegionOne auth: username: 'mordred' password: XXXXXXX project_name: 'shade' auth_url: 'https://montytaylor-sjc.openstack.blueboxgrid.com:5001/v2.0' infra: profile: rackspace auth: username: openstackci password: XXXXXXXX project_id: 610275 regions: - DFW - ORD - IAD 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` 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. `regions` can be a list of regions. When you call `get_all_clouds`, you'll get a cloud config object for each cloud/region combo. As seen with `dns_service_type`, any setting that makes sense to be per-service, like `service_type` or `endpoint` or `api_version` can be set by prefixing the setting with the default service type. That might strike you funny when setting `service_type` and it does me too - but that's just the world we live in. 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 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 auth, this includes `auth_url`, `username` and `password` as well as anything related to domains, projects and trusts. Splitting Secrets ----------------- In some scenarios, such as configuration management controlled environments, it might be easier to have secrets in one file and non-secrets in another. This is fully supported via an optional file `secure.yaml` which follows all the same location rules as `clouds.yaml`. It can contain anything you put in `clouds.yaml` and will take precedence over anything in the `clouds.yaml` file. .. code-block:: yaml # clouds.yaml clouds: internap: profile: internap auth: username: api-55f9a00fb2619 project_name: inap-17037 regions: - ams01 - nyj01 # secure.yaml clouds: internap: auth: password: XXXXXXXXXXXXXXXXX SSL Settings ------------ When the access to a cloud is done via a secure connection, `os-client-config` 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 precedence over `cacert`, so when setting a CA cert but disabling `verify`, the cloud cert will never be validated. Client certs are also configurable. `cert` will be the client cert file location. In case the cert key is not included within the client cert file, its file location needs to be set via `key`. 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` understands passing through cache settings to dogpile.cache, with the following behaviors: * Listing no config settings means you get a null cache. * `cache.expiration_time` and nothing else gets you memory cache. * Otherwise, `cache.class` and `cache.arguments` are passed in Different cloud behaviors are also differently expensive to deal with. If you want to get really crazy and tweak stuff, you can specify different expiration 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 and presents the cache information so that your various applications that are connecting to OpenStack can share a cache should you desire. .. code-block:: yaml cache: class: dogpile.cache.pylibmc expiration_time: 3600 arguments: url: - 127.0.0.1 expiration: server: 5 flavor: -1 clouds: mtvexx: profile: vexxhost auth: username: mordred@inaugust.com password: XXXXXXXXX project_name: mordred@inaugust.com region_name: ca-ymq-1 dns_api_version: 1 IPv6 ---- IPv6 is the future, and you should always use it if your cloud supports it and if your local network supports it. Both of those are easily detectable and all friendly software should do the right thing. However, sometimes you might exist in a location where you have an IPv6 stack, but something evil has caused it to not actually function. In that case, there is a config option you can set to unbreak you `force_ipv4`, or `OS_FORCE_IPV4` boolean environment variable. .. code-block:: yaml client: force_ipv4: true clouds: mtvexx: profile: vexxhost auth: username: mordred@inaugust.com password: XXXXXXXXX project_name: mordred@inaugust.com region_name: ca-ymq-1 dns_api_version: 1 monty: profile: rax auth: username: mordred@inaugust.com password: XXXXXXXXX project_name: mordred@inaugust.com region_name: DFW The above snippet will tell client programs to prefer returning an IPv4 address. Per-region settings ------------------- Sometimes you have a cloud provider that has config that is common to the cloud, but also with some things you might want to express on a per-region basis. For instance, Internap provides a public and private network specific to the user in each region, and putting the values of those networks into config can make consuming programs more efficient. To support this, the region list can actually be a list of dicts, and any setting that can be set at the cloud level can be overridden for that region. :: clouds: internap: profile: internap auth: password: XXXXXXXXXXXXXXXXX username: api-55f9a00fb2619 project_name: inap-17037 regions: - name: ams01 values: external_network: inap-17037-WAN1654 internal_network: inap-17037-LAN4820 - name: nyj01 values: external_network: inap-17037-WAN7752 internal_network: inap-17037-LAN6745 Usage ----- The simplest and least useful thing you can do is: .. code-block:: python python -m os_client_config.config Which will print out whatever if finds for your config. If you want to use it from python, which is much more likely what you want to do, things like: Get a named cloud. .. code-block:: python import os_client_config cloud_config = os_client_config.OpenStackConfig().get_one_cloud( 'internap', region_name='ams01') print(cloud_config.name, cloud_config.region, cloud_config.config) Or, get all of the clouds. .. code-block:: python import os_client_config cloud_config = os_client_config.OpenStackConfig().get_all_clouds() for cloud in cloud_config: print(cloud.name, cloud.region, cloud.config) argparse -------- If you're using os-client-config from a program that wants to process command line options, there is a registration function to register the arguments that both os-client-config and keystoneauth know how to deal with - as well as a consumption argument. .. code-block:: python import argparse import sys import os_client_config cloud_config = os_client_config.OpenStackConfig() parser = argparse.ArgumentParser() cloud_config.register_argparse_arguments(parser, sys.argv) options = parser.parse_args() cloud = cloud_config.get_one_cloud(argparse=options) Constructing Legacy Client objects ---------------------------------- If all you want to do is get a Client object from a python-\*client library, and you want it to do all the normal things related to clouds.yaml, `OS_` environment variables, a helper function is provided. The following will get you a fully configured `novaclient` instance. .. code-block:: python import os_client_config nova = os_client_config.make_client('compute') If you want to do the same thing but on a named cloud. .. code-block:: python import os_client_config nova = os_client_config.make_client('compute', cloud='mtvexx') If you want to do the same thing but also support command line parsing. .. code-block:: python import argparse import os_client_config nova = os_client_config.make_client( 'compute', options=argparse.ArgumentParser()) If you want to get fancier than that in your python, then the rest of the API is available to you. But often times, you just want to do the one thing. Constructing Mounted Session Objects ------------------------------------ What if you want to make direct REST calls via a Session interface? You're in luck. The same interface for `make_client` is supported for `session_client` and will return you a keystoneauth Session object that is mounted on the endpoint for the service you're looking for. import os_client_config session = os_client_config.session_client('compute', cloud='vexxhost') response = session.get('/servers') server_list = response.json()['servers'] Source ------ * Free software: Apache license * Documentation: http://docs.openstack.org/developer/os-client-config * Source: http://git.openstack.org/cgit/openstack/os-client-config * Bugs: http://bugs.launchpad.net/os-client-config os-client-config-1.16.0/doc/000077500000000000000000000000001266264256200155175ustar00rootroot00000000000000os-client-config-1.16.0/doc/source/000077500000000000000000000000001266264256200170175ustar00rootroot00000000000000os-client-config-1.16.0/doc/source/api-reference.rst000066400000000000000000000003101266264256200222500ustar00rootroot00000000000000============= API Reference ============= .. module:: os_client_config :synopsis: OpenStack client configuration .. autoclass:: os_client_config.OpenStackConfig :members: :inherited-members: os-client-config-1.16.0/doc/source/conf.py000077500000000000000000000046761266264256200203360ustar00rootroot00000000000000# -*- coding: utf-8 -*- # 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 sys 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', #'sphinx.ext.intersphinx', 'oslosphinx', 'reno.sphinxext' ] # autodoc generation is a bit aggressive and a nuisance when doing heavy # text edit cycles. # execute "export SPHINX_DEBUG=1" in your terminal to disable # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u'os-client-config' copyright = u'2015, various OpenStack developers' # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # 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 # 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, u'%s Documentation' % project, u'OpenStack Foundation', 'manual'), ] # Example configuration for intersphinx: refer to the Python standard library. #intersphinx_mapping = {'http://docs.python.org/': None} os-client-config-1.16.0/doc/source/contributing.rst000066400000000000000000000001121266264256200222520ustar00rootroot00000000000000============ Contributing ============ .. include:: ../../CONTRIBUTING.rstos-client-config-1.16.0/doc/source/index.rst000066400000000000000000000003541266264256200206620ustar00rootroot00000000000000.. include:: ../../README.rst .. toctree:: :maxdepth: 2 vendor-support contributing installation api-reference releasenotes Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` os-client-config-1.16.0/doc/source/installation.rst000066400000000000000000000003301266264256200222460ustar00rootroot00000000000000============ Installation ============ At the command line:: $ pip install os-client-config Or, if you have virtualenvwrapper installed:: $ mkvirtualenv os-client-config $ pip install os-client-configos-client-config-1.16.0/doc/source/releasenotes.rst000066400000000000000000000000761266264256200222450ustar00rootroot00000000000000============= Release Notes ============= .. release-notes:: os-client-config-1.16.0/doc/source/vendor-support.rst000066400000000000000000000143701266264256200225650ustar00rootroot00000000000000============== Vendor Support ============== OpenStack presents deployers with many options, some of which can expose differences to end users. `os-client-config` tries its best to collect information about various things a user would need to know. The following is a text representation of the vendor related defaults `os-client-config` knows about. Default Values -------------- These are the default behaviors unless a cloud is configured differently. * Identity uses `password` authentication * Identity API Version is 2 * Image API Version is 2 * Volume API Version is 2 * Images must be in `qcow2` format * Images are uploaded using PUT interface * Public IPv4 is directly routable via DHCP from Neutron * IPv6 is not provided * Floating IPs are provided by Neutron * Security groups are provided by Neutron * Vendor specific agents are not used auro ---- https://api.auro.io:5000/v2.0 ============== ================ Region Name Human Name ============== ================ van1 Vancouver, BC ============== ================ * Public IPv4 is provided via NAT with Neutron Floating IP catalyst -------- https://api.cloud.catalyst.net.nz:5000/v2.0 ============== ================ Region Name Human Name ============== ================ nz-por-1 Porirua, NZ nz_wlg_2 Wellington, NZ ============== ================ * Image API Version is 1 * Images must be in `raw` format * Volume API Version is 1 citycloud --------- https://identity1.citycloud.com:5000/v3/ ============== ================ Region Name Human Name ============== ================ Lon1 London, UK Sto2 Stockholm, SE Kna1 Karlskrona, SE ============== ================ * Identity API Version is 3 * Public IPv4 is provided via NAT with Neutron Floating IP * Volume API Version is 1 conoha ------ https://identity.%(region_name)s.conoha.io ============== ================ Region Name Human Name ============== ================ tyo1 Tokyo, JP sin1 Singapore sjc1 San Jose, CA ============== ================ * Image upload is not supported datacentred ----------- https://compute.datacentred.io:5000 ============== ================ Region Name Human Name ============== ================ sal01 Manchester, UK ============== ================ * Image API Version is 1 dreamhost --------- https://keystone.dream.io/v2.0 ============== ================ Region Name Human Name ============== ================ RegionOne Region One ============== ================ * Images must be in `raw` format * Public IPv4 is provided via NAT with Neutron Floating IP * IPv6 is provided to every server elastx ------ https://ops.elastx.net:5000/v2.0 ============== ================ Region Name Human Name ============== ================ regionOne Region One ============== ================ * Public IPv4 is provided via NAT with Neutron Floating IP entercloudsuite --------------- https://api.entercloudsuite.com/v2.0 ============== ================ Region Name Human Name ============== ================ nl-ams1 Amsterdam, NL it-mil1 Milan, IT de-fra1 Frankfurt, DE ============== ================ * Volume API Version is 1 ibmcloud -------- https://identity.open.softlayer.com ============== ================ Region Name Human Name ============== ================ london London, UK ============== ================ * Public IPv4 is provided via NAT with Neutron Floating IP internap -------- https://identity.api.cloud.iweb.com/v2.0 ============== ================ Region Name Human Name ============== ================ ams01 Amsterdam, NL da01 Dallas, TX nyj01 New York, NY ============== ================ * Image API Version is 1 * Floating IPs are not supported ovh --- https://auth.cloud.ovh.net/v2.0 ============== ================ Region Name Human Name ============== ================ BHS1 Beauharnois, QC SBG1 Strassbourg, FR GRA1 Gravelines, FR ============== ================ * Images must be in `raw` format * Floating IPs are not supported rackspace --------- https://identity.api.rackspacecloud.com/v2.0/ ============== ================ Region Name Human Name ============== ================ DFW Dallas HKG Hong Kong IAD Washington, D.C. LON London ORD Chicago SYD Sydney ============== ================ * Database Service Type is `rax:database` * Compute Service Name is `cloudServersOpenStack` * Images must be in `vhd` format * Images must be uploaded using the Glance Task Interface * Floating IPs are not supported * Public IPv4 is directly routable via static config by Nova * IPv6 is provided to every server * Security groups are not supported * Uploaded Images need properties to not use vendor agent:: :vm_mode: hvm :xenapi_use_agent: False * Volume API Version is 1 switchengines ------------- https://keystone.cloud.switch.ch:5000/v2.0 ============== ================ Region Name Human Name ============== ================ LS Lausanne, CH ZH Zurich, CH ============== ================ * Images must be in `raw` format * Images must be uploaded using the Glance Task Interface * Volume API Version is 1 ultimum ------- https://console.ultimum-cloud.com:5000/v2.0 ============== ================ Region Name Human Name ============== ================ RegionOne Region One ============== ================ * Volume API Version is 1 unitedstack ----------- https://identity.api.ustack.com/v3 ============== ================ Region Name Human Name ============== ================ bj1 Beijing gd1 Guangdong ============== ================ * Identity API Version is 3 * Images must be in `raw` format * Volume API Version is 1 vexxhost -------- http://auth.vexxhost.net ============== ================ Region Name Human Name ============== ================ ca-ymq-1 Montreal ============== ================ * DNS API Version is 1 * Identity API Version is 3 zetta ----- https://identity.api.zetta.io/v3 ============== ================ Region Name Human Name ============== ================ no-osl1 Oslo ============== ================ * DNS API Version is 2 * Identity API Version is 3 os-client-config-1.16.0/os_client_config/000077500000000000000000000000001266264256200202565ustar00rootroot00000000000000os-client-config-1.16.0/os_client_config/__init__.py000066400000000000000000000052661266264256200224000ustar00rootroot00000000000000# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. # # 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 sys from os_client_config import cloud_config from os_client_config.config import OpenStackConfig # noqa def get_config(service_key=None, options=None, **kwargs): config = OpenStackConfig() if options: config.register_argparse_options(options, sys.argv, service_key) parsed_options = options.parse_known_args(sys.argv) else: parsed_options = None return config.get_one_cloud(options=parsed_options, **kwargs) def session_client(service_key, options=None, **kwargs): """Simple wrapper function. It has almost no features. This will get you a raw requests Session Adapter that is mounted on the given service from the keystone service catalog. If you leave off cloud and region_name, it will assume that you've got env vars set, but if you give them, it'll use clouds.yaml as you'd expect. This function is deliberately simple. It has no flexibility. If you want flexibility, you can make a cloud config object and call get_session_client on it. This function is to make it easy to poke at OpenStack REST APIs with a properly configured keystone session. """ cloud = get_config(service_key=service_key, options=options, **kwargs) return cloud.get_session_client(service_key) # Backwards compat - simple_client was a terrible name simple_client = session_client def make_client(service_key, constructor=None, options=None, **kwargs): """Simple wrapper for getting a client instance from a client lib. OpenStack Client Libraries all have a fairly consistent constructor interface which os-client-config supports. In the simple case, there is one and only one right way to construct a client object. If as a user you don't want to do fancy things, just use this. It honors OS_ environment variables and clouds.yaml - and takes as **kwargs anything you'd expect to pass in. """ cloud = get_config(service_key=service_key, options=options, **kwargs) if not constructor: constructor = cloud_config._get_client(service_key) return cloud.get_legacy_client(service_key, constructor) os-client-config-1.16.0/os_client_config/_log.py000066400000000000000000000014761266264256200215600ustar00rootroot00000000000000# Copyright (c) 2015 IBM Corp. # # 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 logging class NullHandler(logging.Handler): def emit(self, record): pass def setup_logging(name): log = logging.getLogger(name) if len(log.handlers) == 0: h = NullHandler() log.addHandler(h) return log os-client-config-1.16.0/os_client_config/cloud_config.py000066400000000000000000000434721266264256200232750ustar00rootroot00000000000000# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. # # 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 importlib import warnings from keystoneauth1 import adapter from keystoneauth1 import plugin from keystoneauth1 import session import requestsexceptions from os_client_config import _log from os_client_config import constructors from os_client_config import exceptions def _get_client(service_key): class_mapping = constructors.get_constructor_mapping() if service_key not in class_mapping: raise exceptions.OpenStackConfigException( "Service {service_key} is unkown. Please pass in a client" " constructor or submit a patch to os-client-config".format( service_key=service_key)) mod_name, ctr_name = class_mapping[service_key].rsplit('.', 1) lib_name = mod_name.split('.')[0] try: mod = importlib.import_module(mod_name) except ImportError: raise exceptions.OpenStackConfigException( "Client for '{service_key}' was requested, but" " {mod_name} was unable to be imported. Either import" " the module yourself and pass the constructor in as an argument," " or perhaps you do not have python-{lib_name} installed.".format( service_key=service_key, mod_name=mod_name, lib_name=lib_name)) try: ctr = getattr(mod, ctr_name) except AttributeError: raise exceptions.OpenStackConfigException( "Client for '{service_key}' was requested, but although" " {mod_name} imported fine, the constructor at {fullname}" " as not found. Please check your installation, we have no" " clue what is wrong with your computer.".format( service_key=service_key, mod_name=mod_name, fullname=class_mapping[service_key])) return ctr def _make_key(key, service_type): if not service_type: return key else: service_type = service_type.lower().replace('-', '_') return "_".join([service_type, key]) class CloudConfig(object): def __init__(self, name, region, config, force_ipv4=False, auth_plugin=None, openstack_config=None): self.name = name self.region = region self.config = config self.log = _log.setup_logging(__name__) self._force_ipv4 = force_ipv4 self._auth = auth_plugin self._openstack_config = openstack_config self._keystone_session = None def __getattr__(self, key): """Return arbitrary attributes.""" if key.startswith('os_'): key = key[3:] if key in [attr.replace('-', '_') for attr in self.config]: return self.config[key] else: return None def __iter__(self): return self.config.__iter__() def __eq__(self, other): return (self.name == other.name and self.region == other.region and self.config == other.config) def __ne__(self, other): return not self == other def get_requests_verify_args(self): """Return the verify and cert values for the requests library.""" if self.config['verify'] and self.config['cacert']: verify = self.config['cacert'] else: verify = self.config['verify'] if self.config['cacert']: warnings.warn( "You are specifying a cacert for the cloud {0} but " "also to ignore the host verification. The host SSL cert " "will not be verified.".format(self.name)) cert = self.config.get('cert', None) if cert: if self.config['key']: cert = (cert, self.config['key']) return (verify, cert) def get_services(self): """Return a list of service types we know something about.""" services = [] for key, val in self.config.items(): if (key.endswith('api_version') or key.endswith('service_type') or key.endswith('service_name')): services.append("_".join(key.split('_')[:-2])) return list(set(services)) def get_auth_args(self): return self.config['auth'] def get_interface(self, service_type=None): key = _make_key('interface', service_type) interface = self.config.get('interface') return self.config.get(key, interface) def get_region_name(self, service_type=None): if not service_type: return self.region key = _make_key('region_name', service_type) return self.config.get(key, self.region) def get_api_version(self, service_type): key = _make_key('api_version', service_type) return self.config.get(key, None) def get_service_type(self, service_type): key = _make_key('service_type', service_type) # Cinder did an evil thing where they defined a second service # type in the catalog. Of course, that's insane, so let's hide this # atrocity from the as-yet-unsullied eyes of our users. # Of course, if the user requests a volumev2, that structure should # still work. if (service_type == 'volume' and self.get_api_version(service_type).startswith('2')): service_type = 'volumev2' return self.config.get(key, service_type) def get_service_name(self, service_type): key = _make_key('service_name', service_type) return self.config.get(key, None) def get_endpoint(self, service_type): key = _make_key('endpoint_override', service_type) old_key = _make_key('endpoint', service_type) return self.config.get(key, self.config.get(old_key, None)) @property def prefer_ipv6(self): return not self._force_ipv4 @property def force_ipv4(self): return self._force_ipv4 def get_auth(self): """Return a keystoneauth plugin from the auth credentials.""" return self._auth def get_session(self): """Return a keystoneauth session based on the auth credentials.""" if self._keystone_session is None: if not self._auth: raise exceptions.OpenStackConfigException( "Problem with auth parameters") (verify, cert) = self.get_requests_verify_args() # Turn off urllib3 warnings about insecure certs if we have # explicitly configured requests to tell it we do not want # cert verification if not verify: self.log.debug( "Turning off SSL warnings for {cloud}:{region}" " since verify=False".format( cloud=self.name, region=self.region)) requestsexceptions.squelch_warnings(insecure_requests=not verify) self._keystone_session = session.Session( auth=self._auth, verify=verify, cert=cert, timeout=self.config['api_timeout']) return self._keystone_session def get_session_client(self, service_key): """Return a prepped requests adapter for a given service. This is useful for making direct requests calls against a 'mounted' endpoint. That is, if you do: client = get_session_client('compute') then you can do: client.get('/flavors') and it will work like you think. """ return adapter.Adapter( session=self.get_session(), service_type=self.get_service_type(service_key), service_name=self.get_service_name(service_key), interface=self.get_interface(service_key), region_name=self.region) def get_session_endpoint(self, service_key): """Return the endpoint from config or the catalog. If a configuration lists an explicit endpoint for a service, return that. Otherwise, fetch the service catalog from the keystone session and return the appropriate endpoint. :param service_key: Generic key for service, such as 'compute' or 'network' :returns: Endpoint for the service, or None if not found """ override_endpoint = self.get_endpoint(service_key) if override_endpoint: return override_endpoint # keystone is a special case in keystone, because what? session = self.get_session() if service_key == 'identity': endpoint = session.get_endpoint(interface=plugin.AUTH_INTERFACE) else: endpoint = session.get_endpoint( service_type=self.get_service_type(service_key), service_name=self.get_service_name(service_key), interface=self.get_interface(service_key), region_name=self.region) return endpoint def get_legacy_client( self, service_key, client_class=None, interface_key=None, pass_version_arg=True, version=None, **kwargs): """Return a legacy OpenStack client object for the given config. Most of the OpenStack python-*client libraries have the same interface for their client constructors, but there are several parameters one wants to pass given a :class:`CloudConfig` object. In the future, OpenStack API consumption should be done through the OpenStack SDK, but that's not ready yet. This is for getting Client objects from python-*client only. :param service_key: Generic key for service, such as 'compute' or 'network' :param client_class: Class of the client to be instantiated. This should be the unversioned version if there is one, such as novaclient.client.Client, or the versioned one, such as neutronclient.v2_0.client.Client if there isn't :param interface_key: (optional) Some clients, such as glanceclient only accept the parameter 'interface' instead of 'endpoint_type' - this is a get-out-of-jail parameter for those until they can be aligned. os-client-config understands this to be the case if service_key is image, so this is really only for use with other unknown broken clients. :param pass_version_arg: (optional) If a versioned Client constructor was passed to client_class, set this to False, which will tell get_client to not pass a version parameter. os-client-config already understand that this is the case for network, so it can be omitted in that case. :param version: (optional) Version string to override the configured version string. :param kwargs: (optional) keyword args are passed through to the Client constructor, so this is in case anything additional needs to be passed in. """ if not client_class: client_class = _get_client(service_key) # Because of course swift is different if service_key == 'object-store': return self._get_swift_client(client_class=client_class, **kwargs) interface = self.get_interface(service_key) # trigger exception on lack of service endpoint = self.get_session_endpoint(service_key) endpoint_override = self.get_endpoint(service_key) if not interface_key: if service_key in ('image', 'key-manager'): interface_key = 'interface' else: interface_key = 'endpoint_type' constructor_kwargs = dict( session=self.get_session(), service_name=self.get_service_name(service_key), service_type=self.get_service_type(service_key), endpoint_override=endpoint_override, region_name=self.region) if service_key == 'image': # os-client-config does not depend on glanceclient, but if # the user passed in glanceclient.client.Client, which they # would need to do if they were requesting 'image' - then # they necessarily have glanceclient installed from glanceclient.common import utils as glance_utils endpoint, detected_version = glance_utils.strip_version(endpoint) # If the user has passed in a version, that's explicit, use it if not version: version = detected_version # If the user has passed in or configured an override, use it. # Otherwise, ALWAYS pass in an endpoint_override becuase # we've already done version stripping, so we don't want version # reconstruction to happen twice if not endpoint_override: constructor_kwargs['endpoint_override'] = endpoint constructor_kwargs.update(kwargs) constructor_kwargs[interface_key] = interface if pass_version_arg: if not version: version = self.get_api_version(service_key) # Temporary workaround while we wait for python-openstackclient # to be able to handle 2.0 which is what neutronclient expects if service_key == 'network' and version == '2': version = '2.0' if service_key == 'identity': # Workaround for bug#1513839 if 'endpoint' not in constructor_kwargs: endpoint = self.get_session_endpoint('identity') constructor_kwargs['endpoint'] = endpoint if service_key == 'network': constructor_kwargs['api_version'] = version else: constructor_kwargs['version'] = version return client_class(**constructor_kwargs) def _get_swift_client(self, client_class, **kwargs): auth_args = self.get_auth_args() auth_version = self.get_api_version('identity') session = self.get_session() token = session.get_token() endpoint = self.get_session_endpoint(service_key='object-store') if not endpoint: return None # If we have a username/password, we want to pass them to # swift - because otherwise it will not re-up tokens appropriately # However, if we only have non-password auth, then get a token # and pass it in swift_kwargs = dict( auth_version=auth_version, preauthurl=endpoint, preauthtoken=token, os_options=dict( region_name=self.get_region_name(), auth_token=token, object_storage_url=endpoint, service_type=self.get_service_type('object-store'), endpoint_type=self.get_interface('object-store'), )) if self.config['api_timeout'] is not None: swift_kwargs['timeout'] = float(self.config['api_timeout']) # create with password swift_kwargs['user'] = auth_args.get('username') swift_kwargs['key'] = auth_args.get('password') swift_kwargs['authurl'] = auth_args.get('auth_url') os_options = {} if auth_version == '2.0': os_options['tenant_name'] = auth_args.get('project_name') os_options['tenant_id'] = auth_args.get('project_id') else: os_options['project_name'] = auth_args.get('project_name') os_options['project_id'] = auth_args.get('project_id') for key in ( 'user_id', 'project_domain_id', 'project_domain_name', 'user_domain_id', 'user_domain_name'): os_options[key] = auth_args.get(key) swift_kwargs['os_options'].update(os_options) return client_class(**swift_kwargs) def get_cache_expiration_time(self): if self._openstack_config: return self._openstack_config.get_cache_expiration_time() def get_cache_path(self): if self._openstack_config: return self._openstack_config.get_cache_path() def get_cache_class(self): if self._openstack_config: return self._openstack_config.get_cache_class() def get_cache_arguments(self): if self._openstack_config: return self._openstack_config.get_cache_arguments() def get_cache_expiration(self): if self._openstack_config: return self._openstack_config.get_cache_expiration() def get_cache_resource_expiration(self, resource, default=None): """Get expiration time for a resource :param resource: Name of the resource type :param default: Default value to return if not found (optional, defaults to None) :returns: Expiration time for the resource type as float or default """ if self._openstack_config: expiration = self._openstack_config.get_cache_expiration() if resource not in expiration: return default return float(expiration[resource]) os-client-config-1.16.0/os_client_config/config.py000066400000000000000000001205211266264256200220760ustar00rootroot00000000000000# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. # # 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. # alias because we already had an option named argparse import argparse as argparse_mod import collections import copy import json import os import sys import warnings import appdirs from keystoneauth1 import adapter from keystoneauth1 import loading import yaml from os_client_config import _log from os_client_config import cloud_config from os_client_config import defaults from os_client_config import exceptions from os_client_config import vendors APPDIRS = appdirs.AppDirs('openstack', 'OpenStack', multipath='/etc') CONFIG_HOME = APPDIRS.user_config_dir CACHE_PATH = APPDIRS.user_cache_dir UNIX_CONFIG_HOME = os.path.join( os.path.expanduser(os.path.join('~', '.config')), 'openstack') UNIX_SITE_CONFIG_HOME = '/etc/openstack' SITE_CONFIG_HOME = APPDIRS.site_config_dir CONFIG_SEARCH_PATH = [ os.getcwd(), CONFIG_HOME, UNIX_CONFIG_HOME, SITE_CONFIG_HOME, UNIX_SITE_CONFIG_HOME ] YAML_SUFFIXES = ('.yaml', '.yml') JSON_SUFFIXES = ('.json',) CONFIG_FILES = [ os.path.join(d, 'clouds' + s) for d in CONFIG_SEARCH_PATH for s in YAML_SUFFIXES + JSON_SUFFIXES ] SECURE_FILES = [ os.path.join(d, 'secure' + s) for d in CONFIG_SEARCH_PATH for s in YAML_SUFFIXES + JSON_SUFFIXES ] VENDOR_FILES = [ os.path.join(d, 'clouds-public' + s) for d in CONFIG_SEARCH_PATH for s in YAML_SUFFIXES + JSON_SUFFIXES ] BOOL_KEYS = ('insecure', 'cache') # NOTE(dtroyer): This turns out to be not the best idea so let's move # overriding defaults to a kwarg to OpenStackConfig.__init__() # Remove this sometime in June 2015 once OSC is comfortably # changed-over and global-defaults is updated. def set_default(key, value): warnings.warn( "Use of set_default() is deprecated. Defaults should be set with the " "`override_defaults` parameter of OpenStackConfig." ) defaults.get_defaults() # make sure the dict is initialized defaults._defaults[key] = value def get_boolean(value): if type(value) is bool: return value if value.lower() == 'true': return True return False def _get_os_environ(envvar_prefix=None): ret = defaults.get_defaults() if not envvar_prefix: # This makes the or below be OS_ or OS_ which is a no-op envvar_prefix = 'OS_' environkeys = [k for k in os.environ.keys() if (k.startswith('OS_') or k.startswith(envvar_prefix)) and not k.startswith('OS_TEST') # infra CI var and not k.startswith('OS_STD') # infra CI var ] for k in environkeys: newkey = k.split('_', 1)[-1].lower() ret[newkey] = os.environ[k] # If the only environ key is region name, don't make a cloud, because # it's being used as a cloud selector if not environkeys or ( len(environkeys) == 1 and 'region_name' in ret): return None return ret def _merge_clouds(old_dict, new_dict): """Like dict.update, except handling nested dicts.""" ret = old_dict.copy() for (k, v) in new_dict.items(): if isinstance(v, dict): if k in ret: ret[k] = _merge_clouds(ret[k], v) else: ret[k] = v.copy() else: ret[k] = v return ret def _auth_update(old_dict, new_dict_source): """Like dict.update, except handling the nested dict called auth.""" new_dict = copy.deepcopy(new_dict_source) for (k, v) in new_dict.items(): if k == 'auth': if k in old_dict: old_dict[k].update(v) else: old_dict[k] = v.copy() else: old_dict[k] = v return old_dict def _fix_argv(argv): # Transform any _ characters in arg names to - so that we don't # have to throw billions of compat argparse arguments around all # over the place. processed = collections.defaultdict(list) for index in range(0, len(argv)): if argv[index].startswith('--'): split_args = argv[index].split('=') orig = split_args[0] new = orig.replace('_', '-') if orig != new: split_args[0] = new argv[index] = "=".join(split_args) # Save both for later so we can throw an error about dupes processed[new].append(orig) overlap = [] for new, old in processed.items(): if len(old) > 1: overlap.extend(old) if overlap: raise exceptions.OpenStackConfigException( "The following options were given: '{options}' which contain" " duplicates except that one has _ and one has -. There is" " no sane way for us to know what you're doing. Remove the" " duplicate option and try again".format( options=','.join(overlap))) class OpenStackConfig(object): def __init__(self, config_files=None, vendor_files=None, override_defaults=None, force_ipv4=None, envvar_prefix=None, secure_files=None): self.log = _log.setup_logging(__name__) self._config_files = config_files or CONFIG_FILES self._secure_files = secure_files or SECURE_FILES self._vendor_files = vendor_files or VENDOR_FILES config_file_override = os.environ.pop('OS_CLIENT_CONFIG_FILE', None) if config_file_override: self._config_files.insert(0, config_file_override) secure_file_override = os.environ.pop('OS_CLIENT_SECURE_FILE', None) if secure_file_override: self._secure_files.insert(0, secure_file_override) self.defaults = defaults.get_defaults() if override_defaults: self.defaults.update(override_defaults) # First, use a config file if it exists where expected self.config_filename, self.cloud_config = self._load_config_file() _, secure_config = self._load_secure_file() if secure_config: self.cloud_config = _merge_clouds( self.cloud_config, secure_config) if not self.cloud_config: self.cloud_config = {'clouds': {}} if 'clouds' not in self.cloud_config: self.cloud_config['clouds'] = {} # Grab ipv6 preference settings from env client_config = self.cloud_config.get('client', {}) if force_ipv4 is not None: # If it's passed in to the constructor, honor it. self.force_ipv4 = force_ipv4 else: # Get the backwards compat value prefer_ipv6 = get_boolean( os.environ.pop( 'OS_PREFER_IPV6', client_config.get( 'prefer_ipv6', client_config.get( 'prefer-ipv6', True)))) force_ipv4 = get_boolean( os.environ.pop( 'OS_FORCE_IPV4', client_config.get( 'force_ipv4', client_config.get( 'broken-ipv6', False)))) self.force_ipv4 = force_ipv4 if not prefer_ipv6: # this will only be false if someone set it explicitly # honor their wishes self.force_ipv4 = True # Next, process environment variables and add them to the mix self.envvar_key = os.environ.pop('OS_CLOUD_NAME', 'envvars') if self.envvar_key in self.cloud_config['clouds']: raise exceptions.OpenStackConfigException( '"{0}" defines a cloud named "{1}", but' ' OS_CLOUD_NAME is also set to "{1}". Please rename' ' either your environment based cloud, or one of your' ' file-based clouds.'.format(self.config_filename, self.envvar_key)) # Pull out OS_CLOUD so that if it's the only thing set, do not # make an envvars cloud self.default_cloud = os.environ.pop('OS_CLOUD', None) 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 # Finally, fall through and make a cloud that starts with defaults # because we need somewhere to put arguments, and there are neither # config files or env vars if not self.cloud_config['clouds']: self.cloud_config = dict( clouds=dict(defaults=dict(self.defaults))) self.default_cloud = 'defaults' self._cache_expiration_time = 0 self._cache_path = CACHE_PATH self._cache_class = 'dogpile.cache.null' self._cache_arguments = {} self._cache_expiration = {} if 'cache' in self.cloud_config: cache_settings = self._normalize_keys(self.cloud_config['cache']) # expiration_time used to be 'max_age' but the dogpile setting # is expiration_time. Support max_age for backwards compat. self._cache_expiration_time = cache_settings.get( 'expiration_time', cache_settings.get( 'max_age', self._cache_expiration_time)) # If cache class is given, use that. If not, but if cache time # is given, default to memory. Otherwise, default to nothing. # to memory. if self._cache_expiration_time: self._cache_class = 'dogpile.cache.memory' self._cache_class = self.cloud_config['cache'].get( 'class', self._cache_class) self._cache_path = os.path.expanduser( cache_settings.get('path', self._cache_path)) self._cache_arguments = cache_settings.get( 'arguments', self._cache_arguments) self._cache_expiration = cache_settings.get( 'expiration', self._cache_expiration) # Flag location to hold the peeked value of an argparse timeout value self._argv_timeout = False def get_extra_config(self, key, defaults=None): """Fetch an arbitrary extra chunk of config, laying in defaults. :param string key: name of the config section to fetch :param dict defaults: (optional) default values to merge under the found config """ if not defaults: defaults = {} return _merge_clouds( self._normalize_keys(defaults), self._normalize_keys(self.cloud_config.get(key, {}))) def _load_config_file(self): return self._load_yaml_json_file(self._config_files) def _load_secure_file(self): return self._load_yaml_json_file(self._secure_files) def _load_vendor_file(self): return self._load_yaml_json_file(self._vendor_files) def _load_yaml_json_file(self, filelist): for path in filelist: if os.path.exists(path): with open(path, 'r') as f: if path.endswith('json'): return path, json.load(f) else: return path, yaml.safe_load(f) return (None, {}) def _normalize_keys(self, config): new_config = {} for key, value in config.items(): key = key.replace('-', '_') if isinstance(value, dict): new_config[key] = self._normalize_keys(value) elif isinstance(value, bool): new_config[key] = value elif isinstance(value, int) and key != 'verbose_level': new_config[key] = str(value) elif isinstance(value, float): new_config[key] = str(value) else: new_config[key] = value return new_config def get_cache_expiration_time(self): return int(self._cache_expiration_time) def get_cache_interval(self): return self.get_cache_expiration_time() def get_cache_max_age(self): return self.get_cache_expiration_time() def get_cache_path(self): return self._cache_path def get_cache_class(self): return self._cache_class def get_cache_arguments(self): return copy.deepcopy(self._cache_arguments) def get_cache_expiration(self): return copy.deepcopy(self._cache_expiration) def _expand_region_name(self, region_name): return {'name': region_name, 'values': {}} def _expand_regions(self, regions): ret = [] for region in regions: if isinstance(region, dict): ret.append(copy.deepcopy(region)) else: ret.append(self._expand_region_name(region)) return ret def _get_regions(self, cloud): if cloud not in self.cloud_config['clouds']: return [self._expand_region_name('')] regions = self._get_known_regions(cloud) if not regions: # We don't know of any regions use a workable default. regions = [self._expand_region_name('')] return regions def _get_known_regions(self, cloud): config = self._normalize_keys(self.cloud_config['clouds'][cloud]) if 'regions' in config: return self._expand_regions(config['regions']) elif 'region_name' in config: regions = config['region_name'].split(',') if len(regions) > 1: warnings.warn( "Comma separated lists in region_name are deprecated." " Please use a yaml list in the regions" " parameter in {0} instead.".format(self.config_filename)) return self._expand_regions(regions) else: # crappit. we don't have a region defined. new_cloud = dict() our_cloud = self.cloud_config['clouds'].get(cloud, dict()) self._expand_vendor_profile(cloud, new_cloud, our_cloud) if 'regions' in new_cloud and new_cloud['regions']: return self._expand_regions(new_cloud['regions']) elif 'region_name' in new_cloud and new_cloud['region_name']: return [self._expand_region_name(new_cloud['region_name'])] def _get_region(self, cloud=None, region_name=''): if region_name is None: region_name = '' if not cloud: return self._expand_region_name(region_name) regions = self._get_known_regions(cloud) if not regions: return self._expand_region_name(region_name) if not region_name: return regions[0] for region in regions: if region['name'] == region_name: return region raise exceptions.OpenStackConfigException( 'Region {region_name} is not a valid region name for cloud' ' {cloud}. Valid choices are {region_list}. Please note that' ' region names are case sensitive.'.format( region_name=region_name, region_list=','.join([r['name'] for r in regions]), cloud=cloud)) def get_cloud_names(self): return self.cloud_config['clouds'].keys() def _get_base_cloud_config(self, name): cloud = dict() # Only validate cloud name if one was given if name and name not in self.cloud_config['clouds']: raise exceptions.OpenStackConfigException( "Named cloud {name} requested that was not found.".format( name=name)) our_cloud = self.cloud_config['clouds'].get(name, dict()) # Get the defaults cloud.update(self.defaults) self._expand_vendor_profile(name, cloud, our_cloud) if 'auth' not in cloud: cloud['auth'] = dict() _auth_update(cloud, our_cloud) if 'cloud' in cloud: del cloud['cloud'] return cloud def _expand_vendor_profile(self, name, cloud, our_cloud): # Expand a profile if it exists. 'cloud' is an old confusing name # for this. profile_name = our_cloud.get('profile', our_cloud.get('cloud', None)) if profile_name and profile_name != self.envvar_key: if 'cloud' in our_cloud: warnings.warn( "{0} use the keyword 'cloud' to reference a known " "vendor profile. This has been deprecated in favor of the " "'profile' keyword.".format(self.config_filename)) vendor_filename, vendor_file = self._load_vendor_file() if vendor_file and profile_name in vendor_file['public-clouds']: _auth_update(cloud, vendor_file['public-clouds'][profile_name]) else: profile_data = vendors.get_profile(profile_name) if profile_data: _auth_update(cloud, profile_data) else: # Can't find the requested vendor config, go about business warnings.warn("Couldn't find the vendor profile '{0}', for" " the cloud '{1}'".format(profile_name, name)) def _fix_backwards_madness(self, cloud): cloud = self._fix_backwards_auth_plugin(cloud) cloud = self._fix_backwards_project(cloud) cloud = self._fix_backwards_interface(cloud) cloud = self._handle_domain_id(cloud) return cloud def _project_scoped(self, cloud): return ('project_id' in cloud or 'project_name' in cloud or 'project_id' in cloud['auth'] or 'project_name' in cloud['auth']) def _handle_domain_id(self, cloud): # Allow people to just specify domain once if it's the same mappings = { 'domain_id': ('user_domain_id', 'project_domain_id'), 'domain_name': ('user_domain_name', 'project_domain_name'), } for target_key, possible_values in mappings.items(): if not self._project_scoped(cloud): if target_key in cloud and target_key not in cloud['auth']: cloud['auth'][target_key] = cloud.pop(target_key) continue for key in possible_values: if target_key in cloud['auth'] and key not in cloud['auth']: cloud['auth'][key] = cloud['auth'][target_key] cloud['auth'].pop(target_key, None) return cloud def _fix_backwards_project(self, cloud): # Do the lists backwards so that project_name is the ultimate winner # Also handle moving domain names into auth so that domain mapping # is easier mappings = { 'domain_id': ('domain_id', 'domain-id'), 'domain_name': ('domain_name', 'domain-name'), 'user_domain_id': ('user_domain_id', 'user-domain-id'), 'user_domain_name': ('user_domain_name', 'user-domain-name'), 'project_domain_id': ('project_domain_id', 'project-domain-id'), 'project_domain_name': ( 'project_domain_name', 'project-domain-name'), 'token': ('auth-token', 'auth_token', 'token'), } if cloud.get('auth_type', None) == 'v2password': # If v2password is explcitly requested, this is to deal with old # clouds. That's fine - we need to map settings in the opposite # direction mappings['tenant_id'] = ( 'project_id', 'project-id', 'tenant_id', 'tenant-id') mappings['tenant_name'] = ( 'project_name', 'project-name', 'tenant_name', 'tenant-name') else: mappings['project_id'] = ( 'tenant_id', 'tenant-id', 'project_id', 'project-id') mappings['project_name'] = ( 'tenant_name', 'tenant-name', 'project_name', 'project-name') for target_key, possible_values in mappings.items(): target = None for key in possible_values: if key in cloud: target = str(cloud[key]) del cloud[key] if key in cloud['auth']: target = str(cloud['auth'][key]) del cloud['auth'][key] if target: cloud['auth'][target_key] = target return cloud def _fix_backwards_auth_plugin(self, cloud): # Do the lists backwards so that auth_type is the ultimate winner mappings = { 'auth_type': ('auth_plugin', 'auth_type'), } for target_key, possible_values in mappings.items(): target = None for key in possible_values: if key in cloud: target = cloud[key] del cloud[key] cloud[target_key] = target # Because we force alignment to v3 nouns, we want to force # use of the auth plugin that can do auto-selection and dealing # with that based on auth parameters. v2password is basically # completely broken return cloud def register_argparse_arguments(self, parser, argv, service_keys=[]): """Register all of the common argparse options needed. Given an argparse parser, register the keystoneauth Session arguments, the keystoneauth Auth Plugin Options and os-cloud. Also, peek in the argv to see if all of the auth plugin options should be registered or merely the ones already configured. :param argparse.ArgumentParser: parser to attach argparse options to :param list argv: the arguments provided to the application :param string service_keys: Service or list of services this argparse should be specialized for, if known. The first item in the list will be used as the default value for service_type (optional) :raises exceptions.OpenStackConfigException if an invalid auth-type is requested """ # Fix argv in place - mapping any keys with embedded _ in them to - _fix_argv(argv) local_parser = argparse_mod.ArgumentParser(add_help=False) for p in (parser, local_parser): p.add_argument( '--os-cloud', metavar='', default=os.environ.get('OS_CLOUD', None), help='Named cloud to connect to') # we need to peek to see if timeout was actually passed, since # the keystoneauth declaration of it has a default, which means # we have no clue if the value we get is from the ksa default # for from the user passing it explicitly. We'll stash it for later local_parser.add_argument('--timeout', metavar='') # We need for get_one_cloud to be able to peek at whether a token # was passed so that we can swap the default from password to # token if it was. And we need to also peek for --os-auth-token # for novaclient backwards compat local_parser.add_argument('--os-token') local_parser.add_argument('--os-auth-token') # Peek into the future and see if we have an auth-type set in # config AND a cloud set, so that we know which command line # arguments to register and show to the user (the user may want # to say something like: # openstack --os-cloud=foo --os-oidctoken=bar # although I think that user is the cause of my personal pain options, _args = local_parser.parse_known_args(argv) if options.timeout: self._argv_timeout = True # validate = False because we're not _actually_ loading here # we're only peeking, so it's the wrong time to assert that # the rest of the arguments given are invalid for the plugin # chosen (for instance, --help may be requested, so that the # user can see what options he may want to give cloud = self.get_one_cloud(argparse=options, validate=False) default_auth_type = cloud.config['auth_type'] try: loading.register_auth_argparse_arguments( parser, argv, default=default_auth_type) except Exception: # Hidiing the keystoneauth exception because we're not actually # loading the auth plugin at this point, so the error message # from it doesn't actually make sense to os-client-config users options, _args = parser.parse_known_args(argv) plugin_names = loading.get_available_plugin_names() raise exceptions.OpenStackConfigException( "An invalid auth-type was specified: {auth_type}." " Valid choices are: {plugin_names}.".format( auth_type=options.os_auth_type, plugin_names=",".join(plugin_names))) if service_keys: primary_service = service_keys[0] else: primary_service = None loading.register_session_argparse_arguments(parser) adapter.register_adapter_argparse_arguments( parser, service_type=primary_service) for service_key in service_keys: # legacy clients have un-prefixed api-version options parser.add_argument( '--{service_key}-api-version'.format( service_key=service_key.replace('_', '-'), help=argparse_mod.SUPPRESS)) adapter.register_service_adapter_argparse_arguments( parser, service_type=service_key) # Backwards compat options for legacy clients parser.add_argument('--http-timeout', help=argparse_mod.SUPPRESS) parser.add_argument('--os-endpoint-type', help=argparse_mod.SUPPRESS) parser.add_argument('--endpoint-type', help=argparse_mod.SUPPRESS) def _fix_backwards_interface(self, cloud): new_cloud = {} for key in cloud.keys(): if key.endswith('endpoint_type'): target_key = key.replace('endpoint_type', 'interface') else: target_key = key new_cloud[target_key] = cloud[key] return new_cloud def _fix_backwards_api_timeout(self, cloud): new_cloud = {} # requests can only have one timeout, which means that in a single # cloud there is no point in different timeout values. However, # for some reason many of the legacy clients decided to shove their # service name in to the arg name for reasons surpassin sanity. If # we find any values that are not api_timeout, overwrite api_timeout # with the value service_timeout = None for key in cloud.keys(): if key.endswith('timeout') and not ( key == 'timeout' or key == 'api_timeout'): service_timeout = cloud[key] else: new_cloud[key] = cloud[key] if service_timeout is not None: new_cloud['api_timeout'] = service_timeout # The common argparse arg from keystoneauth is called timeout, but # os-client-config expects it to be called api_timeout if self._argv_timeout: if 'timeout' in new_cloud and new_cloud['timeout']: new_cloud['api_timeout'] = new_cloud.pop('timeout') return new_cloud def get_all_clouds(self): clouds = [] for cloud in self.get_cloud_names(): for region in self._get_regions(cloud): if region: clouds.append(self.get_one_cloud( cloud, region_name=region['name'])) return clouds def _fix_args(self, args, argparse=None): """Massage the passed-in options Replace - with _ and strip os_ prefixes. Convert an argparse Namespace object to a dict, removing values that are either None or ''. """ if argparse: # Convert the passed-in Namespace o_dict = vars(argparse) parsed_args = dict() for k in o_dict: if o_dict[k] is not None and o_dict[k] != '': parsed_args[k] = o_dict[k] args.update(parsed_args) os_args = dict() new_args = dict() for (key, val) in iter(args.items()): if type(args[key]) == dict: # dive into the auth dict new_args[key] = self._fix_args(args[key]) continue key = key.replace('-', '_') if key.startswith('os_'): os_args[key[3:]] = val else: new_args[key] = val new_args.update(os_args) return new_args def _find_winning_auth_value(self, opt, config): opt_name = opt.name.replace('-', '_') if opt_name in config: return config[opt_name] else: deprecated = getattr(opt, 'deprecated', getattr( opt, 'deprecated_opts', [])) for d_opt in deprecated: d_opt_name = d_opt.name.replace('-', '_') if d_opt_name in config: return config[d_opt_name] def auth_config_hook(self, config): """Allow examination of config values before loading auth plugin OpenStackClient will override this to perform additional chacks on auth_type. """ return config def _get_auth_loader(self, config): # Re-use the admin_token plugin for the "None" plugin # since it does not look up endpoints or tokens but rather # does a passthrough. This is useful for things like Ironic # that have a keystoneless operational mode, but means we're # still dealing with a keystoneauth Session object, so all the # _other_ things (SSL arg handling, timeout) all work consistently if config['auth_type'] in (None, "None", ''): config['auth_type'] = 'admin_token' # Set to notused rather than None because validate_auth will # strip the value if it's actually python None config['auth']['token'] = 'notused' return loading.get_plugin_loader(config['auth_type']) def _validate_auth_ksc(self, config): try: import keystoneclient.auth as ksc_auth except ImportError: return config # May throw a keystoneclient.exceptions.NoMatchingPlugin plugin_options = ksc_auth.get_plugin_class( config['auth_type']).get_options() for p_opt in plugin_options: # if it's in config.auth, win, kill it from config dict # if it's in config and not in config.auth, move it # deprecated loses to current # provided beats default, deprecated or not winning_value = self._find_winning_auth_value( p_opt, config['auth']) if not winning_value: winning_value = self._find_winning_auth_value(p_opt, config) # if the plugin tells us that this value is required # then error if it's doesn't exist now if not winning_value and p_opt.required: raise exceptions.OpenStackConfigException( 'Unable to find auth information for cloud' ' {cloud} in config files {files}' ' or environment variables. Missing value {auth_key}' ' required for auth plugin {plugin}'.format( cloud=cloud, files=','.join(self._config_files), auth_key=p_opt.name, plugin=config.get('auth_type'))) # Clean up after ourselves for opt in [p_opt.name] + [o.name for o in p_opt.deprecated_opts]: opt = opt.replace('-', '_') config.pop(opt, None) config['auth'].pop(opt, None) if winning_value: # Prefer the plugin configuration dest value if the value's key # is marked as depreciated. if p_opt.dest is None: config['auth'][p_opt.name.replace('-', '_')] = ( winning_value) else: config['auth'][p_opt.dest] = winning_value return config def _validate_auth(self, config, loader): # May throw a keystoneauth1.exceptions.NoMatchingPlugin plugin_options = loader.get_options() for p_opt in plugin_options: # if it's in config.auth, win, kill it from config dict # if it's in config and not in config.auth, move it # deprecated loses to current # provided beats default, deprecated or not winning_value = self._find_winning_auth_value( p_opt, config['auth']) if not winning_value: winning_value = self._find_winning_auth_value(p_opt, config) # Clean up after ourselves for opt in [p_opt.name] + [o.name for o in p_opt.deprecated]: opt = opt.replace('-', '_') config.pop(opt, None) config['auth'].pop(opt, None) if winning_value: # Prefer the plugin configuration dest value if the value's key # is marked as depreciated. if p_opt.dest is None: config['auth'][p_opt.name.replace('-', '_')] = ( winning_value) else: config['auth'][p_opt.dest] = winning_value return config def get_one_cloud(self, cloud=None, validate=True, argparse=None, **kwargs): """Retrieve a single cloud configuration and merge additional options :param string cloud: The name of the configuration to load from clouds.yaml :param boolean validate: Validate the config. Setting this to False causes no auth plugin to be created. It's really only useful for testing. :param Namespace argparse: 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 region_name: Name of the region of the cloud. :param kwargs: Additional configuration options :raises: keystoneauth1.exceptions.MissingRequiredOptions on missing required auth parameters """ args = self._fix_args(kwargs, argparse=argparse) if cloud is None: if 'cloud' in args: cloud = args['cloud'] else: cloud = self.default_cloud config = self._get_base_cloud_config(cloud) # Get region specific settings if 'region_name' not in args: args['region_name'] = '' region = self._get_region(cloud=cloud, region_name=args['region_name']) args['region_name'] = region['name'] region_args = copy.deepcopy(region['values']) # Regions is a list that we can use to create a list of cloud/region # objects. It does not belong in the single-cloud dict config.pop('regions', None) # Can't just do update, because None values take over for arg_list in region_args, args: for (key, val) in iter(arg_list.items()): if val is not None: if key == 'auth' and config[key] is not None: config[key] = _auth_update(config[key], val) else: config[key] = val # Infer token plugin if a token was given if (('auth' in config and 'token' in config['auth']) or ('auth_token' in config and config['auth_token']) or ('token' in config and config['token'])): config.setdefault('token', config.pop('auth_token', None)) # These backwards compat values are only set via argparse. If it's # there, it's because it was passed in explicitly, and should win config = self._fix_backwards_api_timeout(config) if 'endpoint_type' in config: config['interface'] = config.pop('endpoint_type') config = self._fix_backwards_madness(config) for key in BOOL_KEYS: if key in config: if type(config[key]) is not bool: config[key] = get_boolean(config[key]) # TODO(mordred): Special casing auth_url here. We should # come back to this betterer later so that it's # more generalized if 'auth' in config and 'auth_url' in config['auth']: config['auth']['auth_url'] = config['auth']['auth_url'].format( **config) # NOTE(dtroyer): OSC needs a hook into the auth args before the # plugin is loaded in order to maintain backward- # compatible behaviour config = self.auth_config_hook(config) if validate: try: loader = self._get_auth_loader(config) config = self._validate_auth(config, loader) auth_plugin = loader.load_from_options(**config['auth']) except Exception as e: # We WANT the ksa exception normally # but OSC can't handle it right now, so we try deferring # to ksc. If that ALSO fails, it means there is likely # a deeper issue, so we assume the ksa error was correct self.log.debug("Deferring keystone exception: {e}".format(e=e)) auth_plugin = None try: config = self._validate_auth_ksc(config) except Exception: raise e else: auth_plugin = None # If any of the defaults reference other values, we need to expand for (key, value) in config.items(): if hasattr(value, 'format'): config[key] = value.format(**config) force_ipv4 = config.pop('force_ipv4', self.force_ipv4) prefer_ipv6 = config.pop('prefer_ipv6', True) if not prefer_ipv6: force_ipv4 = True if cloud is None: cloud_name = '' else: cloud_name = str(cloud) return cloud_config.CloudConfig( name=cloud_name, region=config['region_name'], config=self._normalize_keys(config), force_ipv4=force_ipv4, auth_plugin=auth_plugin, openstack_config=self ) @staticmethod def set_one_cloud(config_file, cloud, set_config=None): """Set a single cloud configuration. :param string config_file: The path to the config file to edit. If this file does not exist it will be created. :param string cloud: The name of the configuration to save to clouds.yaml :param dict set_config: Configuration options to be set """ set_config = set_config or {} cur_config = {} try: with open(config_file) as fh: cur_config = yaml.safe_load(fh) except IOError as e: # Not no such file if e.errno != 2: raise pass clouds_config = cur_config.get('clouds', {}) cloud_config = _auth_update(clouds_config.get(cloud, {}), set_config) clouds_config[cloud] = cloud_config cur_config['clouds'] = clouds_config with open(config_file, 'w') as fh: yaml.safe_dump(cur_config, fh, default_flow_style=False) if __name__ == '__main__': config = OpenStackConfig().get_all_clouds() for cloud in config: print_cloud = False if len(sys.argv) == 1: print_cloud = True elif len(sys.argv) == 3 and ( sys.argv[1] == cloud.name and sys.argv[2] == cloud.region): print_cloud = True elif len(sys.argv) == 2 and ( sys.argv[1] == cloud.name): print_cloud = True if print_cloud: print(cloud.name, cloud.region, cloud.config) os-client-config-1.16.0/os_client_config/constructors.json000066400000000000000000000007341266264256200237250ustar00rootroot00000000000000{ "compute": "novaclient.client.Client", "database": "troveclient.client.Client", "identity": "keystoneclient.client.Client", "image": "glanceclient.Client", "key-manager": "barbicanclient.client.Client", "metering": "ceilometerclient.client.Client", "network": "neutronclient.neutron.client.Client", "object-store": "swiftclient.client.Connection", "orchestration": "heatclient.client.Client", "volume": "cinderclient.client.Client" } os-client-config-1.16.0/os_client_config/constructors.py000066400000000000000000000017041266264256200234020ustar00rootroot00000000000000# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. # # 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 json import os _json_path = os.path.join( os.path.dirname(os.path.realpath(__file__)), 'constructors.json') _class_mapping = None def get_constructor_mapping(): global _class_mapping if not _class_mapping: with open(_json_path, 'r') as json_file: _class_mapping = json.load(json_file) return _class_mapping os-client-config-1.16.0/os_client_config/defaults.json000066400000000000000000000011551266264256200227620ustar00rootroot00000000000000{ "auth_type": "password", "baremetal_api_version": "1", "container_api_version": "1", "compute_api_version": "2", "database_api_version": "1.0", "disable_vendor_agent": {}, "dns_api_version": "2", "interface": "public", "floating_ip_source": "neutron", "identity_api_version": "2.0", "image_api_use_tasks": false, "image_api_version": "2", "image_format": "qcow2", "key_manager_api_version": "v1", "metering_api_version": "2", "network_api_version": "2", "object_store_api_version": "1", "orchestration_api_version": "1", "secgroup_source": "neutron", "volume_api_version": "2" } os-client-config-1.16.0/os_client_config/defaults.py000066400000000000000000000024531266264256200224430ustar00rootroot00000000000000# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. # # 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 json import os _json_path = os.path.join( os.path.dirname(os.path.realpath(__file__)), 'defaults.json') _defaults = None def get_defaults(): global _defaults if not _defaults: # Python language specific defaults # These are defaults related to use of python libraries, they are # not qualities of a cloud. _defaults = dict( api_timeout=None, verify=True, cacert=None, cert=None, key=None, ) with open(_json_path, 'r') as json_file: updates = json.load(json_file) if updates is not None: _defaults.update(updates) return _defaults.copy() os-client-config-1.16.0/os_client_config/exceptions.py000066400000000000000000000013221266264256200230070ustar00rootroot00000000000000# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. # # 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. class OpenStackConfigException(Exception): """Something went wrong with parsing your OpenStack Config.""" os-client-config-1.16.0/os_client_config/schema.json000066400000000000000000000065241266264256200224200ustar00rootroot00000000000000{ "$schema": "http://json-schema.org/draft-04/schema#", "id": "https://git.openstack.org/cgit/openstack/cloud-data/plain/schema.json#", "type": "object", "properties": { "auth_type": { "name": "Auth Type", "description": "Name of authentication plugin to be used", "default": "password", "type": "string" }, "disable_vendor_agent": { "name": "Disable Vendor Agent Properties", "description": "Image properties required to disable vendor agent", "type": "object", "properties": {} }, "floating_ip_source": { "name": "Floating IP Source", "description": "Which service provides Floating IPs", "enum": [ "neutron", "nova", "None" ], "default": "neutron" }, "image_api_use_tasks": { "name": "Image Task API", "description": "Does the cloud require the Image Task API", "default": false, "type": "boolean" }, "image_format": { "name": "Image Format", "description": "Format for uploaded Images", "default": "qcow2", "type": "string" }, "interface": { "name": "API Interface", "description": "Which API Interface should connections hit", "default": "public", "enum": [ "public", "internal", "admin" ] }, "secgroup_source": { "name": "Security Group Source", "description": "Which service provides security groups", "default": "neutron", "enum": [ "neutron", "nova", "None" ] }, "baremetal_api_version": { "name": "Baremetal API Service Type", "description": "Baremetal API Service Type", "default": "1", "type": "string" }, "compute_api_version": { "name": "Compute API Version", "description": "Compute API Version", "default": "2", "type": "string" }, "database_api_version": { "name": "Database API Version", "description": "Database API Version", "default": "1.0", "type": "string" }, "dns_api_version": { "name": "DNS API Version", "description": "DNS API Version", "default": "2", "type": "string" }, "identity_api_version": { "name": "Identity API Version", "description": "Identity API Version", "default": "2", "type": "string" }, "image_api_version": { "name": "Image API Version", "description": "Image API Version", "default": "1", "type": "string" }, "network_api_version": { "name": "Network API Version", "description": "Network API Version", "default": "2", "type": "string" }, "object_store_api_version": { "name": "Object Storage API Version", "description": "Object Storage API Version", "default": "1", "type": "string" }, "volume_api_version": { "name": "Volume API Version", "description": "Volume API Version", "default": "2", "type": "string" } }, "required": [ "auth_type", "baremetal_api_version", "compute_api_version", "database_api_version", "disable_vendor_agent", "dns_api_version", "floating_ip_source", "identity_api_version", "image_api_use_tasks", "image_api_version", "image_format", "interface", "network_api_version", "object_store_api_version", "secgroup_source", "volume_api_version" ] } os-client-config-1.16.0/os_client_config/tests/000077500000000000000000000000001266264256200214205ustar00rootroot00000000000000os-client-config-1.16.0/os_client_config/tests/__init__.py000066400000000000000000000000001266264256200235170ustar00rootroot00000000000000os-client-config-1.16.0/os_client_config/tests/base.py000066400000000000000000000147021266264256200227100ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright 2010-2011 OpenStack Foundation # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # 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 copy import os import tempfile from os_client_config import cloud_config import extras import fixtures from oslotest import base import yaml VENDOR_CONF = { 'public-clouds': { '_test_cloud_in_our_cloud': { 'auth': { 'auth_url': 'http://example.com/v2', 'username': 'testotheruser', 'project_name': 'testproject', }, }, } } USER_CONF = { 'cache': { 'max_age': '1', 'expiration': { 'server': 5, 'image': '7', }, }, 'client': { 'force_ipv4': True, }, 'clouds': { '_test-cloud_': { 'profile': '_test_cloud_in_our_cloud', 'auth': { 'auth_url': 'http://example.com/v2', 'username': 'testuser', 'password': 'testpass', }, 'region_name': 'test-region', }, '_test_cloud_no_vendor': { 'profile': '_test_non_existant_cloud', 'auth': { 'auth_url': 'http://example.com/v2', 'username': 'testuser', 'project_name': 'testproject', }, 'region-name': 'test-region', }, '_test-cloud-int-project_': { 'auth': { 'username': 'testuser', 'password': 'testpass', 'domain_id': 'awesome-domain', 'project_id': 12345, 'auth_url': 'http://example.com/v2', }, 'region_name': 'test-region', }, '_test-cloud-domain-id_': { 'auth': { 'username': 'testuser', 'password': 'testpass', 'project_id': 12345, 'auth_url': 'http://example.com/v2', 'domain_id': '6789', 'project_domain_id': '123456789', }, 'region_name': 'test-region', }, '_test_cloud_regions': { 'auth': { 'username': 'testuser', 'password': 'testpass', 'project-id': 'testproject', 'auth_url': 'http://example.com/v2', }, 'regions': [ { 'name': 'region1', 'values': { 'external_network': 'region1-network', } }, { 'name': 'region2', 'values': { 'external_network': 'my-network', } } ], }, '_test_cloud_hyphenated': { 'auth': { 'username': 'testuser', 'password': 'testpass', 'project-id': '12345', 'auth_url': 'http://example.com/v2', }, 'region_name': 'test-region', }, '_test-cloud_no_region': { 'profile': '_test_cloud_in_our_cloud', 'auth': { 'auth_url': 'http://example.com/v2', 'username': 'testuser', 'password': 'testpass', }, }, '_test-cloud-domain-scoped_': { 'auth': { 'auth_url': 'http://example.com/v2', 'username': 'testuser', 'password': 'testpass', 'domain-id': '12345', }, }, }, 'ansible': { 'expand-hostvars': False, 'use_hostnames': True, }, } SECURE_CONF = { 'clouds': { '_test_cloud_no_vendor': { 'auth': { 'password': 'testpass', }, } } } NO_CONF = { 'cache': {'max_age': 1}, } def _write_yaml(obj): # Assume NestedTempfile so we don't have to cleanup with tempfile.NamedTemporaryFile(delete=False) as obj_yaml: obj_yaml.write(yaml.safe_dump(obj).encode('utf-8')) return obj_yaml.name class TestCase(base.BaseTestCase): """Test case base class for all unit tests.""" def setUp(self): super(TestCase, self).setUp() self.useFixture(fixtures.NestedTempfile()) conf = copy.deepcopy(USER_CONF) tdir = self.useFixture(fixtures.TempDir()) conf['cache']['path'] = tdir.path self.cloud_yaml = _write_yaml(conf) self.secure_yaml = _write_yaml(SECURE_CONF) self.vendor_yaml = _write_yaml(VENDOR_CONF) self.no_yaml = _write_yaml(NO_CONF) # Isolate the test runs from the environment # Do this as two loops because you can't modify the dict in a loop # over the dict in 3.4 keys_to_isolate = [] for env in os.environ.keys(): if env.startswith('OS_'): keys_to_isolate.append(env) for env in keys_to_isolate: self.useFixture(fixtures.EnvironmentVariable(env)) def _assert_cloud_details(self, cc): self.assertIsInstance(cc, cloud_config.CloudConfig) self.assertTrue(extras.safe_hasattr(cc, 'auth')) self.assertIsInstance(cc.auth, dict) self.assertIsNone(cc.cloud) self.assertIn('username', cc.auth) self.assertEqual('testuser', cc.auth['username']) self.assertEqual('testpass', cc.auth['password']) self.assertFalse(cc.config['image_api_use_tasks']) self.assertTrue('project_name' in cc.auth or 'project_id' in cc.auth) if 'project_name' in cc.auth: self.assertEqual('testproject', cc.auth['project_name']) elif 'project_id' in cc.auth: self.assertEqual('testproject', cc.auth['project_id']) self.assertEqual(cc.get_cache_expiration_time(), 1) self.assertEqual(cc.get_cache_resource_expiration('server'), 5.0) self.assertEqual(cc.get_cache_resource_expiration('image'), 7.0) os-client-config-1.16.0/os_client_config/tests/test_cloud_config.py000066400000000000000000000613661266264256200255000ustar00rootroot00000000000000# 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 copy from keystoneauth1 import plugin as ksa_plugin from keystoneauth1 import session as ksa_session import mock from os_client_config import cloud_config from os_client_config import defaults from os_client_config import exceptions from os_client_config.tests import base fake_config_dict = {'a': 1, 'os_b': 2, 'c': 3, 'os_c': 4} fake_services_dict = { 'compute_api_version': '2', 'compute_endpoint_override': 'http://compute.example.com', 'compute_region_name': 'region-bl', 'telemetry_endpoint': 'http://telemetry.example.com', 'interface': 'public', 'image_service_type': 'mage', 'identity_interface': 'admin', 'identity_service_name': 'locks', 'volume_api_version': '1', 'auth': {'password': 'hunter2', 'username': 'AzureDiamond'}, } class TestCloudConfig(base.TestCase): def test_arbitrary_attributes(self): cc = cloud_config.CloudConfig("test1", "region-al", fake_config_dict) self.assertEqual("test1", cc.name) self.assertEqual("region-al", cc.region) # Look up straight value self.assertEqual(1, cc.a) # Look up prefixed attribute, fail - returns None self.assertIsNone(cc.os_b) # Look up straight value, then prefixed value self.assertEqual(3, cc.c) self.assertEqual(3, cc.os_c) # Lookup mystery attribute self.assertIsNone(cc.x) # Test default ipv6 self.assertFalse(cc.force_ipv4) def test_iteration(self): cc = cloud_config.CloudConfig("test1", "region-al", fake_config_dict) self.assertTrue('a' in cc) self.assertFalse('x' in cc) def test_equality(self): cc1 = cloud_config.CloudConfig("test1", "region-al", fake_config_dict) cc2 = cloud_config.CloudConfig("test1", "region-al", fake_config_dict) self.assertEqual(cc1, cc2) def test_inequality(self): cc1 = cloud_config.CloudConfig("test1", "region-al", fake_config_dict) cc2 = cloud_config.CloudConfig("test2", "region-al", fake_config_dict) self.assertNotEqual(cc1, cc2) cc2 = cloud_config.CloudConfig("test1", "region-xx", fake_config_dict) self.assertNotEqual(cc1, cc2) cc2 = cloud_config.CloudConfig("test1", "region-al", {}) self.assertNotEqual(cc1, cc2) def test_verify(self): config_dict = copy.deepcopy(fake_config_dict) config_dict['cacert'] = None config_dict['verify'] = False cc = cloud_config.CloudConfig("test1", "region-xx", config_dict) (verify, cert) = cc.get_requests_verify_args() self.assertFalse(verify) config_dict['verify'] = True cc = cloud_config.CloudConfig("test1", "region-xx", config_dict) (verify, cert) = cc.get_requests_verify_args() self.assertTrue(verify) def test_verify_cacert(self): config_dict = copy.deepcopy(fake_config_dict) config_dict['cacert'] = "certfile" config_dict['verify'] = False cc = cloud_config.CloudConfig("test1", "region-xx", config_dict) (verify, cert) = cc.get_requests_verify_args() self.assertFalse(verify) config_dict['verify'] = True cc = cloud_config.CloudConfig("test1", "region-xx", config_dict) (verify, cert) = cc.get_requests_verify_args() self.assertEqual("certfile", verify) def test_cert_with_key(self): config_dict = copy.deepcopy(fake_config_dict) config_dict['cacert'] = None config_dict['verify'] = False config_dict['cert'] = 'cert' config_dict['key'] = 'key' cc = cloud_config.CloudConfig("test1", "region-xx", config_dict) (verify, cert) = cc.get_requests_verify_args() self.assertEqual(("cert", "key"), cert) def test_ipv6(self): cc = cloud_config.CloudConfig( "test1", "region-al", fake_config_dict, force_ipv4=True) self.assertTrue(cc.force_ipv4) def test_getters(self): cc = cloud_config.CloudConfig("test1", "region-al", fake_services_dict) self.assertEqual(['compute', 'identity', 'image', 'volume'], sorted(cc.get_services())) self.assertEqual({'password': 'hunter2', 'username': 'AzureDiamond'}, cc.get_auth_args()) self.assertEqual('public', cc.get_interface()) self.assertEqual('public', cc.get_interface('compute')) self.assertEqual('admin', cc.get_interface('identity')) self.assertEqual('region-al', cc.get_region_name()) self.assertEqual('region-al', cc.get_region_name('image')) self.assertEqual('region-bl', cc.get_region_name('compute')) self.assertIsNone(cc.get_api_version('image')) self.assertEqual('2', cc.get_api_version('compute')) self.assertEqual('mage', cc.get_service_type('image')) self.assertEqual('compute', cc.get_service_type('compute')) self.assertEqual('1', cc.get_api_version('volume')) self.assertEqual('volume', cc.get_service_type('volume')) self.assertEqual('http://compute.example.com', cc.get_endpoint('compute')) self.assertIsNone(cc.get_endpoint('image')) self.assertIsNone(cc.get_service_name('compute')) self.assertEqual('locks', cc.get_service_name('identity')) def test_volume_override(self): cc = cloud_config.CloudConfig("test1", "region-al", fake_services_dict) cc.config['volume_api_version'] = '2' self.assertEqual('volumev2', cc.get_service_type('volume')) def test_get_session_no_auth(self): config_dict = defaults.get_defaults() config_dict.update(fake_services_dict) cc = cloud_config.CloudConfig("test1", "region-al", config_dict) self.assertRaises( exceptions.OpenStackConfigException, cc.get_session) @mock.patch.object(ksa_session, 'Session') def test_get_session(self, mock_session): config_dict = defaults.get_defaults() config_dict.update(fake_services_dict) cc = cloud_config.CloudConfig( "test1", "region-al", config_dict, auth_plugin=mock.Mock()) cc.get_session() mock_session.assert_called_with( auth=mock.ANY, verify=True, cert=None, timeout=None) @mock.patch.object(ksa_session, 'Session') def test_get_session_with_timeout(self, mock_session): config_dict = defaults.get_defaults() config_dict.update(fake_services_dict) config_dict['api_timeout'] = 9 cc = cloud_config.CloudConfig( "test1", "region-al", config_dict, auth_plugin=mock.Mock()) cc.get_session() mock_session.assert_called_with( auth=mock.ANY, verify=True, cert=None, timeout=9) @mock.patch.object(ksa_session, 'Session') def test_override_session_endpoint_override(self, mock_session): config_dict = defaults.get_defaults() config_dict.update(fake_services_dict) cc = cloud_config.CloudConfig( "test1", "region-al", config_dict, auth_plugin=mock.Mock()) self.assertEqual( cc.get_session_endpoint('compute'), fake_services_dict['compute_endpoint_override']) @mock.patch.object(ksa_session, 'Session') def test_override_session_endpoint(self, mock_session): config_dict = defaults.get_defaults() config_dict.update(fake_services_dict) cc = cloud_config.CloudConfig( "test1", "region-al", config_dict, auth_plugin=mock.Mock()) self.assertEqual( cc.get_session_endpoint('telemetry'), fake_services_dict['telemetry_endpoint']) @mock.patch.object(cloud_config.CloudConfig, 'get_session') def test_session_endpoint_identity(self, mock_get_session): mock_session = mock.Mock() mock_get_session.return_value = mock_session config_dict = defaults.get_defaults() config_dict.update(fake_services_dict) cc = cloud_config.CloudConfig( "test1", "region-al", config_dict, auth_plugin=mock.Mock()) cc.get_session_endpoint('identity') mock_session.get_endpoint.assert_called_with( interface=ksa_plugin.AUTH_INTERFACE) @mock.patch.object(cloud_config.CloudConfig, 'get_session') def test_session_endpoint(self, mock_get_session): mock_session = mock.Mock() mock_get_session.return_value = mock_session config_dict = defaults.get_defaults() config_dict.update(fake_services_dict) cc = cloud_config.CloudConfig( "test1", "region-al", config_dict, auth_plugin=mock.Mock()) cc.get_session_endpoint('orchestration') mock_session.get_endpoint.assert_called_with( interface='public', service_name=None, region_name='region-al', service_type='orchestration') @mock.patch.object(cloud_config.CloudConfig, 'get_api_version') @mock.patch.object(cloud_config.CloudConfig, 'get_auth_args') @mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint') def test_legacy_client_object_store_password( self, mock_get_session_endpoint, mock_get_auth_args, mock_get_api_version): mock_client = mock.Mock() mock_get_session_endpoint.return_value = 'http://swift.example.com' mock_get_api_version.return_value = '3' mock_get_auth_args.return_value = dict( username='testuser', password='testpassword', project_name='testproject', auth_url='http://example.com', ) config_dict = defaults.get_defaults() config_dict.update(fake_services_dict) cc = cloud_config.CloudConfig( "test1", "region-al", config_dict, auth_plugin=mock.Mock()) cc.get_legacy_client('object-store', mock_client) mock_client.assert_called_with( preauthtoken=mock.ANY, auth_version=u'3', authurl='http://example.com', key='testpassword', os_options={ 'auth_token': mock.ANY, 'region_name': 'region-al', 'object_storage_url': 'http://swift.example.com', 'user_id': None, 'user_domain_name': None, 'project_name': 'testproject', 'project_domain_name': None, 'project_domain_id': None, 'project_id': None, 'service_type': 'object-store', 'endpoint_type': 'public', 'user_domain_id': None }, preauthurl='http://swift.example.com', user='testuser') @mock.patch.object(cloud_config.CloudConfig, 'get_auth_args') @mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint') def test_legacy_client_object_store_password_v2( self, mock_get_session_endpoint, mock_get_auth_args): mock_client = mock.Mock() mock_get_session_endpoint.return_value = 'http://swift.example.com' mock_get_auth_args.return_value = dict( username='testuser', password='testpassword', project_name='testproject', auth_url='http://example.com', ) config_dict = defaults.get_defaults() config_dict.update(fake_services_dict) cc = cloud_config.CloudConfig( "test1", "region-al", config_dict, auth_plugin=mock.Mock()) cc.get_legacy_client('object-store', mock_client) mock_client.assert_called_with( preauthtoken=mock.ANY, auth_version=u'2.0', authurl='http://example.com', key='testpassword', os_options={ 'auth_token': mock.ANY, 'region_name': 'region-al', 'object_storage_url': 'http://swift.example.com', 'user_id': None, 'user_domain_name': None, 'tenant_name': 'testproject', 'project_domain_name': None, 'project_domain_id': None, 'tenant_id': None, 'service_type': 'object-store', 'endpoint_type': 'public', 'user_domain_id': None }, preauthurl='http://swift.example.com', user='testuser') @mock.patch.object(cloud_config.CloudConfig, 'get_auth_args') @mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint') def test_legacy_client_object_store( self, mock_get_session_endpoint, mock_get_auth_args): mock_client = mock.Mock() mock_get_session_endpoint.return_value = 'http://example.com/v2' mock_get_auth_args.return_value = {} config_dict = defaults.get_defaults() config_dict.update(fake_services_dict) cc = cloud_config.CloudConfig( "test1", "region-al", config_dict, auth_plugin=mock.Mock()) cc.get_legacy_client('object-store', mock_client) mock_client.assert_called_with( preauthtoken=mock.ANY, auth_version=u'2.0', authurl=None, key=None, os_options={ 'auth_token': mock.ANY, 'region_name': 'region-al', 'object_storage_url': 'http://example.com/v2', 'user_id': None, 'user_domain_name': None, 'tenant_name': None, 'project_domain_name': None, 'project_domain_id': None, 'tenant_id': None, 'service_type': 'object-store', 'endpoint_type': 'public', 'user_domain_id': None }, preauthurl='http://example.com/v2', user=None) @mock.patch.object(cloud_config.CloudConfig, 'get_auth_args') @mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint') def test_legacy_client_object_store_timeout( self, mock_get_session_endpoint, mock_get_auth_args): mock_client = mock.Mock() mock_get_session_endpoint.return_value = 'http://example.com/v2' mock_get_auth_args.return_value = {} config_dict = defaults.get_defaults() config_dict.update(fake_services_dict) config_dict['api_timeout'] = 9 cc = cloud_config.CloudConfig( "test1", "region-al", config_dict, auth_plugin=mock.Mock()) cc.get_legacy_client('object-store', mock_client) mock_client.assert_called_with( preauthtoken=mock.ANY, auth_version=u'2.0', authurl=None, key=None, os_options={ 'auth_token': mock.ANY, 'region_name': 'region-al', 'object_storage_url': 'http://example.com/v2', 'user_id': None, 'user_domain_name': None, 'tenant_name': None, 'project_domain_name': None, 'project_domain_id': None, 'tenant_id': None, 'service_type': 'object-store', 'endpoint_type': 'public', 'user_domain_id': None }, preauthurl='http://example.com/v2', timeout=9.0, user=None) @mock.patch.object(cloud_config.CloudConfig, 'get_auth_args') def test_legacy_client_object_store_endpoint( self, mock_get_auth_args): mock_client = mock.Mock() mock_get_auth_args.return_value = {} config_dict = defaults.get_defaults() config_dict.update(fake_services_dict) config_dict['object_store_endpoint'] = 'http://example.com/swift' cc = cloud_config.CloudConfig( "test1", "region-al", config_dict, auth_plugin=mock.Mock()) cc.get_legacy_client('object-store', mock_client) mock_client.assert_called_with( preauthtoken=mock.ANY, auth_version=u'2.0', authurl=None, key=None, os_options={ 'auth_token': mock.ANY, 'region_name': 'region-al', 'object_storage_url': 'http://example.com/swift', 'user_id': None, 'user_domain_name': None, 'tenant_name': None, 'project_domain_name': None, 'project_domain_id': None, 'tenant_id': None, 'service_type': 'object-store', 'endpoint_type': 'public', 'user_domain_id': None }, preauthurl='http://example.com/swift', user=None) @mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint') def test_legacy_client_image(self, mock_get_session_endpoint): mock_client = mock.Mock() mock_get_session_endpoint.return_value = 'http://example.com/v2' config_dict = defaults.get_defaults() config_dict.update(fake_services_dict) cc = cloud_config.CloudConfig( "test1", "region-al", config_dict, auth_plugin=mock.Mock()) cc.get_legacy_client('image', mock_client) mock_client.assert_called_with( version=2.0, service_name=None, endpoint_override='http://example.com', region_name='region-al', interface='public', session=mock.ANY, # Not a typo - the config dict above overrides this service_type='mage' ) @mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint') def test_legacy_client_image_override(self, mock_get_session_endpoint): mock_client = mock.Mock() mock_get_session_endpoint.return_value = 'http://example.com/v2' config_dict = defaults.get_defaults() config_dict.update(fake_services_dict) config_dict['image_endpoint_override'] = 'http://example.com/override' cc = cloud_config.CloudConfig( "test1", "region-al", config_dict, auth_plugin=mock.Mock()) cc.get_legacy_client('image', mock_client) mock_client.assert_called_with( version=2.0, service_name=None, endpoint_override='http://example.com/override', region_name='region-al', interface='public', session=mock.ANY, # Not a typo - the config dict above overrides this service_type='mage' ) @mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint') def test_legacy_client_image_versioned(self, mock_get_session_endpoint): mock_client = mock.Mock() mock_get_session_endpoint.return_value = 'http://example.com/v2' config_dict = defaults.get_defaults() config_dict.update(fake_services_dict) # v2 endpoint was passed, 1 requested in config, endpoint wins config_dict['image_api_version'] = '1' cc = cloud_config.CloudConfig( "test1", "region-al", config_dict, auth_plugin=mock.Mock()) cc.get_legacy_client('image', mock_client) mock_client.assert_called_with( version=2.0, service_name=None, endpoint_override='http://example.com', region_name='region-al', interface='public', session=mock.ANY, # Not a typo - the config dict above overrides this service_type='mage' ) @mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint') def test_legacy_client_image_unversioned(self, mock_get_session_endpoint): mock_client = mock.Mock() mock_get_session_endpoint.return_value = 'http://example.com/' config_dict = defaults.get_defaults() config_dict.update(fake_services_dict) # Versionless endpoint, config wins config_dict['image_api_version'] = '1' cc = cloud_config.CloudConfig( "test1", "region-al", config_dict, auth_plugin=mock.Mock()) cc.get_legacy_client('image', mock_client) mock_client.assert_called_with( version='1', service_name=None, endpoint_override='http://example.com', region_name='region-al', interface='public', session=mock.ANY, # Not a typo - the config dict above overrides this service_type='mage' ) @mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint') def test_legacy_client_image_argument(self, mock_get_session_endpoint): mock_client = mock.Mock() mock_get_session_endpoint.return_value = 'http://example.com/v3' config_dict = defaults.get_defaults() config_dict.update(fake_services_dict) # Versionless endpoint, config wins config_dict['image_api_version'] = '6' cc = cloud_config.CloudConfig( "test1", "region-al", config_dict, auth_plugin=mock.Mock()) cc.get_legacy_client('image', mock_client, version='beef') mock_client.assert_called_with( version='beef', service_name=None, endpoint_override='http://example.com', region_name='region-al', interface='public', session=mock.ANY, # Not a typo - the config dict above overrides this service_type='mage' ) @mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint') def test_legacy_client_network(self, mock_get_session_endpoint): mock_client = mock.Mock() mock_get_session_endpoint.return_value = 'http://example.com/v2' config_dict = defaults.get_defaults() config_dict.update(fake_services_dict) cc = cloud_config.CloudConfig( "test1", "region-al", config_dict, auth_plugin=mock.Mock()) cc.get_legacy_client('network', mock_client) mock_client.assert_called_with( api_version='2.0', endpoint_type='public', endpoint_override=None, region_name='region-al', service_type='network', session=mock.ANY, service_name=None) @mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint') def test_legacy_client_compute(self, mock_get_session_endpoint): mock_client = mock.Mock() mock_get_session_endpoint.return_value = 'http://example.com/v2' config_dict = defaults.get_defaults() config_dict.update(fake_services_dict) cc = cloud_config.CloudConfig( "test1", "region-al", config_dict, auth_plugin=mock.Mock()) cc.get_legacy_client('compute', mock_client) mock_client.assert_called_with( version='2', endpoint_type='public', endpoint_override='http://compute.example.com', region_name='region-al', service_type='compute', session=mock.ANY, service_name=None) @mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint') def test_legacy_client_identity(self, mock_get_session_endpoint): mock_client = mock.Mock() mock_get_session_endpoint.return_value = 'http://example.com/v2' config_dict = defaults.get_defaults() config_dict.update(fake_services_dict) cc = cloud_config.CloudConfig( "test1", "region-al", config_dict, auth_plugin=mock.Mock()) cc.get_legacy_client('identity', mock_client) mock_client.assert_called_with( version='2.0', endpoint='http://example.com/v2', endpoint_type='admin', endpoint_override=None, region_name='region-al', service_type='identity', session=mock.ANY, service_name='locks') @mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint') def test_legacy_client_identity_v3(self, mock_get_session_endpoint): mock_client = mock.Mock() mock_get_session_endpoint.return_value = 'http://example.com' config_dict = defaults.get_defaults() config_dict.update(fake_services_dict) config_dict['identity_api_version'] = '3' cc = cloud_config.CloudConfig( "test1", "region-al", config_dict, auth_plugin=mock.Mock()) cc.get_legacy_client('identity', mock_client) mock_client.assert_called_with( version='3', endpoint='http://example.com', endpoint_type='admin', endpoint_override=None, region_name='region-al', service_type='identity', session=mock.ANY, service_name='locks') os-client-config-1.16.0/os_client_config/tests/test_config.py000066400000000000000000001004041266264256200242750ustar00rootroot00000000000000# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. # # 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 argparse import copy import os import fixtures import testtools import yaml from os_client_config import cloud_config from os_client_config import config from os_client_config import defaults from os_client_config import exceptions from os_client_config.tests import base class TestConfig(base.TestCase): def test_get_all_clouds(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml], secure_files=[self.no_yaml]) clouds = c.get_all_clouds() # We add one by hand because the regions cloud is going to exist # twice since it has two regions in it user_clouds = [ cloud for cloud in base.USER_CONF['clouds'].keys() ] + ['_test_cloud_regions'] configured_clouds = [cloud.name for cloud in clouds] self.assertItemsEqual(user_clouds, configured_clouds) def test_get_one_cloud(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cloud = c.get_one_cloud(validate=False) self.assertIsInstance(cloud, cloud_config.CloudConfig) self.assertEqual(cloud.name, '') def test_get_one_cloud_auth_defaults(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml]) cc = c.get_one_cloud(cloud='_test-cloud_', auth={'username': 'user'}) self.assertEqual('user', cc.auth['username']) self.assertEqual( defaults._defaults['auth_type'], cc.auth_type, ) self.assertEqual( defaults._defaults['identity_api_version'], cc.identity_api_version, ) def test_get_one_cloud_auth_override_defaults(self): default_options = {'compute_api_version': '4'} c = config.OpenStackConfig(config_files=[self.cloud_yaml], override_defaults=default_options) cc = c.get_one_cloud(cloud='_test-cloud_', auth={'username': 'user'}) self.assertEqual('user', cc.auth['username']) self.assertEqual('4', cc.compute_api_version) self.assertEqual( defaults._defaults['identity_api_version'], cc.identity_api_version, ) def test_get_one_cloud_with_config_files(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml], secure_files=[self.secure_yaml]) self.assertIsInstance(c.cloud_config, dict) self.assertIn('cache', c.cloud_config) self.assertIsInstance(c.cloud_config['cache'], dict) self.assertIn('max_age', c.cloud_config['cache']) self.assertIn('path', c.cloud_config['cache']) cc = c.get_one_cloud('_test-cloud_') self._assert_cloud_details(cc) cc = c.get_one_cloud('_test_cloud_no_vendor') self._assert_cloud_details(cc) def test_get_one_cloud_with_int_project_id(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cc = c.get_one_cloud('_test-cloud-int-project_') self.assertEqual('12345', cc.auth['project_id']) def test_get_one_cloud_with_domain_id(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cc = c.get_one_cloud('_test-cloud-domain-id_') self.assertEqual('6789', cc.auth['user_domain_id']) self.assertEqual('123456789', cc.auth['project_domain_id']) self.assertNotIn('domain_id', cc.auth) self.assertNotIn('domain-id', cc.auth) def test_get_one_cloud_domain_scoped(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cc = c.get_one_cloud('_test-cloud-domain-scoped_') self.assertEqual('12345', cc.auth['domain_id']) self.assertNotIn('user_domain_id', cc.auth) self.assertNotIn('project_domain_id', cc.auth) def test_get_one_cloud_infer_user_domain(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cc = c.get_one_cloud('_test-cloud-int-project_') self.assertEqual('awesome-domain', cc.auth['user_domain_id']) self.assertEqual('awesome-domain', cc.auth['project_domain_id']) self.assertNotIn('domain_id', cc.auth) def test_get_one_cloud_with_hyphenated_project_id(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cc = c.get_one_cloud('_test_cloud_hyphenated') self.assertEqual('12345', cc.auth['project_id']) def test_get_one_cloud_with_hyphenated_kwargs(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) args = { 'auth': { 'username': 'testuser', 'password': 'testpass', 'project-id': '12345', 'auth-url': 'http://example.com/v2', }, 'region_name': 'test-region', } cc = c.get_one_cloud(**args) self.assertEqual('http://example.com/v2', cc.auth['auth_url']) def test_no_environ(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) self.assertRaises( exceptions.OpenStackConfigException, c.get_one_cloud, 'envvars') def test_fallthrough(self): c = config.OpenStackConfig(config_files=[self.no_yaml], vendor_files=[self.no_yaml], secure_files=[self.no_yaml]) for k in os.environ.keys(): if k.startswith('OS_'): self.useFixture(fixtures.EnvironmentVariable(k)) c.get_one_cloud(cloud='defaults', validate=False) def test_prefer_ipv6_true(self): c = config.OpenStackConfig(config_files=[self.no_yaml], vendor_files=[self.no_yaml], secure_files=[self.no_yaml]) cc = c.get_one_cloud(cloud='defaults', validate=False) self.assertTrue(cc.prefer_ipv6) def test_prefer_ipv6_false(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cc = c.get_one_cloud(cloud='_test-cloud_') self.assertFalse(cc.prefer_ipv6) def test_force_ipv4_true(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cc = c.get_one_cloud(cloud='_test-cloud_') self.assertTrue(cc.force_ipv4) def test_force_ipv4_false(self): c = config.OpenStackConfig(config_files=[self.no_yaml], vendor_files=[self.no_yaml], secure_files=[self.no_yaml]) cc = c.get_one_cloud(cloud='defaults', validate=False) self.assertFalse(cc.force_ipv4) def test_get_one_cloud_auth_merge(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml]) cc = c.get_one_cloud(cloud='_test-cloud_', auth={'username': 'user'}) self.assertEqual('user', cc.auth['username']) self.assertEqual('testpass', cc.auth['password']) def test_only_secure_yaml(self): c = config.OpenStackConfig(config_files=['nonexistent'], vendor_files=['nonexistent'], secure_files=[self.secure_yaml]) cc = c.get_one_cloud(cloud='_test_cloud_no_vendor') self.assertEqual('testpass', cc.auth['password']) def test_get_cloud_names(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], secure_files=[self.no_yaml]) self.assertEqual( ['_test-cloud-domain-id_', '_test-cloud-domain-scoped_', '_test-cloud-int-project_', '_test-cloud_', '_test-cloud_no_region', '_test_cloud_hyphenated', '_test_cloud_no_vendor', '_test_cloud_regions', ], sorted(c.get_cloud_names())) c = config.OpenStackConfig(config_files=[self.no_yaml], vendor_files=[self.no_yaml], secure_files=[self.no_yaml]) for k in os.environ.keys(): if k.startswith('OS_'): self.useFixture(fixtures.EnvironmentVariable(k)) c.get_one_cloud(cloud='defaults', validate=False) self.assertEqual(['defaults'], sorted(c.get_cloud_names())) def test_set_one_cloud_creates_file(self): config_dir = fixtures.TempDir() self.useFixture(config_dir) config_path = os.path.join(config_dir.path, 'clouds.yaml') config.OpenStackConfig.set_one_cloud(config_path, '_test_cloud_') self.assertTrue(os.path.isfile(config_path)) with open(config_path) as fh: self.assertEqual({'clouds': {'_test_cloud_': {}}}, yaml.safe_load(fh)) def test_set_one_cloud_updates_cloud(self): new_config = { 'cloud': 'new_cloud', 'auth': { 'password': 'newpass' } } resulting_cloud_config = { 'auth': { 'password': 'newpass', 'username': 'testuser', 'auth_url': 'http://example.com/v2', }, 'cloud': 'new_cloud', 'profile': '_test_cloud_in_our_cloud', 'region_name': 'test-region' } resulting_config = copy.deepcopy(base.USER_CONF) resulting_config['clouds']['_test-cloud_'] = resulting_cloud_config config.OpenStackConfig.set_one_cloud(self.cloud_yaml, '_test-cloud_', new_config) with open(self.cloud_yaml) as fh: written_config = yaml.safe_load(fh) # We write a cache config for testing written_config['cache'].pop('path', None) self.assertEqual(written_config, resulting_config) def test_get_region_no_region_default(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml], secure_files=[self.no_yaml]) region = c._get_region(cloud='_test-cloud_no_region') self.assertEqual(region, {'name': '', 'values': {}}) def test_get_region_no_region(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml], secure_files=[self.no_yaml]) region = c._get_region(cloud='_test-cloud_no_region', region_name='override-region') self.assertEqual(region, {'name': 'override-region', 'values': {}}) def test_get_region_region_is_none(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml], secure_files=[self.no_yaml]) region = c._get_region(cloud='_test-cloud_no_region', region_name=None) self.assertEqual(region, {'name': '', 'values': {}}) def test_get_region_region_set(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml], secure_files=[self.no_yaml]) region = c._get_region(cloud='_test-cloud_', region_name='test-region') self.assertEqual(region, {'name': 'test-region', 'values': {}}) def test_get_region_many_regions_default(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml], secure_files=[self.no_yaml]) region = c._get_region(cloud='_test_cloud_regions', region_name='') self.assertEqual(region, {'name': 'region1', 'values': {'external_network': 'region1-network'}}) def test_get_region_many_regions(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml], secure_files=[self.no_yaml]) region = c._get_region(cloud='_test_cloud_regions', region_name='region2') self.assertEqual(region, {'name': 'region2', 'values': {'external_network': 'my-network'}}) def test_get_region_invalid_region(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml], secure_files=[self.no_yaml]) self.assertRaises( exceptions.OpenStackConfigException, c._get_region, cloud='_test_cloud_regions', region_name='invalid-region') def test_get_region_no_cloud(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml], secure_files=[self.no_yaml]) region = c._get_region(region_name='no-cloud-region') self.assertEqual(region, {'name': 'no-cloud-region', 'values': {}}) class TestConfigArgparse(base.TestCase): def setUp(self): super(TestConfigArgparse, self).setUp() self.args = dict( auth_url='http://example.com/v2', username='user', password='password', project_name='project', region_name='region2', snack_type='cookie', os_auth_token='no-good-things', ) self.options = argparse.Namespace(**self.args) def test_get_one_cloud_bad_region_argparse(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) self.assertRaises( exceptions.OpenStackConfigException, c.get_one_cloud, cloud='_test-cloud_', argparse=self.options) def test_get_one_cloud_argparse(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cc = c.get_one_cloud( cloud='_test_cloud_regions', argparse=self.options) self.assertEqual(cc.region_name, 'region2') self.assertEqual(cc.snack_type, 'cookie') def test_get_one_cloud_just_argparse(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cc = c.get_one_cloud(argparse=self.options) self.assertIsNone(cc.cloud) self.assertEqual(cc.region_name, 'region2') self.assertEqual(cc.snack_type, 'cookie') def test_get_one_cloud_just_kwargs(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cc = c.get_one_cloud(**self.args) self.assertIsNone(cc.cloud) self.assertEqual(cc.region_name, 'region2') self.assertEqual(cc.snack_type, 'cookie') def test_get_one_cloud_dash_kwargs(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) args = { 'auth-url': 'http://example.com/v2', 'username': 'user', 'password': 'password', 'project_name': 'project', 'region_name': 'other-test-region', 'snack_type': 'cookie', } cc = c.get_one_cloud(**args) self.assertIsNone(cc.cloud) self.assertEqual(cc.region_name, 'other-test-region') self.assertEqual(cc.snack_type, 'cookie') def test_get_one_cloud_no_argparse(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cc = c.get_one_cloud(cloud='_test-cloud_', argparse=None) self._assert_cloud_details(cc) self.assertEqual(cc.region_name, 'test-region') self.assertIsNone(cc.snack_type) def test_get_one_cloud_no_argparse_regions(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cc = c.get_one_cloud(cloud='_test_cloud_regions', argparse=None) self._assert_cloud_details(cc) self.assertEqual(cc.region_name, 'region1') self.assertIsNone(cc.snack_type) def test_get_one_cloud_bad_region(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) self.assertRaises( exceptions.OpenStackConfigException, c.get_one_cloud, cloud='_test_cloud_regions', region_name='bad') def test_get_one_cloud_bad_region_no_regions(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) self.assertRaises( exceptions.OpenStackConfigException, c.get_one_cloud, cloud='_test-cloud_', region_name='bad_region') def test_get_one_cloud_no_argparse_region2(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cc = c.get_one_cloud( cloud='_test_cloud_regions', region_name='region2', argparse=None) self._assert_cloud_details(cc) self.assertEqual(cc.region_name, 'region2') self.assertIsNone(cc.snack_type) def test_get_one_cloud_network(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cc = c.get_one_cloud( cloud='_test_cloud_regions', region_name='region1', argparse=None) self._assert_cloud_details(cc) self.assertEqual(cc.region_name, 'region1') self.assertEqual('region1-network', cc.config['external_network']) def test_get_one_cloud_per_region_network(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cc = c.get_one_cloud( cloud='_test_cloud_regions', region_name='region2', argparse=None) self._assert_cloud_details(cc) self.assertEqual(cc.region_name, 'region2') self.assertEqual('my-network', cc.config['external_network']) def test_fix_env_args(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) env_args = {'os-compute-api-version': 1} fixed_args = c._fix_args(env_args) self.assertDictEqual({'compute_api_version': 1}, fixed_args) def test_extra_config(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) defaults = {'use_hostnames': False, 'other-value': 'something'} ansible_options = c.get_extra_config('ansible', defaults) # This should show that the default for use_hostnames above is # overridden by the value in the config file defined in base.py # It should also show that other-value key is normalized and passed # through even though there is no corresponding value in the config # file, and that expand-hostvars key is normalized and the value # from the config comes through even though there is no default. self.assertDictEqual( { 'expand_hostvars': False, 'use_hostnames': True, 'other_value': 'something', }, ansible_options) def test_register_argparse_cloud(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) parser = argparse.ArgumentParser() c.register_argparse_arguments(parser, []) opts, _remain = parser.parse_known_args(['--os-cloud', 'foo']) self.assertEqual(opts.os_cloud, 'foo') def test_env_argparse_precedence(self): self.useFixture(fixtures.EnvironmentVariable( 'OS_TENANT_NAME', 'tenants-are-bad')) c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cc = c.get_one_cloud( cloud='envvars', argparse=self.options) self.assertEqual(cc.auth['project_name'], 'project') def test_argparse_default_no_token(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) parser = argparse.ArgumentParser() c.register_argparse_arguments(parser, []) # novaclient will add this parser.add_argument('--os-auth-token') opts, _remain = parser.parse_known_args() cc = c.get_one_cloud( cloud='_test_cloud_regions', argparse=opts) self.assertEqual(cc.config['auth_type'], 'password') self.assertNotIn('token', cc.config['auth']) def test_argparse_token(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) parser = argparse.ArgumentParser() c.register_argparse_arguments(parser, []) # novaclient will add this parser.add_argument('--os-auth-token') opts, _remain = parser.parse_known_args( ['--os-auth-token', 'very-bad-things', '--os-auth-type', 'token']) cc = c.get_one_cloud(argparse=opts) self.assertEqual(cc.config['auth_type'], 'token') self.assertEqual(cc.config['auth']['token'], 'very-bad-things') def test_argparse_underscores(self): c = config.OpenStackConfig(config_files=[self.no_yaml], vendor_files=[self.no_yaml], secure_files=[self.no_yaml]) parser = argparse.ArgumentParser() parser.add_argument('--os_username') argv = [ '--os_username', 'user', '--os_password', 'pass', '--os-auth-url', 'auth-url', '--os-project-name', 'project'] c.register_argparse_arguments(parser, argv=argv) opts, _remain = parser.parse_known_args(argv) cc = c.get_one_cloud(argparse=opts) self.assertEqual(cc.config['auth']['username'], 'user') self.assertEqual(cc.config['auth']['password'], 'pass') self.assertEqual(cc.config['auth']['auth_url'], 'auth-url') def test_argparse_underscores_duplicate(self): c = config.OpenStackConfig(config_files=[self.no_yaml], vendor_files=[self.no_yaml], secure_files=[self.no_yaml]) parser = argparse.ArgumentParser() parser.add_argument('--os_username') argv = [ '--os_username', 'user', '--os_password', 'pass', '--os-username', 'user1', '--os-password', 'pass1', '--os-auth-url', 'auth-url', '--os-project-name', 'project'] self.assertRaises( exceptions.OpenStackConfigException, c.register_argparse_arguments, parser=parser, argv=argv) def test_register_argparse_bad_plugin(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) parser = argparse.ArgumentParser() self.assertRaises( exceptions.OpenStackConfigException, c.register_argparse_arguments, parser, ['--os-auth-type', 'foo']) def test_register_argparse_not_password(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) parser = argparse.ArgumentParser() args = [ '--os-auth-type', 'v3token', '--os-token', 'some-secret', ] c.register_argparse_arguments(parser, args) opts, _remain = parser.parse_known_args(args) self.assertEqual(opts.os_token, 'some-secret') def test_register_argparse_password(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) parser = argparse.ArgumentParser() args = [ '--os-password', 'some-secret', ] c.register_argparse_arguments(parser, args) opts, _remain = parser.parse_known_args(args) self.assertEqual(opts.os_password, 'some-secret') with testtools.ExpectedException(AttributeError): opts.os_token def test_register_argparse_service_type(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) parser = argparse.ArgumentParser() args = [ '--os-service-type', 'network', '--os-endpoint-type', 'admin', '--http-timeout', '20', ] c.register_argparse_arguments(parser, args) opts, _remain = parser.parse_known_args(args) self.assertEqual(opts.os_service_type, 'network') self.assertEqual(opts.os_endpoint_type, 'admin') self.assertEqual(opts.http_timeout, '20') with testtools.ExpectedException(AttributeError): opts.os_network_service_type cloud = c.get_one_cloud(argparse=opts, verify=False) self.assertEqual(cloud.config['service_type'], 'network') self.assertEqual(cloud.config['interface'], 'admin') self.assertEqual(cloud.config['api_timeout'], '20') self.assertNotIn('http_timeout', cloud.config) def test_register_argparse_network_service_type(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) parser = argparse.ArgumentParser() args = [ '--os-endpoint-type', 'admin', '--network-api-version', '4', ] c.register_argparse_arguments(parser, args, ['network']) opts, _remain = parser.parse_known_args(args) self.assertEqual(opts.os_service_type, 'network') self.assertEqual(opts.os_endpoint_type, 'admin') self.assertEqual(opts.os_network_service_type, None) self.assertEqual(opts.os_network_api_version, None) self.assertEqual(opts.network_api_version, '4') cloud = c.get_one_cloud(argparse=opts, verify=False) self.assertEqual(cloud.config['service_type'], 'network') self.assertEqual(cloud.config['interface'], 'admin') self.assertEqual(cloud.config['network_api_version'], '4') self.assertNotIn('http_timeout', cloud.config) def test_register_argparse_network_service_types(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) parser = argparse.ArgumentParser() args = [ '--os-compute-service-name', 'cloudServers', '--os-network-service-type', 'badtype', '--os-endpoint-type', 'admin', '--network-api-version', '4', ] c.register_argparse_arguments( parser, args, ['compute', 'network', 'volume']) opts, _remain = parser.parse_known_args(args) self.assertEqual(opts.os_network_service_type, 'badtype') self.assertEqual(opts.os_compute_service_type, None) self.assertEqual(opts.os_volume_service_type, None) self.assertEqual(opts.os_service_type, 'compute') self.assertEqual(opts.os_compute_service_name, 'cloudServers') self.assertEqual(opts.os_endpoint_type, 'admin') self.assertEqual(opts.os_network_api_version, None) self.assertEqual(opts.network_api_version, '4') cloud = c.get_one_cloud(argparse=opts, verify=False) self.assertEqual(cloud.config['service_type'], 'compute') self.assertEqual(cloud.config['network_service_type'], 'badtype') self.assertEqual(cloud.config['interface'], 'admin') self.assertEqual(cloud.config['network_api_version'], '4') self.assertNotIn('volume_service_type', cloud.config) self.assertNotIn('http_timeout', cloud.config) class TestConfigDefault(base.TestCase): def setUp(self): super(TestConfigDefault, self).setUp() # Reset defaults after each test so that other tests are # not affected by any changes. self.addCleanup(self._reset_defaults) def _reset_defaults(self): defaults._defaults = None def test_set_no_default(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cc = c.get_one_cloud(cloud='_test-cloud_', argparse=None) self._assert_cloud_details(cc) self.assertEqual('password', cc.auth_type) def test_set_default_before_init(self): config.set_default('identity_api_version', '4') c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cc = c.get_one_cloud(cloud='_test-cloud_', argparse=None) self.assertEqual('4', cc.identity_api_version) class TestBackwardsCompatibility(base.TestCase): def test_set_no_default(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cloud = { 'identity_endpoint_type': 'admin', 'compute_endpoint_type': 'private', 'endpoint_type': 'public', 'auth_type': 'v3password', } result = c._fix_backwards_interface(cloud) expected = { 'identity_interface': 'admin', 'compute_interface': 'private', 'interface': 'public', 'auth_type': 'v3password', } self.assertDictEqual(expected, result) def test_project_v2password(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cloud = { 'auth_type': 'v2password', 'auth': { 'project-name': 'my_project_name', 'project-id': 'my_project_id' } } result = c._fix_backwards_project(cloud) expected = { 'auth_type': 'v2password', 'auth': { 'tenant_name': 'my_project_name', 'tenant_id': 'my_project_id' } } self.assertEqual(expected, result) def test_project_password(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cloud = { 'auth_type': 'password', 'auth': { 'project-name': 'my_project_name', 'project-id': 'my_project_id' } } result = c._fix_backwards_project(cloud) expected = { 'auth_type': 'password', 'auth': { 'project_name': 'my_project_name', 'project_id': 'my_project_id' } } self.assertEqual(expected, result) os-client-config-1.16.0/os_client_config/tests/test_environ.py000066400000000000000000000154571266264256200245250ustar00rootroot00000000000000# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. # # 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. from os_client_config import cloud_config from os_client_config import config from os_client_config import exceptions from os_client_config.tests import base import fixtures class TestEnviron(base.TestCase): def setUp(self): super(TestEnviron, self).setUp() self.useFixture( fixtures.EnvironmentVariable('OS_AUTH_URL', 'https://example.com')) self.useFixture( fixtures.EnvironmentVariable('OS_USERNAME', 'testuser')) self.useFixture( fixtures.EnvironmentVariable('OS_PASSWORD', 'testpass')) self.useFixture( fixtures.EnvironmentVariable('OS_PROJECT_NAME', 'testproject')) self.useFixture( fixtures.EnvironmentVariable('NOVA_PROJECT_ID', 'testnova')) def test_get_one_cloud(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) self.assertIsInstance(c.get_one_cloud(), cloud_config.CloudConfig) def test_no_fallthrough(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) self.assertRaises( exceptions.OpenStackConfigException, c.get_one_cloud, 'openstack') def test_envvar_name_override(self): self.useFixture( fixtures.EnvironmentVariable('OS_CLOUD_NAME', 'override')) c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cc = c.get_one_cloud('override') self._assert_cloud_details(cc) def test_envvar_prefer_ipv6_override(self): self.useFixture( fixtures.EnvironmentVariable('OS_PREFER_IPV6', 'false')) c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml], secure_files=[self.secure_yaml]) cc = c.get_one_cloud('_test-cloud_') self.assertFalse(cc.prefer_ipv6) def test_environ_exists(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml], secure_files=[self.secure_yaml]) cc = c.get_one_cloud('envvars') self._assert_cloud_details(cc) self.assertNotIn('auth_url', cc.config) self.assertIn('auth_url', cc.config['auth']) self.assertNotIn('project_id', cc.config['auth']) self.assertNotIn('auth_url', cc.config) cc = c.get_one_cloud('_test-cloud_') self._assert_cloud_details(cc) cc = c.get_one_cloud('_test_cloud_no_vendor') self._assert_cloud_details(cc) def test_environ_prefix(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml], envvar_prefix='NOVA_', secure_files=[self.secure_yaml]) cc = c.get_one_cloud('envvars') self._assert_cloud_details(cc) self.assertNotIn('auth_url', cc.config) self.assertIn('auth_url', cc.config['auth']) self.assertIn('project_id', cc.config['auth']) self.assertNotIn('auth_url', cc.config) cc = c.get_one_cloud('_test-cloud_') self._assert_cloud_details(cc) cc = c.get_one_cloud('_test_cloud_no_vendor') self._assert_cloud_details(cc) def test_get_one_cloud_with_config_files(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml], secure_files=[self.secure_yaml]) self.assertIsInstance(c.cloud_config, dict) self.assertIn('cache', c.cloud_config) self.assertIsInstance(c.cloud_config['cache'], dict) self.assertIn('max_age', c.cloud_config['cache']) self.assertIn('path', c.cloud_config['cache']) cc = c.get_one_cloud('_test-cloud_') self._assert_cloud_details(cc) cc = c.get_one_cloud('_test_cloud_no_vendor') self._assert_cloud_details(cc) def test_config_file_override(self): self.useFixture( fixtures.EnvironmentVariable( 'OS_CLIENT_CONFIG_FILE', self.cloud_yaml)) c = config.OpenStackConfig(config_files=[], vendor_files=[self.vendor_yaml]) cc = c.get_one_cloud('_test-cloud_') self._assert_cloud_details(cc) class TestEnvvars(base.TestCase): def test_no_envvars(self): self.useFixture( fixtures.EnvironmentVariable('NOVA_USERNAME', 'nova')) c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) self.assertRaises( exceptions.OpenStackConfigException, c.get_one_cloud, 'envvars') def test_test_envvars(self): self.useFixture( fixtures.EnvironmentVariable('NOVA_USERNAME', 'nova')) self.useFixture( fixtures.EnvironmentVariable('OS_STDERR_CAPTURE', 'True')) c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) self.assertRaises( exceptions.OpenStackConfigException, c.get_one_cloud, 'envvars') def test_have_envvars(self): self.useFixture( fixtures.EnvironmentVariable('NOVA_USERNAME', 'nova')) self.useFixture( fixtures.EnvironmentVariable('OS_USERNAME', 'user')) c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cc = c.get_one_cloud('envvars') self.assertEqual(cc.config['auth']['username'], 'user') def test_old_envvars(self): self.useFixture( fixtures.EnvironmentVariable('NOVA_USERNAME', 'nova')) c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml], envvar_prefix='NOVA_') cc = c.get_one_cloud('envvars') self.assertEqual(cc.config['auth']['username'], 'nova') os-client-config-1.16.0/os_client_config/tests/test_json.py000066400000000000000000000044541266264256200240110ustar00rootroot00000000000000# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. # # 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 glob import json import os import jsonschema from testtools import content from os_client_config import defaults from os_client_config.tests import base class TestConfig(base.TestCase): def json_diagnostics(self, exc_info): self.addDetail('filename', content.text_content(self.filename)) for error in sorted(self.validator.iter_errors(self.json_data)): self.addDetail('jsonschema', content.text_content(str(error))) def test_defaults_valid_json(self): _schema_path = os.path.join( os.path.dirname(os.path.realpath(defaults.__file__)), 'schema.json') schema = json.load(open(_schema_path, 'r')) self.validator = jsonschema.Draft4Validator(schema) self.addOnException(self.json_diagnostics) self.filename = os.path.join( os.path.dirname(os.path.realpath(defaults.__file__)), 'defaults.json') self.json_data = json.load(open(self.filename, 'r')) self.assertTrue(self.validator.is_valid(self.json_data)) def test_vendors_valid_json(self): _schema_path = os.path.join( os.path.dirname(os.path.realpath(defaults.__file__)), 'vendor-schema.json') schema = json.load(open(_schema_path, 'r')) self.validator = jsonschema.Draft4Validator(schema) self.addOnException(self.json_diagnostics) _vendors_path = os.path.join( os.path.dirname(os.path.realpath(defaults.__file__)), 'vendors') for self.filename in glob.glob(os.path.join(_vendors_path, '*.json')): self.json_data = json.load(open(self.filename, 'r')) self.assertTrue(self.validator.is_valid(self.json_data)) os-client-config-1.16.0/os_client_config/vendor-schema.json000066400000000000000000000147721266264256200237170ustar00rootroot00000000000000{ "$schema": "http://json-schema.org/draft-04/schema#", "id": "https://git.openstack.org/cgit/openstack/cloud-data/plain/vendor-schema.json#", "type": "object", "properties": { "name": { "type": "string" }, "profile": { "type": "object", "properties": { "auth": { "type": "object", "properties": { "auth_url": { "name": "Auth URL", "description": "URL of the primary Keystone endpoint", "type": "string" } } }, "auth_type": { "name": "Auth Type", "description": "Name of authentication plugin to be used", "default": "password", "type": "string" }, "disable_vendor_agent": { "name": "Disable Vendor Agent Properties", "description": "Image properties required to disable vendor agent", "type": "object", "properties": {} }, "floating_ip_source": { "name": "Floating IP Source", "description": "Which service provides Floating IPs", "enum": [ "neutron", "nova", "None" ], "default": "neutron" }, "image_api_use_tasks": { "name": "Image Task API", "description": "Does the cloud require the Image Task API", "default": false, "type": "boolean" }, "image_format": { "name": "Image Format", "description": "Format for uploaded Images", "default": "qcow2", "type": "string" }, "interface": { "name": "API Interface", "description": "Which API Interface should connections hit", "default": "public", "enum": [ "public", "internal", "admin" ] }, "secgroup_source": { "name": "Security Group Source", "description": "Which service provides security groups", "enum": [ "neutron", "nova", "None" ], "default": "neutron" }, "compute_service_name": { "name": "Compute API Service Name", "description": "Compute API Service Name", "type": "string" }, "database_service_name": { "name": "Database API Service Name", "description": "Database API Service Name", "type": "string" }, "dns_service_name": { "name": "DNS API Service Name", "description": "DNS API Service Name", "type": "string" }, "identity_service_name": { "name": "Identity API Service Name", "description": "Identity API Service Name", "type": "string" }, "image_service_name": { "name": "Image API Service Name", "description": "Image API Service Name", "type": "string" }, "volume_service_name": { "name": "Volume API Service Name", "description": "Volume API Service Name", "type": "string" }, "network_service_name": { "name": "Network API Service Name", "description": "Network API Service Name", "type": "string" }, "object_service_name": { "name": "Object Storage API Service Name", "description": "Object Storage API Service Name", "type": "string" }, "baremetal_service_name": { "name": "Baremetal API Service Name", "description": "Baremetal API Service Name", "type": "string" }, "compute_service_type": { "name": "Compute API Service Type", "description": "Compute API Service Type", "type": "string" }, "database_service_type": { "name": "Database API Service Type", "description": "Database API Service Type", "type": "string" }, "dns_service_type": { "name": "DNS API Service Type", "description": "DNS API Service Type", "type": "string" }, "identity_service_type": { "name": "Identity API Service Type", "description": "Identity API Service Type", "type": "string" }, "image_service_type": { "name": "Image API Service Type", "description": "Image API Service Type", "type": "string" }, "volume_service_type": { "name": "Volume API Service Type", "description": "Volume API Service Type", "type": "string" }, "network_service_type": { "name": "Network API Service Type", "description": "Network API Service Type", "type": "string" }, "object_service_type": { "name": "Object Storage API Service Type", "description": "Object Storage API Service Type", "type": "string" }, "baremetal_api_version": { "name": "Baremetal API Service Type", "description": "Baremetal API Service Type", "type": "string" }, "compute_api_version": { "name": "Compute API Version", "description": "Compute API Version", "type": "string" }, "database_api_version": { "name": "Database API Version", "description": "Database API Version", "type": "string" }, "dns_api_version": { "name": "DNS API Version", "description": "DNS API Version", "type": "string" }, "identity_api_version": { "name": "Identity API Version", "description": "Identity API Version", "type": "string" }, "image_api_version": { "name": "Image API Version", "description": "Image API Version", "type": "string" }, "volume_api_version": { "name": "Volume API Version", "description": "Volume API Version", "type": "string" }, "network_api_version": { "name": "Network API Version", "description": "Network API Version", "type": "string" }, "object_api_version": { "name": "Object Storage API Version", "description": "Object Storage API Version", "type": "string" }, "baremetal_api_version": { "name": "Baremetal API Version", "description": "Baremetal API Version", "type": "string" } } } }, "required": [ "name", "profile" ] } os-client-config-1.16.0/os_client_config/vendors/000077500000000000000000000000001266264256200217365ustar00rootroot00000000000000os-client-config-1.16.0/os_client_config/vendors/__init__.py000066400000000000000000000025541266264256200240550ustar00rootroot00000000000000# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. # # 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 glob import json import os import yaml _vendors_path = os.path.dirname(os.path.realpath(__file__)) _vendor_defaults = None def get_profile(profile_name): global _vendor_defaults if _vendor_defaults is None: _vendor_defaults = {} for vendor in glob.glob(os.path.join(_vendors_path, '*.yaml')): with open(vendor, 'r') as f: vendor_data = yaml.safe_load(f) _vendor_defaults[vendor_data['name']] = vendor_data['profile'] for vendor in glob.glob(os.path.join(_vendors_path, '*.json')): with open(vendor, 'r') as f: vendor_data = json.load(f) _vendor_defaults[vendor_data['name']] = vendor_data['profile'] return _vendor_defaults.get(profile_name) os-client-config-1.16.0/os_client_config/vendors/auro.json000066400000000000000000000002601266264256200235750ustar00rootroot00000000000000{ "name": "auro", "profile": { "auth": { "auth_url": "https://api.van1.auro.io:5000/v2.0" }, "identity_api_version": "2", "region_name": "van1" } } os-client-config-1.16.0/os_client_config/vendors/bluebox.json000066400000000000000000000001521266264256200242670ustar00rootroot00000000000000{ "name": "bluebox", "profile": { "volume_api_version": "1", "region_name": "RegionOne" } } os-client-config-1.16.0/os_client_config/vendors/catalyst.json000066400000000000000000000004241266264256200244550ustar00rootroot00000000000000{ "name": "catalyst", "profile": { "auth": { "auth_url": "https://api.cloud.catalyst.net.nz:5000/v2.0" }, "regions": [ "nz-por-1", "nz_wlg_2" ], "image_api_version": "1", "volume_api_version": "1", "image_format": "raw" } } os-client-config-1.16.0/os_client_config/vendors/citycloud.json000066400000000000000000000004001266264256200246220ustar00rootroot00000000000000{ "name": "citycloud", "profile": { "auth": { "auth_url": "https://identity1.citycloud.com:5000/v3/" }, "regions": [ "Lon1", "Sto2", "Kna1" ], "volume_api_version": "1", "identity_api_version": "3" } } os-client-config-1.16.0/os_client_config/vendors/conoha.json000066400000000000000000000003361266264256200241020ustar00rootroot00000000000000{ "name": "conoha", "profile": { "auth": { "auth_url": "https://identity.{region_name}.conoha.io" }, "regions": [ "sin1", "sjc1", "tyo1" ], "identity_api_version": "2" } } os-client-config-1.16.0/os_client_config/vendors/datacentred.json000066400000000000000000000003271266264256200251110ustar00rootroot00000000000000{ "name": "datacentred", "profile": { "auth": { "auth_url": "https://compute.datacentred.io:5000" }, "region-name": "sal01", "identity_api_version": "2", "image_api_version": "1" } } os-client-config-1.16.0/os_client_config/vendors/dreamhost.json000066400000000000000000000003141266264256200246150ustar00rootroot00000000000000{ "name": "dreamhost", "profile": { "auth": { "auth_url": "https://keystone.dream.io" }, "identity_api_version": "3", "region_name": "RegionOne", "image_format": "raw" } } os-client-config-1.16.0/os_client_config/vendors/elastx.json000066400000000000000000000002601266264256200241270ustar00rootroot00000000000000{ "name": "elastx", "profile": { "auth": { "auth_url": "https://ops.elastx.net:5000" }, "identity_api_version": "3", "region_name": "regionOne" } } os-client-config-1.16.0/os_client_config/vendors/entercloudsuite.json000066400000000000000000000004071266264256200260500ustar00rootroot00000000000000{ "name": "entercloudsuite", "profile": { "auth": { "auth_url": "https://api.entercloudsuite.com/" }, "identity_api_version": "3", "volume_api_version": "1", "regions": [ "it-mil1", "nl-ams1", "de-fra1" ] } } os-client-config-1.16.0/os_client_config/vendors/ibmcloud.json000066400000000000000000000003401266264256200244240ustar00rootroot00000000000000{ "name": "ibmcloud", "profile": { "auth": { "auth_url": "https://identity.open.softlayer.com" }, "volume_api_version": "2", "identity_api_version": "3", "regions": [ "london" ] } } os-client-config-1.16.0/os_client_config/vendors/internap.json000066400000000000000000000004351266264256200244530ustar00rootroot00000000000000{ "name": "internap", "profile": { "auth": { "auth_url": "https://identity.api.cloud.iweb.com" }, "regions": [ "ams01", "da01", "nyj01" ], "identity_api_version": "3", "image_api_version": "1", "floating_ip_source": "None" } } os-client-config-1.16.0/os_client_config/vendors/ovh.json000066400000000000000000000004131266264256200234230ustar00rootroot00000000000000{ "name": "ovh", "profile": { "auth": { "auth_url": "https://auth.cloud.ovh.net/" }, "regions": [ "BHS1", "GRA1", "SBG1" ], "identity_api_version": "3", "image_format": "raw", "floating_ip_source": "None" } } os-client-config-1.16.0/os_client_config/vendors/rackspace.json000066400000000000000000000011361266264256200245660ustar00rootroot00000000000000{ "name": "rackspace", "profile": { "auth": { "auth_url": "https://identity.api.rackspacecloud.com/v2.0/" }, "regions": [ "DFW", "HKG", "IAD", "ORD", "SYD", "LON" ], "database_service_type": "rax:database", "compute_service_name": "cloudServersOpenStack", "image_api_use_tasks": true, "image_format": "vhd", "floating_ip_source": "None", "secgroup_source": "None", "volume_api_version": "1", "disable_vendor_agent": { "vm_mode": "hvm", "xenapi_use_agent": false }, "has_network": false } } os-client-config-1.16.0/os_client_config/vendors/switchengines.json000066400000000000000000000004171266264256200255050ustar00rootroot00000000000000{ "name": "switchengines", "profile": { "auth": { "auth_url": "https://keystone.cloud.switch.ch:5000/v2.0" }, "regions": [ "LS", "ZH" ], "volume_api_version": "1", "image_api_use_tasks": true, "image_format": "raw" } } os-client-config-1.16.0/os_client_config/vendors/ultimum.json000066400000000000000000000003341266264256200243250ustar00rootroot00000000000000{ "name": "ultimum", "profile": { "auth": { "auth_url": "https://console.ultimum-cloud.com:5000/" }, "identity_api_version": "3", "volume_api_version": "1", "region-name": "RegionOne" } } os-client-config-1.16.0/os_client_config/vendors/unitedstack.json000066400000000000000000000004511266264256200251470ustar00rootroot00000000000000{ "name": "unitedstack", "profile": { "auth": { "auth_url": "https://identity.api.ustack.com/v3" }, "regions": [ "bj1", "gd1" ], "volume_api_version": "1", "identity_api_version": "3", "image_format": "raw", "floating_ip_source": "None" } } os-client-config-1.16.0/os_client_config/vendors/vexxhost.json000066400000000000000000000003671266264256200245270ustar00rootroot00000000000000{ "name": "vexxhost", "profile": { "auth": { "auth_url": "https://auth.vexxhost.net" }, "regions": [ "ca-ymq-1" ], "dns_api_version": "1", "identity_api_version": "3", "floating_ip_source": "None" } } os-client-config-1.16.0/os_client_config/vendors/zetta.json000066400000000000000000000003301266264256200237540ustar00rootroot00000000000000{ "name": "zetta", "profile": { "auth": { "auth_url": "https://identity.api.zetta.io/v3" }, "regions": [ "no-osl1" ], "identity_api_version": "3", "dns_api_version": "2" } } os-client-config-1.16.0/releasenotes/000077500000000000000000000000001266264256200174435ustar00rootroot00000000000000os-client-config-1.16.0/releasenotes/notes/000077500000000000000000000000001266264256200205735ustar00rootroot00000000000000os-client-config-1.16.0/releasenotes/notes/catch-up-release-notes-e385fad34e9f3d6e.yaml000066400000000000000000000015621266264256200303250ustar00rootroot00000000000000--- prelude: > Swiftclient instantiation now provides authentication information so that long lived swiftclient objects can reauthenticate if necessary. This should be a temporary situation until swiftclient supports keystoneauth sessions at which point os-client-config will instantiate swiftclient with a keystoneauth session. features: - Swiftclient instantiation now provides authentication information so that long lived swiftclient objects can reauthenticate if necessary. - Add support for explicit v2password auth type. - Add SSL support to VEXXHOST vendor profile. - Add zetta.io cloud vendor profile. fixes: - Fix bug where project_domain_{name,id} was set even if project_{name,id} was not set. other: - HPCloud vendor profile removed due to cloud shutdown. - RunAbove vendor profile removed due to migration to OVH. os-client-config-1.16.0/releasenotes/notes/session-client-b581a6e5d18c8f04.yaml000066400000000000000000000003061266264256200266350ustar00rootroot00000000000000--- features: - Added kwargs and argparse processing for session_client. deprecations: - Renamed simple_client to session_client. simple_client will remain as an alias for backwards compat. os-client-config-1.16.0/releasenotes/notes/started-using-reno-242e2b0cd27f9480.yaml000066400000000000000000000000631266264256200273410ustar00rootroot00000000000000--- other: - Started using reno for release notes. os-client-config-1.16.0/releasenotes/source/000077500000000000000000000000001266264256200207435ustar00rootroot00000000000000os-client-config-1.16.0/releasenotes/source/_static/000077500000000000000000000000001266264256200223715ustar00rootroot00000000000000os-client-config-1.16.0/releasenotes/source/_static/.placeholder000066400000000000000000000000001266264256200246420ustar00rootroot00000000000000os-client-config-1.16.0/releasenotes/source/_templates/000077500000000000000000000000001266264256200231005ustar00rootroot00000000000000os-client-config-1.16.0/releasenotes/source/_templates/.placeholder000066400000000000000000000000001266264256200253510ustar00rootroot00000000000000os-client-config-1.16.0/releasenotes/source/conf.py000066400000000000000000000204761266264256200222530ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Os-Client-Config Release Notes documentation build configuration file, created by # sphinx-quickstart on Thu Nov 5 11:50:32 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'oslosphinx', 'reno.sphinxext', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'os-client-config Release Notes' copyright = u'2015, os-client-config developers' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # import pbr.version occ_version = pbr.version.VersionInfo('os-client-config') # The short X.Y version. version = occ_version.canonical_version_string() # The full version, including alpha/beta/rc tags. release = occ_version.version_string_with_vcs() # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'OCCReleaseNotesdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'OCCReleaseNotes.tex', u'os-client-config Release Notes Documentation', u'os-client-config developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'occreleasenotes', u'os-client-config Release Notes Documentation', [u'os-client-config developers'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'OCCReleaseNotes', u'os-client-config Release Notes Documentation', u'os-client-config developers', 'OCCReleaseNotes', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False os-client-config-1.16.0/releasenotes/source/index.rst000066400000000000000000000003471266264256200226100ustar00rootroot00000000000000Welcome to Nova Release Notes documentation! ============================================== Contents ======== .. toctree:: :maxdepth: 2 unreleased Indices and tables ================== * :ref:`genindex` * :ref:`search` os-client-config-1.16.0/releasenotes/source/unreleased.rst000066400000000000000000000001531266264256200236230ustar00rootroot00000000000000============================ Current Series Release Notes ============================ .. release-notes:: os-client-config-1.16.0/requirements.txt000066400000000000000000000004541266264256200202410ustar00rootroot00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. PyYAML>=3.1.0 appdirs>=1.3.0 keystoneauth1>=2.1.0 requestsexceptions>=1.1.1 # Apache-2.0 os-client-config-1.16.0/setup.cfg000066400000000000000000000015601266264256200165750ustar00rootroot00000000000000[metadata] name = os-client-config summary = OpenStack Client Configuation Library description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = http://www.openstack.org/ classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.3 Programming Language :: Python :: 3.4 [files] packages = os_client_config [build_sphinx] source-dir = doc/source build-dir = doc/build all_files = 1 [upload_sphinx] upload-dir = doc/build/html [wheel] universal = 1 os-client-config-1.16.0/setup.py000077500000000000000000000014151266264256200164700ustar00rootroot00000000000000#!/usr/bin/env python # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # 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. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools setuptools.setup( setup_requires=['pbr'], pbr=True) os-client-config-1.16.0/test-requirements.txt000066400000000000000000000011541266264256200212140ustar00rootroot00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. hacking>=0.10.2,<0.11 # Apache-2.0 coverage>=3.6 extras fixtures>=0.3.14 discover jsonschema>=2.0.0,<3.0.0,!=2.5.0 mock>=1.2 python-glanceclient>=0.18.0 python-keystoneclient>=1.1.0 python-subunit>=0.0.18 sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3 oslosphinx>=2.5.0,<2.6.0 # Apache-2.0 oslotest>=1.5.1,<1.6.0 # Apache-2.0 reno>=0.1.1 # Apache2 testrepository>=0.0.18 testscenarios>=0.4 testtools>=0.9.36,!=1.2.0 os-client-config-1.16.0/tox.ini000066400000000000000000000016141266264256200162670ustar00rootroot00000000000000[tox] minversion = 1.6 envlist = py34,py27,pypy,pep8 skipsdist = True [testenv] usedevelop = True install_command = pip install -U {opts} {packages} setenv = VIRTUAL_ENV={envdir} deps = -r{toxinidir}/test-requirements.txt commands = python setup.py testr --slowest --testr-args='{posargs}' [testenv:pep8] commands = flake8 [testenv:venv] commands = {posargs} [testenv:cover] commands = python setup.py test --coverage --coverage-package-name=os_client_config --testr-args='{posargs}' [testenv:docs] deps = {[testenv]deps} readme commands = python setup.py build_sphinx python setup.py check -r -s [testenv:releasenotes] commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [flake8] show-source = True builtins = _ exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,releasenotes/source/conf.py