os-client-config-1.29.0/0000775000175100017510000000000013234622446014775 5ustar zuulzuul00000000000000os-client-config-1.29.0/releasenotes/0000775000175100017510000000000013234622446017466 5ustar zuulzuul00000000000000os-client-config-1.29.0/releasenotes/source/0000775000175100017510000000000013234622446020766 5ustar zuulzuul00000000000000os-client-config-1.29.0/releasenotes/source/_static/0000775000175100017510000000000013234622446022414 5ustar zuulzuul00000000000000os-client-config-1.29.0/releasenotes/source/_static/.placeholder0000666000175100017510000000000013234622123024657 0ustar zuulzuul00000000000000os-client-config-1.29.0/releasenotes/source/conf.py0000666000175100017510000002037713234622123022270 0ustar zuulzuul00000000000000# -*- 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' import openstackdocstheme # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ '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' # Release notes do not need a version in the title, they span # multiple versions. # The short X.Y version. version = '' # The full version, including alpha/beta/rc tags. release = '' # 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 = 'openstackdocs' # 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 = [] html_theme_path = [openstackdocstheme.get_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 # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] os-client-config-1.29.0/releasenotes/source/pike.rst0000666000175100017510000000021713234622123022442 0ustar zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike os-client-config-1.29.0/releasenotes/source/mitaka.rst0000666000175100017510000000023213234622123022755 0ustar zuulzuul00000000000000=================================== Mitaka Series Release Notes =================================== .. release-notes:: :branch: origin/stable/mitaka os-client-config-1.29.0/releasenotes/source/index.rst0000666000175100017510000000042113234622123022616 0ustar zuulzuul00000000000000================================ os-client-config Release Notes ================================ Contents ======== .. toctree:: :maxdepth: 2 unreleased pike ocata newton mitaka Indices and tables ================== * :ref:`genindex` * :ref:`search` os-client-config-1.29.0/releasenotes/source/unreleased.rst0000666000175100017510000000015313234622123023640 0ustar zuulzuul00000000000000============================ Current Series Release Notes ============================ .. release-notes:: os-client-config-1.29.0/releasenotes/source/_templates/0000775000175100017510000000000013234622446023123 5ustar zuulzuul00000000000000os-client-config-1.29.0/releasenotes/source/_templates/.placeholder0000666000175100017510000000000013234622123025366 0ustar zuulzuul00000000000000os-client-config-1.29.0/releasenotes/source/ocata.rst0000666000175100017510000000023013234622123022574 0ustar zuulzuul00000000000000=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: origin/stable/ocata os-client-config-1.29.0/releasenotes/source/newton.rst0000666000175100017510000000023213234622123023021 0ustar zuulzuul00000000000000=================================== Newton Series Release Notes =================================== .. release-notes:: :branch: origin/stable/newton os-client-config-1.29.0/releasenotes/notes/0000775000175100017510000000000013234622446020616 5ustar zuulzuul00000000000000os-client-config-1.29.0/releasenotes/notes/min-max-legacy-version-301242466ddefa93.yaml0000666000175100017510000000147313234622123030125 0ustar zuulzuul00000000000000--- features: - Add min_version and max_version to get_legacy_client and to get_session_endpoint. At the moment this is only really fully plumbed through for cinder, which has extra special fun around volume, volumev2 and volumev3. Min and max versions to both methods will look through the options available in the service catalog and try to return the latest one available from the span of requested versions. This means a user can say volume_api_version=None, min_version=2, max_version=3 will get an endpoint from get_session_endpoint or a Client from cinderclient that will be either v2 or v3 but not v1. In the future, min and max version for get_session_endpoint should be able to sort out appropriate endpoints via version discovery, but that does not currently exist. os-client-config-1.29.0/releasenotes/notes/network-list-e6e9dafdd8446263.yaml0000666000175100017510000000065013234622123026451 0ustar zuulzuul00000000000000--- features: - Support added for configuring metadata about networks for a cloud in a list of dicts, rather than in the external_network and internal_network entries. The dicts support a name, a routes_externally field, a nat_destination field and a default_interface field. deprecations: - external_network and internal_network are deprecated and should be replaced with the list of network dicts. os-client-config-1.29.0/releasenotes/notes/cloud-profile-status-e0d29b5e2f10e95c.yaml0000666000175100017510000000034313234622123030055 0ustar zuulzuul00000000000000--- features: - Add a field to vendor cloud profiles to indicate active, deprecated and shutdown status. A message to the user is triggered when attempting to use cloud with either deprecated or shutdown status. os-client-config-1.29.0/releasenotes/notes/make-rest-client-dd3d365632a26fa0.yaml0000666000175100017510000000022013234622123027037 0ustar zuulzuul00000000000000--- deprecations: - Renamed session_client to make_rest_client. session_client will continue to be supported for backwards compatability. os-client-config-1.29.0/releasenotes/notes/nat-source-field-7c7db2a724616d59.yaml0000666000175100017510000000046513234622123027005 0ustar zuulzuul00000000000000--- features: - Added nat_source flag for networks. In some more complex clouds there can not only be more than one valid network on a server that NAT can attach to, there can also be more than one valid network from which to get a NAT address. Allow flagging a network so that it can be found. os-client-config-1.29.0/releasenotes/notes/vendor-add-betacloud-03872c3485104853.yaml0000666000175100017510000000006013234622156027316 0ustar zuulzuul00000000000000--- other: - Add betacloud region for Germany os-client-config-1.29.0/releasenotes/notes/load-yaml-3177efca78e5c67a.yaml0000666000175100017510000000044013234622123025661 0ustar zuulzuul00000000000000--- features: - Added a flag, 'load_yaml_config' that defaults to True. If set to false, no clouds.yaml files will be loaded. This is beneficial if os-client-config wants to be used inside of a service where end-user clouds.yaml files would make things more confusing. os-client-config-1.29.0/releasenotes/notes/option-precedence-1fecab21fdfb2c33.yaml0000666000175100017510000000040413234622123027574 0ustar zuulzuul00000000000000--- fixes: - Reverse the order of option selction in ``OpenStackConfig._validate_auth()`` to prefer auth options passed in (from argparse) over those found in clouds.yaml. This allows the application to override config profile auth settings. os-client-config-1.29.0/releasenotes/notes/catch-up-release-notes-e385fad34e9f3d6e.yaml0000666000175100017510000000156213234622123030342 0ustar zuulzuul00000000000000--- 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.29.0/releasenotes/notes/fix-compat-with-old-keystoneauth-66e11ee9d008b962.yaml0000666000175100017510000000044513234622123032156 0ustar zuulzuul00000000000000--- issues: - Fixed a regression when using latest os-client-config with the keystoneauth from stable/newton. Although this isn't a super common combination, the added feature that broke the interaction is really not worthy of the incompatibility, so a workaround was added. os-client-config-1.29.0/releasenotes/notes/started-using-reno-242e2b0cd27f9480.yaml0000666000175100017510000000006313234622123027356 0ustar zuulzuul00000000000000--- other: - Started using reno for release notes. os-client-config-1.29.0/releasenotes/notes/sdk-helper-41f8d815cfbcfb00.yaml0000666000175100017510000000013513234622123026101 0ustar zuulzuul00000000000000--- features: - Added helper method for constructing OpenStack SDK Connection objects. os-client-config-1.29.0/releasenotes/notes/session-client-b581a6e5d18c8f04.yaml0000666000175100017510000000030613234622123026652 0ustar zuulzuul00000000000000--- 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.29.0/releasenotes/notes/make-rest-client-version-discovery-84125700f159491a.yaml0000666000175100017510000000032013234622123032237 0ustar zuulzuul00000000000000--- features: - Add version argument to make_rest_client and plumb version discovery through get_session_client so that versioned endpoints are properly found if unversioned are in the catalog. os-client-config-1.29.0/releasenotes/notes/vendor-updates-f11184ba56bb27cf.yaml0000666000175100017510000000022413234622123026720 0ustar zuulzuul00000000000000--- other: - Add citycloud regions for Buffalo, Frankfurt, Karlskrona and Los Angles - Add new DreamCompute cloud and deprecate DreamHost cloud os-client-config-1.29.0/releasenotes/notes/shade-helper-568f8cb372eef6d9.yaml0000666000175100017510000000013113234622123026352 0ustar zuulzuul00000000000000--- features: - Added helper method for constructing shade OpenStackCloud objects. os-client-config-1.29.0/releasenotes/notes/ironic-microversion-ba5b0f36f11196a6.yaml0000666000175100017510000000017013234622123027677 0ustar zuulzuul00000000000000--- features: - Add support for passing Ironic microversion to the ironicclient constructor in get_legacy_client. os-client-config-1.29.0/releasenotes/notes/magic-fixes-dca4ae4dac2441a8.yaml0000666000175100017510000000033713234622123026312 0ustar zuulzuul00000000000000--- fixes: - Refactor ``OpenStackConfig._fix_backward_madness()`` into ``OpenStackConfig.magic_fixes()`` that allows subclasses to inject more fixup magic into the flow during ``get_one_cloud()`` processing. os-client-config-1.29.0/releasenotes/notes/default-cloud-7ee0bcb9e5dd24b9.yaml0000666000175100017510000000042313234622123026664 0ustar zuulzuul00000000000000--- issues: - If there was only one cloud defined in clouds.yaml os-client-config was requiring the cloud parameter be passed. This is inconsistent with how the envvars cloud works which WILL work without setting the cloud parameter if it's the only cloud. os-client-config-1.29.0/ChangeLog0000664000175100017510000004012213234622445016545 0ustar zuulzuul00000000000000CHANGES ======= 1.29.0 ------ * Updated from global requirements * Split docs requirements and update tox.ini * Add betacloud to the vendors * Make the get\_service\_type() overrides tolernat of no defaults * Remove osc devstack tips jobs * Do not apply format expansions to passwords * Updated from global requirements * Remove setting of version/release from releasenotes * Add shade-functional-tips jobs * Consume the osc-functional-devstack-tips job * Fix doc typo * Added nat\_source flag for networks * Add shade-tox-tips jobs * Update make\_rest\_client to work with version discovery * Protect against p\_opt not having prompt attribute * Treat clouds.yaml with one cloud like envvars * Updated from global requirements * Fix requires\_floating\_ip * Updates for stestr * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * DataCentred supports Keystone V3 and Glance V2 * Update globals safely * Update the documentation link for doc migration * Remove OSIC * Update reno for stable/pike * Updated from global requirements * Manually sync with g-r 1.28.0 ------ * use openstackdocstheme html context * switch from oslosphinx to openstackdocstheme * turn on warning-is-error in documentation build * rearrange existing documentation to follow the new standard layout * Don't pop from os.environ * Keep a singleton to support multiple get\_config calls * Revert "Revert "Use interface not endpoint\_type for keystoneclient"" * Revert "Use interface not endpoint\_type for keystoneclient" * Add ironicclient to constructors list * Add helper method to fetch service catalog * Fix interactions with keystoneauth from newton 1.27.0 ------ * Make \_fix\_argv() somewhat compatible with Argparse action='append' * Add ability to pass in user\_agent * Docs: add a note about rackspace API keys * Remove out of date comment * Stop special-casing idenity catalog lookups * Add designateclient to constructors list * OVH supports qcow2 * Use interface not endpoint\_type for keystoneclient * Add support for bailing on invalid service versions * modify test-requirement according to requirements project * [Fix gate]Update test requirement * Pass ironic microversion through from api\_version * Add ability to skip yaml loading * Remove the keystoneclient auth fallback * Add support for overriding mistral service type * Add helper scripts to print version discovery info * Add support for indicating required floating IPs * Update reno for stable/ocata * fix location of team tags in README * Fix typo for baremetal\_service\_type 1.26.0 ------ * Revert "Fix interface\_key for identity clients" * Add support for Murano 1.25.0 ------ * Add OpenTelekomCloud to the vendors * Remove 3.4 from tox envlist * Use upper-constraints for tox envs * Update swift constructor to be Session aware * Magnum's service\_type is container\_infra * Add docutils contraint on 0.13.1 to fix building * Fix interface\_key for identity clients * Show team and repo badges on README 1.24.0 ------ * Revert "Remove validate\_auth\_ksc" 1.23.0 ------ * Remove validate\_auth\_ksc * Add fuga.io to vendors * Support token\_endpoint as an auth\_type * Add support for volumev3 service type * Normalize cloud config before osc-lib call * Fix a bunch of tests * Clarify how to set SSL settings * Update ECS image\_api\_version to 1 1.22.0 ------ * Revert "Split auth plugin loading into its own method" * Add setter for session constructor * Enable release notes translation * cloud\_config:get\_session\_endpoint: catch Keystone EndpointNotFound * Using assertIsNone() instead of assertEqual(None, ...) * Update homepage with developer documentation page * List py35 in the default tox env list * Fix AttributeError in \`get\_config\` * modify the home-page info with the developer documentation * Don't create envvars cloud if cloud or region are set * Don't build releasenotes in normal docs build * Update reno for stable/newton * Add ability to configure Session constructor * Split auth plugin loading into its own method 1.21.1 ------ * Go ahead and handle YAML list in region\_name 1.21.0 ------ * Add prompting for KSA options * Clean up vendor support list 1.20.1 ------ * Precedence final solution 1.20.0 ------ * Add support for configuring split-stack networks * Pop domain-id from the config if we infer values * Update Internap information 1.19.1 ------ * Add test for precedence rules * Pass the argparse data into to validate\_auth * Revert "Fix precedence for pass-in options" 1.19.0 ------ * Add release notes for 1.19.0 release * Add the new DreamCompute cloud * Fix precedence for pass-in options * Update citycloud to list new regions * Add support for listing a cloud as shut down * Add support for deprecating cloud profiles * Refactor fix magic in get\_one\_cloud() 1.18.0 ------ * Reword the entries in the README a bit * Add shade constructor helper method * Rename session\_client to make\_rest\_client * Add helper method for OpenStack SDK constructor * Add missing "cloud" argument to \_validate\_auth\_ksc * Workaround bad required params in troveclient * Trivial: Remove 'MANIFEST.in' * Trivial: remove openstack/common from flake8 exclude list * drop python3.3 support in classifier * Fix formatting in readme file * Remove discover from test-requirements.txt * Add version string 1.17.0 ------ * Pull the network settings from the actual dict * Clarify one-per-cloud network values * Flesh out netowrk config list * Change network info indication to a generic list * Update reno for stable/mitaka * Add osic vendor profile 1.16.0 ------ * Update the README a bit * Allow session\_client to take the same args as make\_client * Fix formulation 1.15.0 ------ * Add release notes * Send swiftclient username/password and token * Remove HP and RunAbove from vendor profiles * Added SSL support for VEXXHOST * Add support for zetta.io * Stop ignoring v2password plugin * Go ahead and remove final excludes * Don't set project\_domain if not project scoped * Clean up removed hacking rule from [flake8] ignore lists * set up release notes build 1.14.0 ------ * Fix a precedence problem with auth arguments * Return empty dict instead of None for lack of file * Pass version arg by name not position * Use \_get\_client in make\_client helper function * Add barbicanclient support * Remove openstack-common.conf * Add IBM Public Cloud * Replace assertEqual(None, \*) with assertIsNone in tests * Update auth urls and identity API versions * Stop hardcoding compute in simple\_client * Update volume API default version from v1 to v2 * Debug log a deferred keystone exception, else we mask some useful diag * Fix README.rst, add a check for it to fit PyPI rules * Use reno for release notes * add URLs for release announcement tools * Allow filtering clouds on command line 1.13.1 ------ * Munge region\_name to '' if set to None * Fix some README typos * Fix token\_endpoint usage * remove python 2.6 os-client-config classifier * If cloud doesn't list regions expand passed name 1.13.0 ------ * Fix glance endpoints with endpoint\_override * Allow passing in explicit version for legacy\_client * Pass endpoint override to constructors * Support backwards compat for \_ args * Add backwards compat mapping for auth-token * Replace assertEqual(None, \*) with assertIsNone in tests * Allow arbitrary client-specific options * Add ceilometer constructor to known constructors * Add support for generalized per-region settings * Fix a README typo - hepler is not actually a thing * Make client constructor optional * Updated README to clarify legacy client usage * Add simple helper function for client construction * Add method for registering argparse options * Update vexxhost to Identity v3 * Make sure that cloud always has a name * Remove optional keystoneauth1 imports 1.12.0 ------ * Add BHS1 to OVH * Handle cinder v2 * Add support for secure.yaml file for auth info 1.11.1 ------ * Fix lack of parenthesis around boolean logic 1.11.0 ------ * Only pass timeout to swift if we have a value * Fix name of the object-store api key * Refactor per-service key making * Add support for legacy envvar prefixes * Fix JSON schema 1.10.2 ------ * Workaround a dispute between osc and neutronclient * Workaround for int value with verbose\_level * Remove unneeded workaround for ksc * Add default API version for magnum service 1.10.1 ------ * Work around a bug in keystoneclient constructor * Return cache settings as numbers not strings * Add method to get a mounted session from config 1.10.0 ------ * Convert floats to string * Don't assume pass\_version\_arg=False for network * Update network api version in defaults.json * Dont turn bools into strings * Use requestsexceptions for urllib squelching * Normalize int config values to string * Disable spurious urllib warnings * Add logging module support * Add methods for getting Session and Client objects * Update conoha's vendor profile to include SJC * Use json for in-tree cloud data * Update auro to indicate move to neutron * Copy values in backwards\_interface differently * Fix typo in Catalyst region configs * Aligned a few words in the docs * Sort vendor list * Add conoha public cloud * Allow for templated variables in auth\_url * Use assertDictEqual to test dict equality * Always pull regions from vendor profiles 1.9.0 ----- * Clean up cache interface, add support for services * Add Rackspace LON region * Validate requested region against region list * Fix documentation around regions * Add an API reference to the docs * Pass OpenStackConfig in to CloudConfig for caches * Add auth hook for OpenStackClient * Adds some lines to complete table formatting 1.8.1 ----- * Add universal=1 to setup.cfg to build python 3 wheels * Some cleanup 1.8.0 ----- * Fix two typos * Put in override for Rackspace broken neutron * Support passing force\_ipv4 to the constructor * identity version is 2.0 * Handle OS\_CLOUD and OS\_REGION\_NAME friendly-like * Added SWITCHengines vendor file * update RST for readme so pypi looks pretty 1.7.5 ----- * Fix a little error with the None auth type * Add support for Catalyst as vendor * Change ignore-errors to ignore\_errors 1.7.4 ----- * Handle ksa opt with no deprecated field 1.7.3 ----- * Fall back to keystoneclient arg processing * Fix typo in ovh region names 1.7.2 ----- * Move plugin loader creation to try block 1.7.1 ----- * Convert auth kwargs '-' to '\_' * Properly handle os- prefixed args in fix\_args * Test kwargs passing not just argparse 1.7.0 ----- * Allow configuring domain id once * Add internap to the vendor list * Fix typo in comment - we use ksa not ksc * Defer plugin validation to keystoneauth 1.6.4 ----- * Remove an extra line * Add Datacentred to the vendor list * Add ultimum to list of vendors * Add Enter Cloud Suite to vendors list * Add elastx to vendor support matrix * Switch the image default to v2 * Update auro auth\_url and region information * Add citycloud to the vendors list * Return keystoneauth plugins based on auth args * Remove duplicate lines that are the same as default * Add default version number for heat * Update OVH public cloud information * Handle empty defaults.yaml file * Do not treat project\_name and project\_id the same * Revert "Revert "Use the correct auth\_plugin for token authentication"" 1.6.3 ----- * Revert "Use the correct auth\_plugin for token authentication" 1.6.2 ----- * Ignore infra CI env vars * Use the correct auth\_plugin for token authentication 1.6.1 ----- * Align to generic password auth-type 1.6.0 ----- * Clarify floating ip use for vendors * Add per-service endpoint overrides 1.5.0 ----- * Remove requirements.txt from tox.ini * Remove py26 and py33 from tox.ini * Rename 'endpoint\_type' to 'interface' * Have service name default to None * Remove region list from single cloud * Fix set\_default() when used before config init * Specify the config file with environment variable * Add support for configuring region lists with yaml * Fix rendering issue in Readme * Clean up vendor data * Add support for indicating preference for IPv6 * Normalize project\_name aliases 1.4.0 ----- * Add some accessor methods to CloudConfig * Add missing tests * Add test to check cert and key as a tuple * Use one yaml file per vendor * Raise warning when a vendor profile is missing * Some cleanup in the README.rst * Add support for OVH Public Cloud * Add SSL documentation to README.rst * Stringify project details * Raise a warning with conflicting SSL params * Change references of "clouds.yaml" for real file * Raise a warning when using 'cloud' in config * Add cloud vendor files config in doc 1.3.0 ----- * Provide a helper method to get requests ssl values * Add more defaults to our defaults file * Change naming in vendor doc to match vendors.py * Add auro to list of known vendors * Add list of image params needed to disable agents 1.2.0 ----- * Add set\_one\_cloud method * Add tests for get\_cloud\_names * Add flag to indicate handling of security groups * Don't normalize too deeply 1.1.0 ----- * Add tests for cloud config comparison * Add inequality method * Add an equality method for CloudConfig * Capture the filename used for config * Normalize all keys down to \_ instead of - * Expose method for getting a list of cloud names * Rename cloud to profile * Don't pass None as the cloud name 1.0.0 ----- * Change overriding defaults to kwarg * Add tests for OSC usage * Use appdirs for platform-independent locations * Add UnitedStack * Expose function to get defaults dict * Add default versions for trove and ironic * Sort defaults list for less conflicts * Only add fall through cloud as a fall through 0.8.2 ----- * Add flag to indicate where floating ips come from * get\_one\_cloud should use auth merge * Also accept .yml as a suffix * Remove crufty lines from README * Update vendor support to reflect v2 non-task 0.8.1 ----- * Add flag to trigger task interface for rackspace 0.8.0 ----- * Document vendor support information * Reset cache default to 0 * add .venv to gitignore * Move region\_names out of auth dict * Add runabove to vendors * Add image information to vexxhost account * Add vexxhost * Add DreamCompute to vendors list * Allow overriding envvars as the name of the cloud * Put env vars into their own cloud config * Add keystoneclient to test-requirements * Actually put some content into our sphinx docs 0.7.0 ----- * Update .gitreview for git section rename * Change dogpile cache defaults * Add cover to .gitignore 0.6.0 ----- * Flesh out api version defaults * Handle project\_name/tenant\_name in the auth dict * Add two newlines to the ends of files * Rename auth\_plugin to auth\_type 0.5.0 ----- * Add support for configuring dogpile.cache * Fix coverage report * Add more testing of vendor yaml loading * More comprehensive unit tests for os-client-config * Allow keystone validation bypass for noauth usage * Add basic unit test for config 0.4.3 ----- * Prefer dest value when option is depricated * Allow region\_name to be None 0.4.2 ----- * Don't return the auth dict inside the loop * Make sure we're deep-copying the auth dict 0.4.1 ----- * Remove runtime depend on pbr * Provide Rackspace service\_name override 0.4.0 ----- * Start keeping default versions for all services * Support keystone auth plugins in a generic way * Replace defaults\_dict with scanning env vars * Workflow documentation is now in infra-manual * Corrections to readme * Use yaml.safe\_load instead of load * Throw error if a non-existent cloud is requested 0.3.0 ----- * Fix a missed argument from a previous refactor * Map CloudConfig attributes to CloudConfig.config * Add support for argparse Namespace objects * Add support for command line argument processing * Handle lack of username for project\_name defaults * Handle the project/tenant nonesense more cleanly * Add cache control settings * Handle no vendor clouds config files * Remove unused class method get\_services * Add clouds-public.yaml * Prep for move to stackforge 0.2.0 ----- * Handle missing vendor key * Make env vars lowest priority * Handle null region * Update the README file for more completeness 0.1.0 ----- * Get rid of extra complexity with service values * Remove babel and add pyyaml * Port in config reading from shade * Initial Cookiecutter Commit os-client-config-1.29.0/tox.ini0000666000175100017510000000364313234622156016316 0ustar zuulzuul00000000000000[tox] minversion = 1.6 envlist = py35,py27,pypy,pep8 skipsdist = True [testenv] usedevelop = True passenv = UPPER_CONSTRAINTS_FILE install_command = pip install -U {opts} {packages} setenv = VIRTUAL_ENV={envdir} LANG=en_US.UTF-8 LANGUAGE=en_US:en LC_ALL=C OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = stestr run {posargs} stestr slowest [testenv:pep8] usedevelop = False skip_install = True deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} doc8 hacking pygments readme commands = doc8 doc/source python setup.py check -r -s flake8 os_client_config [testenv:venv] commands = {posargs} [testenv:cover] setenv = {[testenv]setenv} PYTHON=coverage run --source os_client_config --parallel-mode commands = stestr run {posargs} coverage combine coverage html -d cover coverage xml -o cover/coverage.xml [testenv:docs] skip_install = True deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = sphinx-build -W -d doc/build/doctrees -b html doc/source/ doc/build/html [testenv:releasenotes] skip_install = True deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/doc/requirements.txt 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,*lib/python*,*egg,build,releasenotes/source/conf.py os-client-config-1.29.0/setup.py0000666000175100017510000000200613234622123016477 0ustar zuulzuul00000000000000# 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 # In python < 2.7.4, a lazy loading of package `pbr` will break # setuptools if some other modules registered functions in `atexit`. # solution from: http://bugs.python.org/issue15881#msg170215 try: import multiprocessing # noqa except ImportError: pass setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) os-client-config-1.29.0/PKG-INFO0000664000175100017510000000403713234622446016076 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: os-client-config Version: 1.29.0 Summary: OpenStack Client Configuation Library Home-page: https://docs.openstack.org/os-client-config/latest Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description-Content-Type: UNKNOWN Description: ================ os-client-config ================ .. image:: http://governance.openstack.org/badges/os-client-config.svg :target: http://governance.openstack.org/reference/tags/index.html `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 Source ------ * Free software: Apache license * Documentation: http://docs.openstack.org/os-client-config/latest * Source: http://git.openstack.org/cgit/openstack/os-client-config * Bugs: http://bugs.launchpad.net/os-client-config Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 os-client-config-1.29.0/requirements.txt0000666000175100017510000000051313234622123020252 0ustar zuulzuul00000000000000# 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.10 # MIT appdirs>=1.3.0 # MIT License keystoneauth1>=3.3.0 # Apache-2.0 requestsexceptions>=1.2.0 # Apache-2.0 os-client-config-1.29.0/AUTHORS0000664000175100017510000000430413234622445016045 0ustar zuulzuul00000000000000Alex Gaynor Alvaro Lopez Garcia Andreas Jaeger Andy Botting Anita Kuno Arie Bregman Cao Xuan Hoang Cedric Brandily ChangBo Guo(gcb) Chris Church Christian Berendt Clark Boylan Clint Byrum Colleen Murphy David Shrewsbury Davide Guerri Dean Troyer Dean Troyer Dirk Mueller Doug Hellmann Doug Wiegley Eric Harney Flavio Percoco Ghe Rivero Gregory Haynes Ilya Shakhat James E. Blair Jamie Lennox Javier Pena Jeremy Stanley Jim Rollenhagen John Dennis Jordan Pittier Joshua Harlow Julia Kreger LiuNanke Mathieu Gagné Matthew Booth Mohammed Naser Monty Taylor OpenStack Release Bot Paulo Matias Sean Handley Shuquan Huang Simon Leinen Spencer Krum Steve Martinelli Steve Martinelli TerryHowe Thomas Bechtold Tim Burke Tony Xu Xav Paice Yaguang Tang Yuriy Taraday Zuul avnish lifeless lingyongxu ricolin xhzhf os-client-config-1.29.0/.coveragerc0000666000175100017510000000021213234622123017103 0ustar zuulzuul00000000000000[run] branch = True source = os_client_config omit = os_client_config/tests/*,os_client_config/openstack/* [report] ignore_errors = True os-client-config-1.29.0/os_client_config.egg-info/0000775000175100017510000000000013234622446021773 5ustar zuulzuul00000000000000os-client-config-1.29.0/os_client_config.egg-info/dependency_links.txt0000664000175100017510000000000113234622445026040 0ustar zuulzuul00000000000000 os-client-config-1.29.0/os_client_config.egg-info/PKG-INFO0000664000175100017510000000403713234622445023073 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: os-client-config Version: 1.29.0 Summary: OpenStack Client Configuation Library Home-page: https://docs.openstack.org/os-client-config/latest Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description-Content-Type: UNKNOWN Description: ================ os-client-config ================ .. image:: http://governance.openstack.org/badges/os-client-config.svg :target: http://governance.openstack.org/reference/tags/index.html `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 Source ------ * Free software: Apache license * Documentation: http://docs.openstack.org/os-client-config/latest * Source: http://git.openstack.org/cgit/openstack/os-client-config * Bugs: http://bugs.launchpad.net/os-client-config Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 os-client-config-1.29.0/os_client_config.egg-info/requires.txt0000664000175100017510000000011313234622445024365 0ustar zuulzuul00000000000000PyYAML>=3.10 appdirs>=1.3.0 keystoneauth1>=3.3.0 requestsexceptions>=1.2.0 os-client-config-1.29.0/os_client_config.egg-info/pbr.json0000664000175100017510000000005613234622445023451 0ustar zuulzuul00000000000000{"git_version": "ed71a59", "is_release": true}os-client-config-1.29.0/os_client_config.egg-info/SOURCES.txt0000664000175100017510000000725713234622446023672 0ustar zuulzuul00000000000000.coveragerc .mailmap .stestr.conf .zuul.yaml AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE README.rst requirements.txt setup.cfg setup.py test-requirements.txt tox.ini doc/requirements.txt doc/source/conf.py doc/source/index.rst doc/source/contributor/index.rst doc/source/install/index.rst doc/source/reference/index.rst doc/source/user/configuration.rst doc/source/user/index.rst doc/source/user/network-config.rst doc/source/user/releasenotes.rst doc/source/user/using.rst doc/source/user/vendor-support.rst os_client_config/__init__.py os_client_config/_log.py os_client_config/cloud_config.py os_client_config/config.py os_client_config/constructors.json os_client_config/constructors.py os_client_config/defaults.json os_client_config/defaults.py os_client_config/exceptions.py os_client_config/schema.json os_client_config/vendor-schema.json os_client_config.egg-info/PKG-INFO os_client_config.egg-info/SOURCES.txt os_client_config.egg-info/dependency_links.txt os_client_config.egg-info/not-zip-safe os_client_config.egg-info/pbr.json os_client_config.egg-info/requires.txt os_client_config.egg-info/top_level.txt os_client_config/tests/__init__.py os_client_config/tests/base.py os_client_config/tests/test_cloud_config.py os_client_config/tests/test_config.py os_client_config/tests/test_environ.py os_client_config/tests/test_init.py os_client_config/tests/test_json.py os_client_config/vendors/__init__.py os_client_config/vendors/auro.json os_client_config/vendors/betacloud.json os_client_config/vendors/bluebox.json os_client_config/vendors/catalyst.json os_client_config/vendors/citycloud.json os_client_config/vendors/conoha.json os_client_config/vendors/datacentred.json os_client_config/vendors/dreamcompute.json os_client_config/vendors/dreamhost.json os_client_config/vendors/elastx.json os_client_config/vendors/entercloudsuite.json os_client_config/vendors/fuga.json os_client_config/vendors/ibmcloud.json os_client_config/vendors/internap.json os_client_config/vendors/otc.json os_client_config/vendors/ovh.json os_client_config/vendors/rackspace.json os_client_config/vendors/switchengines.json os_client_config/vendors/ultimum.json os_client_config/vendors/unitedstack.json os_client_config/vendors/vexxhost.json os_client_config/vendors/zetta.json releasenotes/notes/catch-up-release-notes-e385fad34e9f3d6e.yaml releasenotes/notes/cloud-profile-status-e0d29b5e2f10e95c.yaml releasenotes/notes/default-cloud-7ee0bcb9e5dd24b9.yaml releasenotes/notes/fix-compat-with-old-keystoneauth-66e11ee9d008b962.yaml releasenotes/notes/ironic-microversion-ba5b0f36f11196a6.yaml releasenotes/notes/load-yaml-3177efca78e5c67a.yaml releasenotes/notes/magic-fixes-dca4ae4dac2441a8.yaml releasenotes/notes/make-rest-client-dd3d365632a26fa0.yaml releasenotes/notes/make-rest-client-version-discovery-84125700f159491a.yaml releasenotes/notes/min-max-legacy-version-301242466ddefa93.yaml releasenotes/notes/nat-source-field-7c7db2a724616d59.yaml releasenotes/notes/network-list-e6e9dafdd8446263.yaml releasenotes/notes/option-precedence-1fecab21fdfb2c33.yaml releasenotes/notes/sdk-helper-41f8d815cfbcfb00.yaml releasenotes/notes/session-client-b581a6e5d18c8f04.yaml releasenotes/notes/shade-helper-568f8cb372eef6d9.yaml releasenotes/notes/started-using-reno-242e2b0cd27f9480.yaml releasenotes/notes/vendor-add-betacloud-03872c3485104853.yaml releasenotes/notes/vendor-updates-f11184ba56bb27cf.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/mitaka.rst releasenotes/source/newton.rst releasenotes/source/ocata.rst releasenotes/source/pike.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder tools/keystone_version.py tools/nova_version.pyos-client-config-1.29.0/os_client_config.egg-info/top_level.txt0000664000175100017510000000002113234622445024515 0ustar zuulzuul00000000000000os_client_config os-client-config-1.29.0/os_client_config.egg-info/not-zip-safe0000664000175100017510000000000113234622432024214 0ustar zuulzuul00000000000000 os-client-config-1.29.0/.mailmap0000666000175100017510000000013013234622123016402 0ustar zuulzuul00000000000000# Format is: # # os-client-config-1.29.0/.stestr.conf0000666000175100017510000000004113234622123017233 0ustar zuulzuul00000000000000[DEFAULT] test_path=. top_dir=./ os-client-config-1.29.0/tools/0000775000175100017510000000000013234622446016135 5ustar zuulzuul00000000000000os-client-config-1.29.0/tools/nova_version.py0000666000175100017510000000410713234622123021213 0ustar zuulzuul00000000000000# Copyright (c) 2017 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import os_client_config ran = [] for cloud in os_client_config.OpenStackConfig().get_all_clouds(): if cloud.name in ran: continue ran.append(cloud.name) c = cloud.get_session_client('compute') try: raw_endpoint = c.get_endpoint() have_current = False endpoint = raw_endpoint.rsplit('/', 2)[0] print(endpoint) r = c.get(endpoint).json() except Exception: print("Error with %s" % cloud.name) continue for version in r['versions']: if version['status'] == 'CURRENT': have_current = True print( "\tVersion ID: {id} updated {updated}".format( id=version.get('id'), updated=version.get('updated'))) print( "\tVersion Max: {max}".format(max=version.get('version'))) print( "\tVersion Min: {min}".format(min=version.get('min_version'))) if not have_current: for version in r['versions']: if version['status'] == 'SUPPORTED': have_current = True print( "\tVersion ID: {id} updated {updated}".format( id=version.get('id'), updated=version.get('updated'))) print( "\tVersion Max: {max}".format(max=version.get('version'))) print( "\tVersion Min: {min}".format( min=version.get('min_version'))) os-client-config-1.29.0/tools/keystone_version.py0000666000175100017510000000542513234622123022115 0ustar zuulzuul00000000000000# Copyright (c) 2017 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import os_client_config import pprint import sys import urlparse def print_versions(r): if 'version' in r: for version in r['version']: print_version(version) if 'values' in r: for version in r['values']: print_version(version) if isinstance(r, list): for version in r: print_version(version) def print_version(version): if version['status'] in ('CURRENT', 'stable'): print( "\tVersion ID: {id} updated {updated}".format( id=version.get('id'), updated=version.get('updated'))) verbose = '-v' in sys.argv ran = [] for cloud in os_client_config.OpenStackConfig().get_all_clouds(): if cloud.name in ran: continue ran.append(cloud.name) # We don't actually need a compute client - but we'll be getting full urls # anyway. Without this SSL cert info becomes wrong. c = cloud.get_session_client('compute') endpoint = cloud.config['auth']['auth_url'] try: print(endpoint) r = c.get(endpoint).json() if verbose: pprint.pprint(r) except Exception as e: print("Error with {cloud}: {e}".format(cloud=cloud.name, e=str(e))) continue if 'version' in r: print_version(r['version']) url = urlparse.urlparse(endpoint) parts = url.path.split(':') if len(parts) == 2: path, port = parts else: path = url.path port = None stripped = path.rsplit('/', 2)[0] if port: stripped = '{stripped}:{port}'.format(stripped=stripped, port=port) endpoint = urlparse.urlunsplit( (url.scheme, url.netloc, stripped, url.params, url.query)) print(" also {endpoint}".format(endpoint=endpoint)) try: r = c.get(endpoint).json() if verbose: pprint.pprint(r) except Exception: print("\tUnauthorized") continue if 'version' in r: print_version(r) elif 'versions' in r: print_versions(r['versions']) else: print("\n\nUNKNOWN\n\n{r}".format(r=r)) else: print_versions(r['versions']) os-client-config-1.29.0/os_client_config/0000775000175100017510000000000013234622446020301 5ustar zuulzuul00000000000000os-client-config-1.29.0/os_client_config/_log.py0000666000175100017510000000147613234622123021575 0ustar zuulzuul00000000000000# 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.29.0/os_client_config/vendor-schema.json0000666000175100017510000001617313234622123023731 0ustar zuulzuul00000000000000{ "$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" ] }, "message": { "name": "Status message", "description": "Optional message with information related to status", "type": "string" }, "requires_floating_ip": { "name": "Requires Floating IP", "description": "Whether the cloud requires a floating IP to route traffic off of the cloud", "default": null, "type": ["boolean", "null"] }, "secgroup_source": { "name": "Security Group Source", "description": "Which service provides security groups", "enum": [ "neutron", "nova", "None" ], "default": "neutron" }, "status": { "name": "Vendor status", "description": "Status of the vendor's cloud", "enum": [ "active", "deprecated", "shutdown"], "default": "active" }, "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_service_type": { "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.29.0/os_client_config/constructors.json0000666000175100017510000000124513234622123023740 0ustar zuulzuul00000000000000{ "application-catalog": "muranoclient.client.Client", "baremetal": "ironicclient.client.Client", "compute": "novaclient.client.Client", "container-infra": "magnumclient.client.Client", "database": "troveclient.client.Client", "dns": "designateclient.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.29.0/os_client_config/vendors/0000775000175100017510000000000013234622446021761 5ustar zuulzuul00000000000000os-client-config-1.29.0/os_client_config/vendors/auro.json0000666000175100017510000000032213234622123023611 0ustar zuulzuul00000000000000{ "name": "auro", "profile": { "auth": { "auth_url": "https://api.van1.auro.io:5000/v2.0" }, "identity_api_version": "2", "region_name": "van1", "requires_floating_ip": true } } os-client-config-1.29.0/os_client_config/vendors/fuga.json0000666000175100017510000000045013234622123023567 0ustar zuulzuul00000000000000{ "name": "fuga", "profile": { "auth": { "auth_url": "https://identity.api.fuga.io:5000", "user_domain_name": "Default", "project_domain_name": "Default" }, "regions": [ "cystack" ], "identity_api_version": "3", "volume_api_version": "3" } } os-client-config-1.29.0/os_client_config/vendors/ultimum.json0000666000175100017510000000033413234622123024342 0ustar zuulzuul00000000000000{ "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.29.0/os_client_config/vendors/elastx.json0000666000175100017510000000026013234622123024144 0ustar zuulzuul00000000000000{ "name": "elastx", "profile": { "auth": { "auth_url": "https://ops.elastx.net:5000" }, "identity_api_version": "3", "region_name": "regionOne" } } os-client-config-1.29.0/os_client_config/vendors/betacloud.json0000666000175100017510000000040013234622156024610 0ustar zuulzuul00000000000000{ "name": "betacloud", "profile": { "auth": { "auth_url": "https://api-1.betacloud.io:5000/v3" }, "regions": [ "betacloud-1" ], "identity_api_version": "3", "image_format": "raw", "volume_api_version": "3" } } os-client-config-1.29.0/os_client_config/vendors/rackspace.json0000666000175100017510000000120113234622123024574 0ustar zuulzuul00000000000000{ "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", "requires_floating_ip": false, "volume_api_version": "1", "disable_vendor_agent": { "vm_mode": "hvm", "xenapi_use_agent": false }, "has_network": false } } os-client-config-1.29.0/os_client_config/vendors/vexxhost.json0000666000175100017510000000043213234622123024535 0ustar zuulzuul00000000000000{ "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", "requires_floating_ip": false } } os-client-config-1.29.0/os_client_config/vendors/datacentred.json0000666000175100017510000000032713234622123025126 0ustar zuulzuul00000000000000{ "name": "datacentred", "profile": { "auth": { "auth_url": "https://compute.datacentred.io:5000" }, "region-name": "sal01", "identity_api_version": "3", "image_api_version": "2" } } os-client-config-1.29.0/os_client_config/vendors/bluebox.json0000666000175100017510000000015213234622123024304 0ustar zuulzuul00000000000000{ "name": "bluebox", "profile": { "volume_api_version": "1", "region_name": "RegionOne" } } os-client-config-1.29.0/os_client_config/vendors/__init__.py0000666000175100017510000000255413234622123024072 0ustar zuulzuul00000000000000# 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.29.0/os_client_config/vendors/conoha.json0000666000175100017510000000033613234622123024117 0ustar zuulzuul00000000000000{ "name": "conoha", "profile": { "auth": { "auth_url": "https://identity.{region_name}.conoha.io" }, "regions": [ "sin1", "sjc1", "tyo1" ], "identity_api_version": "2" } } os-client-config-1.29.0/os_client_config/vendors/dreamhost.json0000666000175100017510000000051313234622123024633 0ustar zuulzuul00000000000000{ "name": "dreamhost", "profile": { "status": "deprecated", "message": "The dreamhost profile is deprecated. Please use the dreamcompute profile instead", "auth": { "auth_url": "https://keystone.dream.io" }, "identity_api_version": "3", "region_name": "RegionOne", "image_format": "raw" } } os-client-config-1.29.0/os_client_config/vendors/otc.json0000666000175100017510000000034313234622123023433 0ustar zuulzuul00000000000000{ "name": "otc", "profile": { "auth": { "auth_url": "https://iam.%(region_name)s.otc.t-systems.com/v3" }, "regions": [ "eu-de" ], "identity_api_version": "3", "image_format": "vhd" } } os-client-config-1.29.0/os_client_config/vendors/dreamcompute.json0000666000175100017510000000032013234622123025326 0ustar zuulzuul00000000000000{ "name": "dreamcompute", "profile": { "auth": { "auth_url": "https://iad2.dream.io:5000" }, "identity_api_version": "3", "region_name": "RegionOne", "image_format": "raw" } } os-client-config-1.29.0/os_client_config/vendors/catalyst.json0000666000175100017510000000042413234622123024472 0ustar zuulzuul00000000000000{ "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.29.0/os_client_config/vendors/unitedstack.json0000666000175100017510000000045113234622123025164 0ustar zuulzuul00000000000000{ "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.29.0/os_client_config/vendors/switchengines.json0000666000175100017510000000041713234622123025522 0ustar zuulzuul00000000000000{ "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.29.0/os_client_config/vendors/internap.json0000666000175100017510000000043513234622123024470 0ustar zuulzuul00000000000000{ "name": "internap", "profile": { "auth": { "auth_url": "https://identity.api.cloud.iweb.com" }, "regions": [ "ams01", "da01", "nyj01", "sin01", "sjc01" ], "identity_api_version": "3", "floating_ip_source": "None" } } os-client-config-1.29.0/os_client_config/vendors/citycloud.json0000666000175100017510000000051313234622123024644 0ustar zuulzuul00000000000000{ "name": "citycloud", "profile": { "auth": { "auth_url": "https://identity1.citycloud.com:5000/v3/" }, "regions": [ "Buf1", "La1", "Fra1", "Lon1", "Sto2", "Kna1" ], "requires_floating_ip": true, "volume_api_version": "1", "identity_api_version": "3" } } os-client-config-1.29.0/os_client_config/vendors/ibmcloud.json0000666000175100017510000000034013234622123024441 0ustar zuulzuul00000000000000{ "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.29.0/os_client_config/vendors/zetta.json0000666000175100017510000000033013234622123023771 0ustar zuulzuul00000000000000{ "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.29.0/os_client_config/vendors/ovh.json0000666000175100017510000000036013234622123023441 0ustar zuulzuul00000000000000{ "name": "ovh", "profile": { "auth": { "auth_url": "https://auth.cloud.ovh.net/" }, "regions": [ "BHS1", "GRA1", "SBG1" ], "identity_api_version": "3", "floating_ip_source": "None" } } os-client-config-1.29.0/os_client_config/vendors/entercloudsuite.json0000666000175100017510000000044513234622123026067 0ustar zuulzuul00000000000000{ "name": "entercloudsuite", "profile": { "auth": { "auth_url": "https://api.entercloudsuite.com/" }, "identity_api_version": "3", "image_api_version": "1", "volume_api_version": "1", "regions": [ "it-mil1", "nl-ams1", "de-fra1" ] } } os-client-config-1.29.0/os_client_config/defaults.py0000666000175100017510000000340413234622123022455 0ustar zuulzuul00000000000000# 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 import threading _json_path = os.path.join( os.path.dirname(os.path.realpath(__file__)), 'defaults.json') _defaults = None _defaults_lock = threading.Lock() def get_defaults(): global _defaults if _defaults is not None: return _defaults.copy() with _defaults_lock: if _defaults is not None: # Did someone else just finish filling it? return _defaults.copy() # Python language specific defaults # These are defaults related to use of python libraries, they are # not qualities of a cloud. # # NOTE(harlowja): update a in-memory dict, before updating # the global one so that other callers of get_defaults do not # see the partially filled one. tmp_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: tmp_defaults.update(updates) _defaults = tmp_defaults return tmp_defaults.copy() os-client-config-1.29.0/os_client_config/config.py0000666000175100017510000014216413234622123022122 0ustar zuulzuul00000000000000# 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 re 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') FORMAT_EXCLUSIONS = frozenset(['password']) # 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 value is None: return False 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 keys are selectors or behavior modification, don't # return anything selectors = set([ 'OS_CLOUD', 'OS_REGION_NAME', 'OS_CLIENT_CONFIG_FILE', 'OS_CLIENT_SECURE_FILE', 'OS_CLOUD_NAME']) if set(environkeys) - selectors: return ret return None 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 the value starts with '--' and has '-' or '_' in it, then # it's worth looking at it if re.match('^--.*(_|-)+.*', argv[index]): 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, pw_func=None, session_constructor=None, app_name=None, app_version=None, load_yaml_config=True): self.log = _log.setup_logging(__name__) self._session_constructor = session_constructor self._app_name = app_name self._app_version = app_version if load_yaml_config: self._config_files = config_files or CONFIG_FILES self._secure_files = secure_files or SECURE_FILES self._vendor_files = vendor_files or VENDOR_FILES else: self._config_files = [] self._secure_files = [] self._vendor_files = [] config_file_override = os.environ.get('OS_CLIENT_CONFIG_FILE') if config_file_override: self._config_files.insert(0, config_file_override) secure_file_override = os.environ.get('OS_CLIENT_SECURE_FILE') 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.get( 'OS_PREFER_IPV6', client_config.get( 'prefer_ipv6', client_config.get( 'prefer-ipv6', True)))) force_ipv4 = get_boolean( os.environ.get( '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.get('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)) self.default_cloud = os.environ.get('OS_CLOUD') envvars = _get_os_environ(envvar_prefix=envvar_prefix) if envvars: self.cloud_config['clouds'][self.envvar_key] = envvars if not self.default_cloud: self.default_cloud = self.envvar_key if not self.default_cloud and self.cloud_config['clouds']: if len(self.cloud_config['clouds'].keys()) == 1: # If there is only one cloud just use it. This matches envvars # behavior and allows for much less typing. # TODO(mordred) allow someone to mark a cloud as "default" in # clouds.yaml. # The next/iter thing is for python3 compat where dict.keys # returns an iterator but in python2 it's a list. self.default_cloud = next(iter( self.cloud_config['clouds'].keys())) # 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 # Save the password callback # password = self._pw_callback(prompt="Password: ") self._pw_callback = pw_func 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: if isinstance(config['region_name'], list): regions = config['region_name'] else: 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( "Cloud {name} 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: status = profile_data.pop('status', 'active') message = profile_data.pop('message', '') if status == 'deprecated': warnings.warn( "{profile_name} is deprecated: {message}".format( profile_name=profile_name, message=message)) elif status == 'shutdown': raise exceptions.OpenStackConfigException( "{profile_name} references a cloud that no longer" " exists: {message}".format( profile_name=profile_name, message=message)) _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 _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 _validate_networks(self, networks, key): value = None for net in networks: if value and net[key]: raise exceptions.OpenStackConfigException( "Duplicate network entries for {key}: {net1} and {net2}." " Only one network can be flagged with {key}".format( key=key, net1=value['name'], net2=net['name'])) if not value and net[key]: value = net def _fix_backwards_networks(self, cloud): # Leave the external_network and internal_network keys in the # dict because consuming code might be expecting them. networks = [] # Normalize existing network entries for net in cloud.get('networks', []): name = net.get('name') if not name: raise exceptions.OpenStackConfigException( 'Entry in network list is missing required field "name".') network = dict( name=name, routes_externally=get_boolean(net.get('routes_externally')), nat_source=get_boolean(net.get('nat_source')), nat_destination=get_boolean(net.get('nat_destination')), default_interface=get_boolean(net.get('default_interface')), ) # routes_ipv4_externally defaults to the value of routes_externally network['routes_ipv4_externally'] = get_boolean( net.get( 'routes_ipv4_externally', network['routes_externally'])) # routes_ipv6_externally defaults to the value of routes_externally network['routes_ipv6_externally'] = get_boolean( net.get( 'routes_ipv6_externally', network['routes_externally'])) networks.append(network) for key in ('external_network', 'internal_network'): external = key.startswith('external') if key in cloud and 'networks' in cloud: raise exceptions.OpenStackConfigException( "Both {key} and networks were specified in the config." " Please remove {key} from the config and use the network" " list to configure network behavior.".format(key=key)) if key in cloud: warnings.warn( "{key} is deprecated. Please replace with an entry in" " a dict inside of the networks list with name: {name}" " and routes_externally: {external}".format( key=key, name=cloud[key], external=external)) networks.append(dict( name=cloud[key], routes_externally=external, nat_destination=not external, default_interface=external)) # Validate that we don't have duplicates self._validate_networks(networks, 'nat_destination') self._validate_networks(networks, 'default_interface') cloud['networks'] = networks return cloud 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.pop(target_key, None) 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=None): """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 """ if service_keys is None: service_keys = [] # 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=None, 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 not args: args = {} 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 checks 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' elif config['auth_type'] == 'token_endpoint': # Humans have been trained to use a thing called token_endpoint # That it does not exist in keystoneauth is irrelvant- it not # doing what they want causes them sorrow. config['auth_type'] = 'admin_token' return loading.get_plugin_loader(config['auth_type']) 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, ) config = self._clean_up_after_ourselves( config, p_opt, winning_value, ) if winning_value: # Prefer the plugin configuration dest value if the value's key # is marked as deprecated. if p_opt.dest is None: good_name = p_opt.name.replace('-', '_') config['auth'][good_name] = winning_value else: config['auth'][p_opt.dest] = winning_value # See if this needs a prompting config = self.option_prompt(config, p_opt) return config def _validate_auth_correctly(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, win, move it and kill it from config dict # if it's in config.auth but not in config it's good # deprecated loses to current # provided beats default, deprecated or not winning_value = self._find_winning_auth_value( p_opt, config, ) if not winning_value: winning_value = self._find_winning_auth_value( p_opt, config['auth'], ) config = self._clean_up_after_ourselves( config, p_opt, winning_value, ) # See if this needs a prompting config = self.option_prompt(config, p_opt) return config def option_prompt(self, config, p_opt): """Prompt user for option that requires a value""" if ( getattr(p_opt, 'prompt', None) is not None and p_opt.dest not in config['auth'] and self._pw_callback is not None ): config['auth'][p_opt.dest] = self._pw_callback(p_opt.prompt) return config def _clean_up_after_ourselves(self, config, p_opt, winning_value): # 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 magic_fixes(self, config): """Perform the set of magic argument fixups""" # 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_auth_plugin(config) config = self._fix_backwards_project(config) config = self._fix_backwards_interface(config) config = self._fix_backwards_networks(config) config = self._handle_domain_id(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) 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 config = self.magic_fixes(config) config = self._normalize_keys(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: loader = self._get_auth_loader(config) config = self._validate_auth(config, loader) auth_plugin = loader.load_from_options(**config['auth']) 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') and key not in FORMAT_EXCLUSIONS: 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=config, force_ipv4=force_ipv4, auth_plugin=auth_plugin, openstack_config=self, session_constructor=self._session_constructor, app_name=self._app_name, app_version=self._app_version, ) def get_one_cloud_osc( 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 config = self.magic_fixes(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: loader = self._get_auth_loader(config) config = self._validate_auth_correctly(config, loader) auth_plugin = loader.load_from_options(**config['auth']) 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') and key not in FORMAT_EXCLUSIONS: 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.29.0/os_client_config/constructors.py0000666000175100017510000000237013234622123023417 0ustar zuulzuul00000000000000# 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 import threading _json_path = os.path.join( os.path.dirname(os.path.realpath(__file__)), 'constructors.json') _class_mapping = None _class_mapping_lock = threading.Lock() def get_constructor_mapping(): global _class_mapping if _class_mapping is not None: return _class_mapping.copy() with _class_mapping_lock: if _class_mapping is not None: return _class_mapping.copy() tmp_class_mapping = {} with open(_json_path, 'r') as json_file: tmp_class_mapping.update(json.load(json_file)) _class_mapping = tmp_class_mapping return tmp_class_mapping.copy() os-client-config-1.29.0/os_client_config/cloud_config.py0000666000175100017510000006112013234622123023300 0ustar zuulzuul00000000000000# 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 math import warnings from keystoneauth1 import adapter import keystoneauth1.exceptions.catalog from keystoneauth1 import session import requestsexceptions import os_client_config 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, session_constructor=None, app_name=None, app_version=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 self._session_constructor = session_constructor or session.Session self._app_name = app_name self._app_version = app_version 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 set_session_constructor(self, session_constructor): """Sets the Session constructor.""" self._session_constructor = session_constructor 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. # What's even more amazing is that they did it AGAIN with cinder v3 # And then I learned that mistral copied it. if service_type == 'volume': vol_type = self.get_api_version(service_type) if vol_type and vol_type.startswith('2'): service_type = 'volumev2' elif vol_type and vol_type.startswith('3'): service_type = 'volumev3' elif service_type == 'workflow': wk_type = self.get_api_version(service_type) if wk_type and wk_type.startswith('2'): service_type = 'workflowv2' 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 = self._session_constructor( auth=self._auth, verify=verify, cert=cert, timeout=self.config['api_timeout']) if hasattr(self._keystone_session, 'additional_user_agent'): self._keystone_session.additional_user_agent.append( ('os-client-config', os_client_config.__version__)) # Using old keystoneauth with new os-client-config fails if # we pass in app_name and app_version. Those are not essential, # nor a reason to bump our minimum, so just test for the session # having the attribute post creation and set them then. if hasattr(self._keystone_session, 'app_name'): self._keystone_session.app_name = self._app_name if hasattr(self._keystone_session, 'app_version'): self._keystone_session.app_version = self._app_version return self._keystone_session def get_service_catalog(self): """Helper method to grab the service catalog.""" return self._auth.get_access(self.get_session()).service_catalog def _get_version_args(self, service_key, version): """Translate OCC version args to those needed by ksa adapter. If no version is requested explicitly and we have a configured version, set the version parameter and let ksa deal with expanding that to min=ver.0, max=ver.latest. If version is set, pass it through. If version is not set and we don't have a configured version, default to latest. """ if version == 'latest': return None, None, 'latest' if not version: version = self.get_api_version(service_key) if not version: return None, None, 'latest' return version, None, None def get_session_client(self, service_key, version=None): """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. """ (version, min_version, max_version) = self._get_version_args( service_key, version) 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), version=version, min_version=min_version, max_version=max_version, region_name=self.region) def _get_highest_endpoint(self, service_types, kwargs): session = self.get_session() for service_type in service_types: kwargs['service_type'] = service_type try: # Return the highest version we find that matches # the request return session.get_endpoint(**kwargs) except keystoneauth1.exceptions.catalog.EndpointNotFound: pass def get_session_endpoint( self, service_key, min_version=None, max_version=None): """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' """ override_endpoint = self.get_endpoint(service_key) if override_endpoint: return override_endpoint endpoint = None kwargs = { 'service_name': self.get_service_name(service_key), 'region_name': self.region } kwargs['interface'] = self.get_interface(service_key) if service_key == 'volume' and not self.get_api_version('volume'): # If we don't have a configured cinder version, we can't know # to request a different service_type min_version = float(min_version or 1) max_version = float(max_version or 3) min_major = math.trunc(float(min_version)) max_major = math.trunc(float(max_version)) versions = range(int(max_major) + 1, int(min_major), -1) service_types = [] for version in versions: if version == 1: service_types.append('volume') else: service_types.append('volumev{v}'.format(v=version)) else: service_types = [self.get_service_type(service_key)] endpoint = self._get_highest_endpoint(service_types, kwargs) if not endpoint: self.log.warning( "Keystone catalog entry not found (" "service_type=%s,service_name=%s" "interface=%s,region_name=%s)", service_key, kwargs['service_name'], kwargs['interface'], kwargs['region_name']) return endpoint def get_legacy_client( self, service_key, client_class=None, interface_key=None, pass_version_arg=True, version=None, min_version=None, max_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 min_version: (options) Minimum version acceptable. :param max_version: (options) Maximum version acceptable. :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) interface = self.get_interface(service_key) # trigger exception on lack of service endpoint = self.get_session_endpoint( service_key, min_version=min_version, max_version=max_version) endpoint_override = self.get_endpoint(service_key) if service_key == 'object-store': constructor_kwargs = dict( session=self.get_session(), os_options=dict( service_type=self.get_service_type(service_key), object_storage_url=endpoint_override, region_name=self.region)) else: 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) if pass_version_arg and service_key != 'object-store': if not version: version = self.get_api_version(service_key) if not version and service_key == 'volume': from cinderclient import client as cinder_client version = cinder_client.get_volume_api_from_url(endpoint) # 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 elif service_key == 'baremetal': if version != '1': # Set Ironic Microversion constructor_kwargs['os_ironic_api_version'] = version # Version arg is the major version, not the full microstring constructor_kwargs['version'] = version[0] else: constructor_kwargs['version'] = version if min_version and min_version > float(version): raise exceptions.OpenStackConfigVersionException( "Minimum version {min_version} requested but {version}" " found".format(min_version=min_version, version=version), version=version) if max_version and max_version < float(version): raise exceptions.OpenStackConfigVersionException( "Maximum version {max_version} requested but {version}" " found".format(max_version=max_version, version=version), version=version) if service_key == 'database': # TODO(mordred) Remove when https://review.openstack.org/314032 # has landed and released. We're passing in a Session, but the # trove Client object has username and password as required # args constructor_kwargs['username'] = None constructor_kwargs['password'] = None if not interface_key: if service_key in ('image', 'key-manager'): interface_key = 'interface' elif (service_key == 'identity' and version and version.startswith('3')): interface_key = 'interface' else: interface_key = 'endpoint_type' if service_key == 'object-store': constructor_kwargs['os_options'][interface_key] = interface else: constructor_kwargs[interface_key] = interface return client_class(**constructor_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]) def requires_floating_ip(self): """Return whether or not this cloud requires floating ips. :returns: True of False if know, None if discovery is needed. If requires_floating_ip is not configured but the cloud is known to not provide floating ips, will return False. """ if self.config['floating_ip_source'] == "None": return False return self.config.get('requires_floating_ip') def get_external_networks(self): """Get list of network names for external networks.""" return [ net['name'] for net in self.config['networks'] if net['routes_externally']] def get_external_ipv4_networks(self): """Get list of network names for external IPv4 networks.""" return [ net['name'] for net in self.config['networks'] if net['routes_ipv4_externally']] def get_external_ipv6_networks(self): """Get list of network names for external IPv6 networks.""" return [ net['name'] for net in self.config['networks'] if net['routes_ipv6_externally']] def get_internal_networks(self): """Get list of network names for internal networks.""" return [ net['name'] for net in self.config['networks'] if not net['routes_externally']] def get_internal_ipv4_networks(self): """Get list of network names for internal IPv4 networks.""" return [ net['name'] for net in self.config['networks'] if not net['routes_ipv4_externally']] def get_internal_ipv6_networks(self): """Get list of network names for internal IPv6 networks.""" return [ net['name'] for net in self.config['networks'] if not net['routes_ipv6_externally']] def get_default_network(self): """Get network used for default interactions.""" for net in self.config['networks']: if net['default_interface']: return net['name'] return None def get_nat_destination(self): """Get network used for NAT destination.""" for net in self.config['networks']: if net['nat_destination']: return net['name'] return None def get_nat_source(self): """Get network used for NAT source.""" for net in self.config['networks']: if net.get('nat_source'): return net['name'] return None os-client-config-1.29.0/os_client_config/defaults.json0000666000175100017510000000140313234622123022773 0ustar zuulzuul00000000000000{ "application_catalog_api_version": "1", "auth_type": "password", "baremetal_api_version": "1", "container_api_version": "1", "container_infra_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", "message": "", "metering_api_version": "2", "network_api_version": "2", "object_store_api_version": "1", "orchestration_api_version": "1", "secgroup_source": "neutron", "status": "active", "volume_api_version": "2", "workflow_api_version": "2" } os-client-config-1.29.0/os_client_config/__init__.py0000666000175100017510000001015113234622123022402 0ustar zuulzuul00000000000000# 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 import pbr.version from os_client_config import cloud_config from os_client_config.config import OpenStackConfig # noqa __version__ = pbr.version.VersionInfo('os_client_config').version_string() _config = None def get_config( service_key=None, options=None, app_name=None, app_version=None, **kwargs): load_yaml_config = kwargs.pop('load_yaml_config', True) global _config if not _config: _config = OpenStackConfig( load_yaml_config=load_yaml_config, app_name=app_name, app_version=app_version) if options: _config.register_argparse_arguments(options, sys.argv, service_key) parsed_options = options.parse_known_args(sys.argv) else: parsed_options = None return _config.get_one_cloud(options=parsed_options, **kwargs) def make_rest_client( service_key, options=None, app_name=None, app_version=None, version=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, app_name=app_name, app_version=app_version, **kwargs) return cloud.get_session_client(service_key, version=version) # Backwards compat - simple_client was a terrible name simple_client = make_rest_client # Backwards compat - session_client was a terrible name session_client = make_rest_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) def make_sdk(options=None, **kwargs): """Simple wrapper for getting an OpenStack SDK Connection. For completeness, provide a mechanism that matches make_client and make_rest_client. The heavy lifting here is done in openstacksdk. :rtype: :class:`~openstack.connection.Connection` """ from openstack import connection cloud = get_config(options=options, **kwargs) return connection.from_config(cloud_config=cloud, options=options) def make_shade(options=None, **kwargs): """Simple wrapper for getting a Shade OpenStackCloud object A mechanism that matches make_sdk, make_client and make_rest_client. :rtype: :class:`~shade.OpenStackCloud` """ import shade cloud = get_config(options=options, **kwargs) return shade.OpenStackCloud(cloud_config=cloud, **kwargs) os-client-config-1.29.0/os_client_config/schema.json0000666000175100017510000000652413234622123022435 0ustar zuulzuul00000000000000{ "$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.29.0/os_client_config/exceptions.py0000666000175100017510000000173713234622123023036 0ustar zuulzuul00000000000000# 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.""" class OpenStackConfigVersionException(OpenStackConfigException): """A version was requested that is different than what was found.""" def __init__(self, version): super(OpenStackConfigVersionException, self).__init__() self.version = version os-client-config-1.29.0/os_client_config/tests/0000775000175100017510000000000013234622446021443 5ustar zuulzuul00000000000000os-client-config-1.29.0/os_client_config/tests/test_environ.py0000666000175100017510000002003413234622123024525 0ustar zuulzuul00000000000000# 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_incomplete_envvars(self): self.useFixture( fixtures.EnvironmentVariable('NOVA_USERNAME', 'nova')) self.useFixture( fixtures.EnvironmentVariable('OS_USERNAME', 'user')) config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) # This is broken due to an issue that's fixed in a subsequent patch # commenting it out in this patch to keep the patch size reasonable # self.assertRaises( # keystoneauth1.exceptions.auth_plugins.MissingRequiredOptions, # c.get_one_cloud, 'envvars') def test_have_envvars(self): self.useFixture( fixtures.EnvironmentVariable('NOVA_USERNAME', 'nova')) self.useFixture( fixtures.EnvironmentVariable('OS_AUTH_URL', 'http://example.com')) self.useFixture( fixtures.EnvironmentVariable('OS_USERNAME', 'user')) self.useFixture( fixtures.EnvironmentVariable('OS_PASSWORD', 'password')) self.useFixture( fixtures.EnvironmentVariable('OS_PROJECT_NAME', 'project')) 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')) self.useFixture( fixtures.EnvironmentVariable( 'NOVA_AUTH_URL', 'http://example.com')) self.useFixture( fixtures.EnvironmentVariable('NOVA_PASSWORD', 'password')) self.useFixture( fixtures.EnvironmentVariable('NOVA_PROJECT_NAME', 'project')) 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.29.0/os_client_config/tests/test_config.py0000666000175100017510000013267313234622123024327 0ustar zuulzuul00000000000000# 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 extras 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 def prompt_for_password(prompt=None): """Fake prompt function that just returns a constant string""" return 'promptpass' 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_default_cloud_from_file(self): single_conf = base._write_yaml({ 'clouds': { 'single': { 'auth': { 'auth_url': 'http://example.com/v2', 'username': 'testuser', 'password': 'testpass', 'project_name': 'testproject', }, 'region_name': 'test-region', } } }) c = config.OpenStackConfig(config_files=[single_conf], vendor_files=[self.vendor_yaml]) cc = c.get_one_cloud() self.assertEqual(cc.name, 'single') 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) self.assertNotIn('domain_id', cc) 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) self.assertNotIn('domain_id', cc) 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_get_one_cloud_networks(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cc = c.get_one_cloud('_test-cloud-networks_') self.assertEqual( ['a-public', 'another-public', 'split-default'], cc.get_external_networks()) self.assertEqual( ['a-private', 'another-private', 'split-no-default'], cc.get_internal_networks()) self.assertEqual('a-public', cc.get_nat_source()) self.assertEqual('another-private', cc.get_nat_destination()) self.assertEqual('another-public', cc.get_default_network()) self.assertEqual( ['a-public', 'another-public', 'split-no-default'], cc.get_external_ipv4_networks()) self.assertEqual( ['a-public', 'another-public', 'split-default'], cc.get_external_ipv6_networks()) def test_get_one_cloud_no_networks(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([], cc.get_external_networks()) self.assertEqual([], cc.get_internal_networks()) self.assertIsNone(cc.get_nat_source()) self.assertIsNone(cc.get_nat_destination()) self.assertIsNone(cc.get_default_network()) 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', validate=False) 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-networks_', '_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 TestExcludedFormattedConfigValue(base.TestCase): # verify LaunchPad bug #1635696 # # get_one_cloud() and get_one_cloud_osc() iterate over config # values and try to expand any variables in those values by # calling value.format(), however some config values # (e.g. password) should never have format() applied to them, not # only might that change the password but it will also cause the # format() function to raise an exception if it can not parse the # format string. Examples would be single brace (e.g. 'foo{') # which raises an ValueError because it's looking for a matching # end brace or a brace pair with a key value that cannot be found # (e.g. 'foo{bar}') which raises a KeyError. def setUp(self): super(TestExcludedFormattedConfigValue, self).setUp() self.args = dict( auth_url='http://example.com/v2', username='user', 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_password_brace(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) password = 'foo{' # Would raise ValueError, single brace self.options.password = password cc = c.get_one_cloud( cloud='_test_cloud_regions', argparse=self.options, validate=False) self.assertEqual(cc.password, password) password = 'foo{bar}' # Would raise KeyError, 'bar' not found self.options.password = password cc = c.get_one_cloud( cloud='_test_cloud_regions', argparse=self.options, validate=False) self.assertEqual(cc.password, password) def test_get_one_cloud_osc_password_brace(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) password = 'foo{' # Would raise ValueError, single brace self.options.password = password cc = c.get_one_cloud_osc( cloud='_test_cloud_regions', argparse=self.options, validate=False) self.assertEqual(cc.password, password) password = 'foo{bar}' # Would raise KeyError, 'bar' not found self.options.password = password cc = c.get_one_cloud_osc( cloud='_test_cloud_regions', argparse=self.options, validate=False) self.assertEqual(cc.password, password) 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, validate=False) self.assertEqual(cc.region_name, 'region2') self.assertEqual(cc.snack_type, 'cookie') def test_get_one_cloud_precedence(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) kwargs = { 'auth': { 'username': 'testuser', 'password': 'authpass', 'project-id': 'testproject', 'auth_url': 'http://example.com/v2', }, 'region_name': 'kwarg_region', 'password': 'ansible_password', 'arbitrary': 'value', } args = dict( auth_url='http://example.com/v2', username='user', password='argpass', project_name='project', region_name='region2', snack_type='cookie', ) options = argparse.Namespace(**args) cc = c.get_one_cloud( argparse=options, **kwargs) self.assertEqual(cc.region_name, 'region2') self.assertEqual(cc.auth['password'], 'authpass') self.assertEqual(cc.snack_type, 'cookie') def test_get_one_cloud_precedence_osc(self): c = config.OpenStackConfig( config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml], ) kwargs = { 'auth': { 'username': 'testuser', 'password': 'authpass', 'project-id': 'testproject', 'auth_url': 'http://example.com/v2', }, 'region_name': 'kwarg_region', 'password': 'ansible_password', 'arbitrary': 'value', } args = dict( auth_url='http://example.com/v2', username='user', password='argpass', project_name='project', region_name='region2', snack_type='cookie', ) options = argparse.Namespace(**args) cc = c.get_one_cloud_osc( argparse=options, **kwargs ) self.assertEqual(cc.region_name, 'region2') self.assertEqual(cc.auth['password'], 'argpass') self.assertEqual(cc.snack_type, 'cookie') def test_get_one_cloud_precedence_no_argparse(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) kwargs = { 'auth': { 'username': 'testuser', 'password': 'authpass', 'project-id': 'testproject', 'auth_url': 'http://example.com/v2', }, 'region_name': 'kwarg_region', 'password': 'ansible_password', 'arbitrary': 'value', } cc = c.get_one_cloud(**kwargs) self.assertEqual(cc.region_name, 'kwarg_region') self.assertEqual(cc.auth['password'], 'authpass') self.assertIsNone(cc.password) 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, validate=False) 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(validate=False, **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_get_one_cloud_no_yaml_no_cloud(self): c = config.OpenStackConfig(load_yaml_config=False) self.assertRaises( exceptions.OpenStackConfigException, c.get_one_cloud, cloud='_test_cloud_regions', region_name='region2', argparse=None) def test_get_one_cloud_no_yaml(self): c = config.OpenStackConfig(load_yaml_config=False) cc = c.get_one_cloud( region_name='region2', argparse=None, **base.USER_CONF['clouds']['_test_cloud_regions']) # Not using assert_cloud_details because of cache settings which # are not present without the file 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.region_name, 'region2') 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, validate=False) 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, validate=False) 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_action_append_no_underscore(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('--foo', action='append') argv = ['--foo', '1', '--foo', '2'] c.register_argparse_arguments(parser, argv=argv) opts, _remain = parser.parse_known_args(argv) self.assertEqual(opts.foo, ['1', '2']) 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, validate=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.assertIsNone(opts.os_network_service_type) self.assertIsNone(opts.os_network_api_version) self.assertEqual(opts.network_api_version, '4') cloud = c.get_one_cloud(argparse=opts, validate=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.assertIsNone(opts.os_compute_service_type) self.assertIsNone(opts.os_volume_service_type) self.assertEqual(opts.os_service_type, 'compute') self.assertEqual(opts.os_compute_service_name, 'cloudServers') self.assertEqual(opts.os_endpoint_type, 'admin') self.assertIsNone(opts.os_network_api_version) self.assertEqual(opts.network_api_version, '4') cloud = c.get_one_cloud(argparse=opts, validate=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 TestConfigPrompt(base.TestCase): def setUp(self): super(TestConfigPrompt, self).setUp() self.args = dict( auth_url='http://example.com/v2', username='user', project_name='project', # region_name='region2', auth_type='password', ) self.options = argparse.Namespace(**self.args) def test_get_one_cloud_prompt(self): c = config.OpenStackConfig( config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml], pw_func=prompt_for_password, ) # This needs a cloud definition without a password. # If this starts failing unexpectedly check that the cloud_yaml # and/or vendor_yaml do not have a password in the selected cloud. cc = c.get_one_cloud( cloud='_test_cloud_no_vendor', argparse=self.options, ) self.assertEqual('promptpass', cc.auth['password']) 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) def test_backwards_network_fail(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cloud = { 'external_network': 'public', 'networks': [ {'name': 'private', 'routes_externally': False}, ] } self.assertRaises( exceptions.OpenStackConfigException, c._fix_backwards_networks, cloud) def test_backwards_network(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cloud = { 'external_network': 'public', 'internal_network': 'private', } result = c._fix_backwards_networks(cloud) expected = { 'external_network': 'public', 'internal_network': 'private', 'networks': [ {'name': 'public', 'routes_externally': True, 'nat_destination': False, 'default_interface': True}, {'name': 'private', 'routes_externally': False, 'nat_destination': True, 'default_interface': False}, ] } self.assertEqual(expected, result) def test_normalize_network(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cloud = { 'networks': [ {'name': 'private'} ] } result = c._fix_backwards_networks(cloud) expected = { 'networks': [ {'name': 'private', 'routes_externally': False, 'nat_destination': False, 'default_interface': False, 'nat_source': False, 'routes_ipv4_externally': False, 'routes_ipv6_externally': False}, ] } self.assertEqual(expected, result) def test_single_default_interface(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml]) cloud = { 'networks': [ {'name': 'blue', 'default_interface': True}, {'name': 'purple', 'default_interface': True}, ] } self.assertRaises( exceptions.OpenStackConfigException, c._fix_backwards_networks, cloud) os-client-config-1.29.0/os_client_config/tests/test_json.py0000666000175100017510000000445413234622123024026 0ustar zuulzuul00000000000000# 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.29.0/os_client_config/tests/test_init.py0000666000175100017510000000231613234622123024013 0ustar zuulzuul00000000000000# 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 os_client_config from os_client_config.tests import base class TestInit(base.TestCase): def test_get_config_without_arg_parser(self): cloud_config = os_client_config.get_config( options=None, validate=False) self.assertIsInstance( cloud_config, os_client_config.cloud_config.CloudConfig ) def test_get_config_with_arg_parser(self): cloud_config = os_client_config.get_config( options=argparse.ArgumentParser(), validate=False) self.assertIsInstance( cloud_config, os_client_config.cloud_config.CloudConfig ) os-client-config-1.29.0/os_client_config/tests/__init__.py0000666000175100017510000000000013234622123023534 0ustar zuulzuul00000000000000os-client-config-1.29.0/os_client_config/tests/test_cloud_config.py0000666000175100017510000006064313234622123025512 0ustar zuulzuul00000000000000# 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 exceptions as ksa_exceptions 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_volume_override_v3(self): cc = cloud_config.CloudConfig("test1", "region-al", fake_services_dict) cc.config['volume_api_version'] = '3' self.assertEqual('volumev3', cc.get_service_type('volume')) def test_workflow_override_v2(self): cc = cloud_config.CloudConfig("test1", "region-al", fake_services_dict) cc.config['workflow_api_version'] = '2' self.assertEqual('workflowv2', cc.get_service_type('workflow')) def test_no_override(self): """Test no override happens when defaults are not configured""" cc = cloud_config.CloudConfig("test1", "region-al", fake_services_dict) self.assertEqual('volume', cc.get_service_type('volume')) self.assertEqual('workflow', cc.get_service_type('workflow')) self.assertEqual('not-exist', cc.get_service_type('not-exist')) 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) fake_session = mock.Mock() fake_session.additional_user_agent = [] mock_session.return_value = fake_session 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) self.assertEqual( fake_session.additional_user_agent, [('os-client-config', '1.2.3')]) @mock.patch.object(ksa_session, 'Session') def test_get_session_with_app_name(self, mock_session): config_dict = defaults.get_defaults() config_dict.update(fake_services_dict) fake_session = mock.Mock() fake_session.additional_user_agent = [] fake_session.app_name = None fake_session.app_version = None mock_session.return_value = fake_session cc = cloud_config.CloudConfig( "test1", "region-al", config_dict, auth_plugin=mock.Mock(), app_name="test_app", app_version="test_version") cc.get_session() mock_session.assert_called_with( auth=mock.ANY, verify=True, cert=None, timeout=None) self.assertEqual(fake_session.app_name, "test_app") self.assertEqual(fake_session.app_version, "test_version") self.assertEqual( fake_session.additional_user_agent, [('os-client-config', '1.2.3')]) @mock.patch.object(ksa_session, 'Session') def test_get_session_with_timeout(self, mock_session): fake_session = mock.Mock() fake_session.additional_user_agent = [] mock_session.return_value = fake_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) self.assertEqual( fake_session.additional_user_agent, [('os-client-config', '1.2.3')]) @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(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_session') def test_session_endpoint_not_found(self, mock_get_session): exc_to_raise = ksa_exceptions.catalog.EndpointNotFound mock_get_session.return_value.get_endpoint.side_effect = exc_to_raise cc = cloud_config.CloudConfig( "test1", "region-al", {}, auth_plugin=mock.Mock()) self.assertIsNone(cc.get_session_endpoint('notfound')) @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( session=mock.ANY, os_options={ 'region_name': 'region-al', 'service_type': 'object-store', 'object_storage_url': None, 'endpoint_type': 'public', }) @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( session=mock.ANY, os_options={ 'region_name': 'region-al', 'service_type': 'object-store', 'object_storage_url': None, 'endpoint_type': 'public', }) @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( session=mock.ANY, os_options={ 'region_name': 'region-al', 'service_type': 'object-store', 'object_storage_url': None, 'endpoint_type': 'public', }) @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( session=mock.ANY, os_options={ 'region_name': 'region-al', 'service_type': 'object-store', 'object_storage_url': None, 'endpoint_type': 'public', }) @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( session=mock.ANY, os_options={ 'region_name': 'region-al', 'service_type': 'object-store', 'object_storage_url': 'http://example.com/swift', 'endpoint_type': 'public', }) @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', interface='admin', endpoint_override=None, region_name='region-al', service_type='identity', session=mock.ANY, service_name='locks') os-client-config-1.29.0/os_client_config/tests/base.py0000666000175100017510000001737613234622123022737 0ustar zuulzuul00000000000000# -*- 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-networks_': { 'auth': { 'username': 'testuser', 'password': 'testpass', 'project_id': 12345, 'auth_url': 'http://example.com/v2', 'domain_id': '6789', 'project_domain_id': '123456789', }, 'networks': [{ 'name': 'a-public', 'routes_externally': True, 'nat_source': True, }, { 'name': 'another-public', 'routes_externally': True, 'default_interface': True, }, { 'name': 'a-private', 'routes_externally': False, }, { 'name': 'another-private', 'routes_externally': False, 'nat_destination': True, }, { 'name': 'split-default', 'routes_externally': True, 'routes_ipv4_externally': False, }, { 'name': 'split-no-default', 'routes_ipv6_externally': False, 'routes_ipv4_externally': True, }], '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) self.useFixture(fixtures.MonkeyPatch( 'os_client_config.__version__', '1.2.3')) # 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.29.0/README.rst0000666000175100017510000000203613234622123016457 0ustar zuulzuul00000000000000================ os-client-config ================ .. image:: http://governance.openstack.org/badges/os-client-config.svg :target: http://governance.openstack.org/reference/tags/index.html `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 Source ------ * Free software: Apache license * Documentation: http://docs.openstack.org/os-client-config/latest * Source: http://git.openstack.org/cgit/openstack/os-client-config * Bugs: http://bugs.launchpad.net/os-client-config os-client-config-1.29.0/setup.cfg0000666000175100017510000000157213234622446016625 0ustar zuulzuul00000000000000[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 = https://docs.openstack.org/os-client-config/latest 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.5 [files] packages = os_client_config [build_sphinx] source-dir = doc/source build-dir = doc/build all_files = 1 warning-is-error = 1 [upload_sphinx] upload-dir = doc/build/html [wheel] universal = 1 [egg_info] tag_build = tag_date = 0 os-client-config-1.29.0/CONTRIBUTING.rst0000666000175100017510000000104213234622123017425 0ustar zuulzuul00000000000000If 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.29.0/LICENSE0000666000175100017510000002363613234622123016006 0ustar zuulzuul00000000000000 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.29.0/.zuul.yaml0000666000175100017510000000016413234622123016731 0ustar zuulzuul00000000000000- project: name: openstack/os-client-config templates: - shade-functional-tips - shade-tox-tips os-client-config-1.29.0/doc/0000775000175100017510000000000013234622446015542 5ustar zuulzuul00000000000000os-client-config-1.29.0/doc/requirements.txt0000666000175100017510000000022613234622156021026 0ustar zuulzuul00000000000000docutils>=0.11 # OSI-Approved Open Source, Public Domain sphinx!=1.6.6,>=1.6.2 # BSD openstackdocstheme>=1.18.1 # Apache-2.0 reno>=2.5.0 # Apache-2.0 os-client-config-1.29.0/doc/source/0000775000175100017510000000000013234622446017042 5ustar zuulzuul00000000000000os-client-config-1.29.0/doc/source/reference/0000775000175100017510000000000013234622446021000 5ustar zuulzuul00000000000000os-client-config-1.29.0/doc/source/reference/index.rst0000666000175100017510000000031013234622123022625 0ustar zuulzuul00000000000000============= API Reference ============= .. module:: os_client_config :synopsis: OpenStack client configuration .. autoclass:: os_client_config.OpenStackConfig :members: :inherited-members: os-client-config-1.29.0/doc/source/conf.py0000777000175100017510000000534013234622123020340 0ustar zuulzuul00000000000000# -*- 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 import openstackdocstheme 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', 'reno.sphinxext', 'openstackdocstheme', ] # openstackdocstheme options repository_name = 'openstack/os-client-config' bug_project = 'os-client-config' bug_tag = '' html_last_updated_fmt = '%Y-%m-%d %H:%M' # 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'] html_theme = 'openstackdocs' html_theme_path = [openstackdocstheme.get_html_theme_path()] # 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.29.0/doc/source/install/0000775000175100017510000000000013234622446020510 5ustar zuulzuul00000000000000os-client-config-1.29.0/doc/source/install/index.rst0000666000175100017510000000033113234622156022346 0ustar zuulzuul00000000000000============ Installation ============ At the command line:: $ pip install os-client-config Or, if you have virtualenvwrapper installed:: $ mkvirtualenv os-client-config $ pip install os-client-config os-client-config-1.29.0/doc/source/index.rst0000666000175100017510000000176713234622123020710 0ustar zuulzuul00000000000000================ os-client-config ================ .. image:: http://governance.openstack.org/badges/os-client-config.svg :target: http://governance.openstack.org/reference/tags/index.html `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 .. toctree:: :maxdepth: 2 install/index user/index reference/index contributor/index Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` os-client-config-1.29.0/doc/source/user/0000775000175100017510000000000013234622446020020 5ustar zuulzuul00000000000000os-client-config-1.29.0/doc/source/user/using.rst0000666000175100017510000001166513234622123021702 0ustar zuulzuul00000000000000========================================== Using os-client-config in an Application ========================================== 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 OpenStack SDK object --------------------------------- If what you want to do is get an OpenStack SDK Connection 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 `openstacksdk` instance. .. code-block:: python import os_client_config sdk = os_client_config.make_sdk() If you want to do the same thing but on a named cloud. .. code-block:: python import os_client_config sdk = os_client_config.make_sdk(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 sdk = os_client_config.make_sdk(options=argparse.ArgumentParser()) It should be noted that OpenStack SDK has ways to construct itself that allow for additional flexibility. If the helper function here does not meet your needs, you should see the `from_config` method of `openstack.connection.Connection `_ Constructing shade objects -------------------------- If what you want to do is get a `shade `_ OpenStackCloud object, a helper function that honors clouds.yaml and `OS_` environment variables is provided. The following will get you a fully configured `OpenStackCloud` instance. .. code-block:: python import os_client_config cloud = os_client_config.make_shade() If you want to do the same thing but on a named cloud. .. code-block:: python import os_client_config cloud = os_client_config.make_shade(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 cloud = os_client_config.make_shade(options=argparse.ArgumentParser()) Constructing REST API Clients ----------------------------- What if you want to make direct REST calls via a Session interface? You're in luck. A similar interface is available as with `openstacksdk` and `shade`. The main difference is that you need to specify which service you want to talk to and `make_rest_client` will return you a keystoneauth Session object that is mounted on the endpoint for the service you're looking for. .. code-block:: python import os_client_config session = os_client_config.make_rest_client('compute', cloud='vexxhost') response = session.get('/servers') server_list = response.json()['servers'] Constructing Legacy Client objects ---------------------------------- If you want get an old-style 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 also 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. os-client-config-1.29.0/doc/source/user/network-config.rst0000666000175100017510000000556113234622123023507 0ustar zuulzuul00000000000000============== Network Config ============== There are several different qualities that networks in OpenStack might have that might not be able to be automatically inferred from the available metadata. To help users navigate more complex setups, `os-client-config` allows configuring a list of network metadata. .. code-block:: yaml clouds: amazing: networks: - name: blue routes_externally: true - name: purple routes_externally: true default_interface: true - name: green routes_externally: false - name: yellow routes_externally: false nat_destination: true - name: chartreuse routes_externally: false routes_ipv6_externally: true - name: aubergine routes_ipv4_externally: false routes_ipv6_externally: true Every entry must have a name field, which can hold either the name or the id of the network. `routes_externally` is a boolean field that labels the network as handling north/south traffic off of the cloud. In a public cloud this might be thought of as the "public" network, but in private clouds it's possible it might be an RFC1918 address. In either case, it's provides IPs to servers that things not on the cloud can use. This value defaults to `false`, which indicates only servers on the same network can talk to it. `routes_ipv4_externally` and `routes_ipv6_externally` are boolean fields to help handle `routes_externally` in the case where a network has a split stack with different values for IPv4 and IPv6. Either entry, if not given, defaults to the value of `routes_externally`. `default_interface` is a boolean field that indicates that the network is the one that programs should use. It defaults to false. An example of needing to use this value is a cloud with two private networks, and where a user is running ansible in one of the servers to talk to other servers on the private network. Because both networks are private, there would otherwise be no way to determine which one should be used for the traffic. There can only be one `default_interface` per cloud. `nat_destination` is a boolean field that indicates which network floating ips should be attached to. It defaults to false. Normally this can be inferred by looking for a network that has subnets that have a gateway_ip. But it's possible to have more than one network that satisfies that condition, so the user might want to tell programs which one to pick. There can be only one `nat_destination` per cloud. `nat_source` is a boolean field that indicates which network floating ips should be requested from. It defaults to false. Normally this can be inferred by looking for a network that is attached to a router. But it's possible to have more than one network that satisfies that condition, so the user might want to tell programs which one to pick. There can be only one `nat_source` per cloud. os-client-config-1.29.0/doc/source/user/index.rst0000666000175100017510000000026713234622123021660 0ustar zuulzuul00000000000000======================== Using os-client-config ======================== .. toctree:: :maxdepth: 2 configuration using vendor-support network-config releasenotes os-client-config-1.29.0/doc/source/user/releasenotes.rst0000666000175100017510000000023113234622123023231 0ustar zuulzuul00000000000000============= Release Notes ============= Release notes for `os-client-config` can be found at http://docs.openstack.org/releasenotes/os-client-config/ os-client-config-1.29.0/doc/source/user/vendor-support.rst0000666000175100017510000001725713234622156023575 0ustar zuulzuul00000000000000============== 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 not required * 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 Location ============== ================ van1 Vancouver, BC ============== ================ * Public IPv4 is provided via NAT with Neutron Floating IP betacloud --------- ============== ================== Region Name Location ============== ================== betacloud-1 Nuremberg, Germany ============== ================== * Identity API Version is 3 * Images must be in `raw` format * Public IPv4 is provided via NAT with Neutron Floating IP * Volume API Version is 3 catalyst -------- https://api.cloud.catalyst.net.nz:5000/v2.0 ============== ================ Region Name Location ============== ================ 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 Location ============== ================ Buf1 Buffalo, NY Fra1 Frankfurt, DE Kna1 Karlskrona, SE La1 Los Angeles, CA Lon1 London, UK Sto2 Stockholm, 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 Location ============== ================ tyo1 Tokyo, JP sin1 Singapore sjc1 San Jose, CA ============== ================ * Image upload is not supported datacentred ----------- https://compute.datacentred.io:5000 ============== ================ Region Name Location ============== ================ sal01 Manchester, UK ============== ================ * Image API Version is 1 dreamcompute ------------ https://iad2.dream.io:5000 ============== ================ Region Name Location ============== ================ RegionOne Ashburn, VA ============== ================ * Identity API Version is 3 * Images must be in `raw` format * IPv6 is provided to every server dreamhost --------- Deprecated, please use dreamcompute https://keystone.dream.io/v2.0 ============== ================ Region Name Location ============== ================ RegionOne Ashburn, VA ============== ================ * Images must be in `raw` format * Public IPv4 is provided via NAT with Neutron Floating IP * IPv6 is provided to every server otc --- https://iam.%(region_name)s.otc.t-systems.com/v3 ============== ================ Region Name Location ============== ================ eu-de Germany ============== ================ * Identity API Version is 3 * Images must be in `vhd` format * Public IPv4 is provided via NAT with Neutron Floating IP elastx ------ https://ops.elastx.net:5000/v2.0 ============== ================ Region Name Location ============== ================ regionOne Stockholm, SE ============== ================ * Public IPv4 is provided via NAT with Neutron Floating IP entercloudsuite --------------- https://api.entercloudsuite.com/v2.0 ============== ================ Region Name Location ============== ================ nl-ams1 Amsterdam, NL it-mil1 Milan, IT de-fra1 Frankfurt, DE ============== ================ * Image API Version is 1 * Volume API Version is 1 fuga ---- https://identity.api.fuga.io:5000 ============== ================ Region Name Location ============== ================ cystack Netherlands ============== ================ * Identity API Version is 3 * Volume API Version is 3 internap -------- https://identity.api.cloud.iweb.com/v2.0 ============== ================ Region Name Location ============== ================ ams01 Amsterdam, NL da01 Dallas, TX nyj01 New York, NY sin01 Singapore sjc01 San Jose, CA ============== ================ * Floating IPs are not supported ovh --- https://auth.cloud.ovh.net/v2.0 ============== ================ Region Name Location ============== ================ BHS1 Beauharnois, QC SBG1 Strassbourg, FR GRA1 Gravelines, FR ============== ================ * Images may be in `raw` format. The `qcow2` default is also supported * Floating IPs are not supported rackspace --------- https://identity.api.rackspacecloud.com/v2.0/ ============== ================ Region Name Location ============== ================ DFW Dallas, TX HKG Hong Kong IAD Washington, D.C. LON London, UK ORD Chicago, IL SYD Sydney, NSW ============== ================ * 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 * While passwords are recommended for use, API keys do work as well. The `rackspaceauth` python package must be installed, and then the following can be added to clouds.yaml:: auth: username: myusername api_key: myapikey auth_type: rackspace_apikey switchengines ------------- https://keystone.cloud.switch.ch:5000/v2.0 ============== ================ Region Name Location ============== ================ 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 Location ============== ================ RegionOne Prague, CZ ============== ================ * Volume API Version is 1 unitedstack ----------- https://identity.api.ustack.com/v3 ============== ================ Region Name Location ============== ================ bj1 Beijing, CN gd1 Guangdong, CN ============== ================ * Identity API Version is 3 * Images must be in `raw` format * Volume API Version is 1 vexxhost -------- http://auth.vexxhost.net ============== ================ Region Name Location ============== ================ ca-ymq-1 Montreal, QC ============== ================ * DNS API Version is 1 * Identity API Version is 3 zetta ----- https://identity.api.zetta.io/v3 ============== ================ Region Name Location ============== ================ no-osl1 Oslo, NO ============== ================ * DNS API Version is 2 * Identity API Version is 3 os-client-config-1.29.0/doc/source/user/configuration.rst0000666000175100017510000002277213234622156023433 0ustar zuulzuul00000000000000=========================================== Configuring os-client-config Applications =========================================== 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`. .. code-block:: yaml # clouds.yaml clouds: secure: auth: ... key: /home/myhome/client-cert.key cert: /home/myhome/client-cert.crt cacert: /home/myhome/ca.crt insecure: auth: ... verify: False 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. .. code-block:: yaml clouds: internap: profile: internap auth: password: XXXXXXXXXXXXXXXXX username: api-55f9a00fb2619 project_name: inap-17037 regions: - name: ams01 values: networks: - name: inap-17037-WAN1654 routes_externally: true - name: inap-17037-LAN6745 - name: nyj01 values: networks: - name: inap-17037-WAN1654 routes_externally: true - name: inap-17037-LAN6745 os-client-config-1.29.0/doc/source/contributor/0000775000175100017510000000000013234622446021414 5ustar zuulzuul00000000000000os-client-config-1.29.0/doc/source/contributor/index.rst0000666000175100017510000000011613234622123023245 0ustar zuulzuul00000000000000============ Contributing ============ .. include:: ../../../CONTRIBUTING.rst os-client-config-1.29.0/HACKING.rst0000666000175100017510000000024513234622123016566 0ustar zuulzuul00000000000000os-client-config Style Commandments =============================================== Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest os-client-config-1.29.0/test-requirements.txt0000666000175100017510000000111113234622156021230 0ustar zuulzuul00000000000000# 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.13.0,<0.14,>=0.12.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 extras>=1.0.0 # MIT fixtures>=3.0.0 # Apache-2.0/BSD jsonschema<3.0.0,>=2.6.0 # MIT mock>=2.0.0 # BSD python-glanceclient>=2.8.0 # Apache-2.0 python-subunit>=1.0.0 # Apache-2.0/BSD oslotest>=3.2.0 # Apache-2.0 stestr>=1.0.0 # Apache-2.0 testscenarios>=0.4 # Apache-2.0/BSD testtools>=2.2.0 # MIT