python-heatclient-0.2.8/0000775000175400017540000000000012304731421016313 5ustar jenkinsjenkins00000000000000python-heatclient-0.2.8/CONTRIBUTING.rst0000664000175400017540000000103212304731347020757 0ustar jenkinsjenkins00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps in the "If you're a developer, start here" section of this page: http://wiki.openstack.org/HowToContribute Once those steps have been completed, changes to OpenStack should be submitted for review via the Gerrit tool, following the workflow documented at: http://wiki.openstack.org/GerritWorkflow Pull requests submitted through GitHub will be ignored. Bugs should be filed on Launchpad, not GitHub: https://bugs.launchpad.net/heat python-heatclient-0.2.8/test-requirements.txt0000664000175400017540000000032712304731350022557 0ustar jenkinsjenkins00000000000000# Hacking already pins down pep8, pyflakes and flake8 hacking>=0.8.0,<0.9 coverage>=3.6 discover fixtures>=0.3.14 mock>=1.0 mox3>=0.7.0 sphinx>=1.1.2,<1.2 testscenarios>=0.4 testrepository>=0.0.18 testtools>=0.9.34 python-heatclient-0.2.8/ChangeLog0000664000175400017540000002456712304731421020103 0ustar jenkinsjenkins00000000000000CHANGES ======= 0.2.8 ----- * Expand get_file using to template scope * "version" section should be required in template * Updated from global requirements * add output-list and output-show * Improve help strings * Do not use the '+' operation dict_items() * Remove None for dict.get() * Support the native signal API * Update apiclient.base and reuse new functional * Python3: fix bytes/str issues * Adding stack-adopt support to python-heatclient * Fixed incorrect indentation in the tests 0.2.7 ----- * Don't use request builtin redirect for any methods * REST method to fetch deployments metadata for a server * Only call decode() on bytes * Replace try...except with assertRaises * Add support for software config resources * Make POST/PUT redirects use original methods * Do not use the '+' operation dict_items() * Decode all headers before logging curl commands * normalise_file_path_to_url: use urlutils.pathname2url * get_file_contents: use six.itervalues() instead of dict.itervalues() * Python3: use six.iteritems() rather than dict.iteritems() * Fixes environment file using correct YAML format * Improve help strings * Add optional args arg back to do_list * python-heatclient stack-abandon support * Add more default CA paths * Port to python requests * Enable default in bash completion * Allow environment to not have resource_registry * Add filter option to stack-list * Add pagination (limit and marker) to stack-list * Populate files with content from get_file function calls * Enable hacking H233 rule * Configure logging regardless of debug flag * Fix order of arguments in assertEqual * Added missed files to create venv with run_tests.sh * Pass empty dict not None for empty environment * Refactor environment functions in preparation for get_file * Updated from global requirements * Add support for build info API * Remove unused method 'string_to_bool' from utils * Raise traceback on error when using CLI and -debug * Remove dependencies on pep8, pyflakes and flake8 * Sync strutils from oslo * Add to_dict() method to Resource class * Fix comparison with singletons * Copy run_test.sh from heat repo * Improve and unit-test template contents fetching * Test coverage of empty environment * Move environment and template file functions to own module * reconcile prepare_environment_file/url into one funtion * Use template_format.parse for local stack parsing * Fetch environment from fields rather than function arg * Move template_format from heat to heatclient * Update variable name in test_shell.py * Use assertRaises instead of try/except/else * Remove unused common.base module * Fix some trivial py3 errors * Remove ununsed httplib2 requirement * Added API reference document * Wrapped some long lines * Fixed typo error OS_IMAGE_URL * Sort resource-type-list output * Fix help formatting for resource-template * Fix inappropriate error message on event-list * Remove vim header * Enable deleting multiple stacks with single call * Increase test_resources coverage * Show better error when json fail to parse template * Updates .gitignore * Misc typos in Heat client * Supports bash_completion for heatclient * Reuse BaseManager and Resource from oslo * Sync base and exceptions from oslo * Allow the environment file to be specified via URL * Use jsonutils from oslo incubator * Updates tox.ini to use new features * Add support for resource_types * Updated from global requirements * Replace inheritance hierarchy with composition * Deprecate mox in favor of mox3 * Change ID column of Event table to UUID 0.2.6 ----- * Add back --token-only for invocation compatibility * Add --include-pass option for grizzly backwards compat * Revert "Don't call credentials_headers() twice" * Make tokens work with --os-no-client-auth * test_shell test current not deprecated options * Fix shell operation with --os-auth-token * Don't call credentials_headers() twice * Use fixtures for all shell environment variables * Cleanup exc.verbose after tests * Pass only tenant name or ID to keystoneclient * use correct url on Windows platform * Fix i18n error when resource-name is non-english * Remove --token-only option, it does nothing * Honor endpoint_type when requesting keystone for a token * Revert "heatclient is not working with os-auth-token" * Updated from global requirements * Encode output for fixing UnicodeDecodeError * Add a basic man page for heat * Returning the json body after a stack-create or stack-update * heatclient is not working with os-auth-token * Add a top level introduction to the index * Add a contibuting section to index.rst * Remove the release notes as it is not maintained here * Resync doc/source/conf.py from heat for consistency * Add doc/Makefile to help with building docs * Print a more correct error with event-show * Fix the testname of scenario * remove python 2.5 support for parse_sql * align the order of parameters for urlencode() * Replace urllib/urllib2 with urlutils * Transform print statement to print function * Use the six library for compatability * Import urlutils from openstack common * Updated from global requirements * Import httplib from six.moves * Replace mox with mox3 * Allow env registry paths to be relative to env file * Updated from global requirements 0.2.5 ----- * Allow -P to be invoked multiple times * Replace OpenStack LLC with OpenStack Foundation * Add handler for heatclient.common.http * Support --version for heatclient * Stack instance identifier property * Added support for running the tests under PyPy with tox * Fix and enable Hacking H501 warning * Be backward compatible after a renaming in API * Rename event logical_resource_id to resource_name * Add X-Region-Name parameter in HTTP request * Apply OS_REGION_NAME for endpoint selection * Do not obscure Unauthorized error messages * Fix heat event list sorted order incorrect * Updated from global requirements * Generate a template from a resource 0.2.4 ----- * Remove unused Stack data method * Do not paginate stack list unless page_size is set * Custom Stack get method to avoid lookup redirects * Fix Stack instance delete method * Add mock as a test requirement * Sync with global requirements * Implement Stack status and action properties * Parse error object (in json format) returned by heat-api * Set credentials headers if there is no auth token * Validate heat url is supplied for --os-no-client-auth 0.2.3 ----- * Cleanup in preperation for release * Display yaml format for stack deployed via hot template * Make the parameter checking consistent * Rename README.md to README.rst * Raise requirements to be in sync with OpenStack/Requirements * Update upper bound of keystoneclient version * Fix "heat validate" (add needed environment option) * Only set X-Auth-User, X-Auth-Key on stack create/update * Format resource required_by in resource-show * Unit tests for pretty table special formatters * Add support for suspend/resume actions * Upload provider templates referenced in the environment * Fix all but H302 hacking rules * Fix all the pep8 E* and F* errors * Fix various smaller pep8 issues * flake8 ignore build directory * Stop passing path to VerifiedHTTPSConnection * Remove explicit distribute depend * Include .testr.conf in source tarball * Use Python 3.x compatible except: construct * Fixes text split for parameters * Add support for environment files * Migrate to testr from nose * Migrate test base classes to testtools * Move tests into package dir * Move requirements files to standard names * Add CONTRIBUTING file * Migrate to pbr * Fix some "H" pep errors * Migrate to flake8 * Cleaned up code in anticipation of flake8 * Restore compatibility with PrettyTable < 0.7.2 0.2.2 ----- * "heat stack-create" doesn't return the error message from server * Display resource metadata as indented json * Don't use human readable heading labels in tables * Return the body as a string instead of an iterable * Always pass username, even for --token-only * Allow for prettytable 0.7.x as well * Remove warlock from pip-requires as it is not used * Change --disable-rollback option to --enable-rollback 0.2.1 ----- * Don't add 'dev' to tag_build 0.2.0 ----- * heatclient : Add --disable-rollback option * heatclient : correct timeout parameter name * Return the exit code from tox to callers of run_tests.sh * Add switch to disable token authentication * Don't log at all if debugging is not turned on * Fixes required for packaging * Relax the required version of prettytable * Switch to using version.canonical_version_string * Update to latest oslo-incubator * Display a better error message on HTTP exception 0.1.0 ----- * Fix git repo url * Deprecate commands and add unified cli replacements * remove some glanceisms from the docs * add some pypi meta info * Update .gitreview for org move * Support for events list and details * Use python-keystoneclient 0.2 series * Pass template as a string if it is not JSON * Ignore heatclient/versioninfo * Implement client resource support * adding test coverage for common/http, improving the redirect handling * Support name or id for stack operations * Pass username and password as well as token * Add .gitreview file * pep8 and more tests * - Comment out commands that don't yet have a REST API equivalent - Use Name/ID when specifying a stack * Feature parity with REST API - Give a nice message for 404 for non-existent stack id - implement delete, gettemplate, validate - run do_list after create, delete - don't encode the json template as a json string * set up default logging even when not debugging * Allow sort column to be specified * An error response shouldn't be logged as an error, since it is part of the REST API * Implement describe, and format the results * Allow pretty_dict to take optional formatters * Shell unit tests, remove unused code, display real results from REST API * Allow labels to be specified separately from field keys * resolve test template file path no matter where it is run from * Some slightly more realistic mock responses. Regexp asserts to check pretty print is printing something * pass the dict to print_dict * Properly configure debug logging * Mock at the json request level instead of the http connection level. Test create * setup tweaks * Get fakes and mox working so that any http request/response sequence can be test scripted * pyc files should not have been committed * Format parameters for create call * add some more non-functioning skeleton code * fix readme path * Swap readme files * Define CLI commands and arguments for entire API * initial version * Initial import * first commit python-heatclient-0.2.8/doc/0000775000175400017540000000000012304731421017060 5ustar jenkinsjenkins00000000000000python-heatclient-0.2.8/doc/.gitignore0000664000175400017540000000002312304731347021052 0ustar jenkinsjenkins00000000000000build/ source/ref/ python-heatclient-0.2.8/doc/Makefile0000664000175400017540000000616012304731347020532 0ustar jenkinsjenkins00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXSOURCE = source PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SPHINXSOURCE) .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-heatclient.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-heatclient.qhc" latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." python-heatclient-0.2.8/doc/source/0000775000175400017540000000000012304731421020360 5ustar jenkinsjenkins00000000000000python-heatclient-0.2.8/doc/source/ext/0000775000175400017540000000000012304731421021160 5ustar jenkinsjenkins00000000000000python-heatclient-0.2.8/doc/source/ext/gen_ref.py0000664000175400017540000000431112304731347023145 0ustar jenkinsjenkins00000000000000# -*- 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 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) ROOT = os.path.abspath(os.path.join(BASE_DIR, "..", "..", "..")) sys.path.insert(0, ROOT) sys.path.insert(0, BASE_DIR) def gen_ref(ver, title, names): refdir = os.path.join(BASE_DIR, "ref") pkg = "heatclient" if ver: pkg = "%s.%s" % (pkg, ver) refdir = os.path.join(refdir, ver) if not os.path.exists(refdir): os.makedirs(refdir) idxpath = os.path.join(refdir, "index.rst") with open(idxpath, "w") as idx: idx.write(("%(title)s\n" "%(signs)s\n" "\n" ".. toctree::\n" " :maxdepth: 1\n" "\n") % {"title": title, "signs": "=" * len(title)}) for name in names: idx.write(" %s\n" % name) rstpath = os.path.join(refdir, "%s.rst" % name) with open(rstpath, "w") as rst: rst.write(("%(title)s\n" "%(signs)s\n" "\n" ".. automodule:: %(pkg)s.%(name)s\n" " :members:\n" " :undoc-members:\n" " :show-inheritance:\n" " :noindex:\n") % {"title": name.capitalize(), "signs": "=" * len(name), "pkg": pkg, "name": name}) gen_ref("", "Client Reference", ["client", "exc"]) gen_ref("v1", "Version 1 API Reference", ["stacks", "resources", "events", "actions", "software_configs", "software_deployments"]) python-heatclient-0.2.8/doc/source/conf.py0000664000175400017540000002077212304731347021676 0ustar jenkinsjenkins00000000000000# -*- 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. # # python-heatclient documentation build configuration file, created by # sphinx-quickstart on Sun Dec 6 14:19:25 2009. # # 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. import os # 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.append(os.path.abspath('.')) exec(open(os.path.join("ext", "gen_ref.py")).read()) # -- 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'] # Add any paths that contain templates here, relative to this directory. if os.getenv('HUDSON_PUBLISH_DOCS'): templates_path = ['_ga', '_templates'] else: templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' # General information about the project. project = 'python-heatclient' copyright = 'OpenStack Contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '2.13' # The full version, including alpha/beta/rc tags. release = '2.13.0' # 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 = [] primary_domain = 'py' nitpicky = 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_path = ['.'] # html_theme = '_theme' # 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 = { "nosidebar": "false" } # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ['_static'] # 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' git_cmd = "git log --pretty=format:'%ad, commit %h' --date=local -n1" html_last_updated_fmt = os.popen(git_cmd).read() # 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 = 'python-heatclientdoc' # -- 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]) latex_documents = [ ('index', 'python-heatclient.tex', 'python-heatclient Documentation', u'OpenStack Foundation', '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 = [ ('man/heat', 'heat', u'Command line access to the heat project.', [u'Heat 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', 'Heat', u'Heat Documentation', u'Heat Developers', 'Heat', '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' python-heatclient-0.2.8/doc/source/index.rst0000664000175400017540000000431112304731347022227 0ustar jenkinsjenkins00000000000000Python bindings to the OpenStack Heat API ========================================= This is a client for OpenStack Heat API. There's a Python API (the :mod:`heatclient` module), and a command-line script (installed as :program:`heat`). Python API ========== In order to use the python api directly, you must first obtain an auth token and identify which endpoint you wish to speak to:: >>> tenant_id = 'b363706f891f48019483f8bd6503c54b' >>> heat_url = 'http://heat.example.org:8004/v1/%s' % tenant_id >>> auth_token = '3bcc3d3a03f44e3d8377f9247b0ad155' Once you have done so, you can use the API like so:: >>> from heatclient.client import Client >>> heat = Client('1', endpoint=heat_url, token=auth_token) Reference --------- .. toctree:: :maxdepth: 1 ref/index ref/v1/index Command-line Tool ================= In order to use the CLI, you must provide your OpenStack username, password, tenant, and auth endpoint. Use the corresponding configuration options (``--os-username``, ``--os-password``, ``--os-tenant-id``, and ``--os-auth-url``) or set them in environment variables:: export OS_USERNAME=user export OS_PASSWORD=pass export OS_TENANT_ID=b363706f891f48019483f8bd6503c54b export OS_AUTH_URL=http://auth.example.com:5000/v2.0 The command line tool will attempt to reauthenticate using your provided credentials for every request. You can override this behavior by manually supplying an auth token using ``--heat-url`` and ``--os-auth-token``. You can alternatively set these environment variables:: export HEAT_URL=http://heat.example.org:8004/v1/b363706f891f48019483f8bd6503c54b export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155 Once you've configured your authentication parameters, you can run ``heat help`` to see a complete listing of available commands. Man Pages ========= .. toctree:: :maxdepth: 1 man/heat Contributing ============ Code is hosted `on GitHub`_. Submit bugs to the Heat project on `Launchpad`_. Submit code to the openstack/python-heatclient project using `Gerrit`_. .. _on GitHub: https://github.com/openstack/python-heatclient .. _Launchpad: https://launchpad.net/python-heatclient .. _Gerrit: http://wiki.openstack.org/GerritWorkflow python-heatclient-0.2.8/doc/source/man/0000775000175400017540000000000012304731421021133 5ustar jenkinsjenkins00000000000000python-heatclient-0.2.8/doc/source/man/heat.rst0000664000175400017540000000256612304731350022620 0ustar jenkinsjenkins00000000000000==== heat ==== .. program:: heat SYNOPSIS ======== `heat` [options] [command-options] `heat help` `heat help` DESCRIPTION =========== `heat` is a command line client for controlling OpenStack Heat. Before the `heat` command is issued, ensure the environment contains the necessary variables so that the CLI can pass user credentials to the server. See `Getting Credentials for a CLI` section of `OpenStack CLI Guide` for more info. OPTIONS ======= To get a list of available commands and options run:: heat help To get usage and options of a command run:: heat help EXAMPLES ======== Get information about stack-create command:: heat help stack-create List available stacks:: heat stack-list List available resources in a stack:: heat resource-list Create a stack:: heat stack-create mystack -f some-template.yaml -P "KeyName=mine" View stack information:: heat stack-show mystack List stack outputs:: heat output-list Show the value of a single output:: heat output-show List events:: heat event-list mystack Delete a stack:: heat stack-delete mystack Abandon a stack:: heat stack-abandon mystack BUGS ==== Heat client is hosted in Launchpad so you can view current bugs at https://bugs.launchpad.net/python-heatclient/. python-heatclient-0.2.8/setup.cfg0000664000175400017540000000162512304731421020140 0ustar jenkinsjenkins00000000000000[metadata] name = python-heatclient summary = OpenStack Orchestration API Client Library description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = http://www.openstack.org/ classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 2.6 [files] packages = heatclient [entry_points] console_scripts = heat = heatclient.shell:main [global] setup-hooks = pbr.hooks.setup_hook [build_sphinx] source-dir = doc/source build-dir = doc/build all_files = 1 [upload_sphinx] upload-dir = doc/build/html [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 python-heatclient-0.2.8/setup.py0000775000175400017540000000141512304731347020040 0ustar jenkinsjenkins00000000000000#!/usr/bin/env python # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools setuptools.setup( setup_requires=['pbr'], pbr=True) python-heatclient-0.2.8/tools/0000775000175400017540000000000012304731421017453 5ustar jenkinsjenkins00000000000000python-heatclient-0.2.8/tools/heat.bash_completion0000664000175400017540000000156112304731347023476 0ustar jenkinsjenkins00000000000000# bash completion for openstack heat _heat_opts="" # lazy init _heat_flags="" # lazy init _heat_opts_exp="" # lazy init _heat() { local cur prev kbc COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" if [ "x$_heat_opts" == "x" ] ; then kbc="`heat bash-completion | sed -e "s/ -h / /"`" _heat_opts="`echo "$kbc" | sed -e "s/--[a-z0-9_-]*//g" -e "s/[ ][ ]*/ /g"`" _heat_flags="`echo " $kbc" | sed -e "s/ [^-][^-][a-z0-9_-]*//g" -e "s/[ ][ ]*/ /g"`" _heat_opts_exp="`echo $_heat_opts | sed -e "s/[ ]/|/g"`" fi if [[ " ${COMP_WORDS[@]} " =~ " "($_heat_opts_exp)" " && "$prev" != "help" ]] ; then COMPREPLY=($(compgen -W "${_heat_flags}" -- ${cur})) else COMPREPLY=($(compgen -W "${_heat_opts}" -- ${cur})) fi return 0 } complete -o default -o nospace -F _heat heat python-heatclient-0.2.8/tools/install_venv.py0000664000175400017540000000500712304731347022542 0ustar jenkinsjenkins00000000000000# Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. # # Copyright 2010 OpenStack Foundation # Copyright 2013 IBM Corp. # 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 ConfigParser import os import sys import install_venv_common as install_venv # flake8: noqa def print_help(project, venv, root): help = """ %(project)s development environment setup is complete. %(project)s development uses virtualenv to track and manage Python dependencies while in development and testing. To activate the %(project)s virtualenv for the extent of your current shell session you can run: $ source %(venv)s/bin/activate Or, if you prefer, you can run commands in the virtualenv on a case by case basis by running: $ %(root)s/tools/with_venv.sh """ print help % dict(project=project, venv=venv, root=root) def main(argv): root = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) if os.environ.get('tools_path'): root = os.environ['tools_path'] venv = os.path.join(root, '.venv') if os.environ.get('venv'): venv = os.environ['venv'] pip_requires = os.path.join(root, 'requirements.txt') test_requires = os.path.join(root, 'test-requirements.txt') py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1]) setup_cfg = ConfigParser.ConfigParser() setup_cfg.read('setup.cfg') project = setup_cfg.get('metadata', 'name') install = install_venv.InstallVenv( root, venv, pip_requires, test_requires, py_version, project) options = install.parse_args(argv) install.check_python_version() install.check_dependencies() install.create_virtualenv(no_site_packages=options.no_site_packages) install.install_dependencies() install.post_process() print_help(project, venv, root) if __name__ == '__main__': main(sys.argv) python-heatclient-0.2.8/tools/with_venv.sh0000775000175400017540000000030312304731347022026 0ustar jenkinsjenkins00000000000000#!/bin/bash command -v tox > /dev/null 2>&1 if [ $? -ne 0 ]; then echo 'This script requires "tox" to run.' echo 'You can install it with "pip install tox".' exit 1; fi tox -evenv -- $@ python-heatclient-0.2.8/tools/install_venv_common.py0000664000175400017540000001350612304731347024115 0ustar jenkinsjenkins00000000000000# Copyright 2013 OpenStack Foundation # Copyright 2013 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. """Provides methods needed by installation script for OpenStack development virtual environments. Since this script is used to bootstrap a virtualenv from the system's Python environment, it should be kept strictly compatible with Python 2.6. Synced in from openstack-common """ from __future__ import print_function import optparse import os import subprocess import sys class InstallVenv(object): def __init__(self, root, venv, requirements, test_requirements, py_version, project): self.root = root self.venv = venv self.requirements = requirements self.test_requirements = test_requirements self.py_version = py_version self.project = project def die(self, message, *args): print(message % args, file=sys.stderr) sys.exit(1) def check_python_version(self): if sys.version_info < (2, 6): self.die("Need Python Version >= 2.6") def run_command_with_code(self, cmd, redirect_output=True, check_exit_code=True): """Runs a command in an out-of-process shell. Returns the output of that command. Working directory is self.root. """ if redirect_output: stdout = subprocess.PIPE else: stdout = None proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout) output = proc.communicate()[0] if check_exit_code and proc.returncode != 0: self.die('Command "%s" failed.\n%s', ' '.join(cmd), output) return (output, proc.returncode) def run_command(self, cmd, redirect_output=True, check_exit_code=True): return self.run_command_with_code(cmd, redirect_output, check_exit_code)[0] def get_distro(self): if (os.path.exists('/etc/fedora-release') or os.path.exists('/etc/redhat-release')): return Fedora( self.root, self.venv, self.requirements, self.test_requirements, self.py_version, self.project) else: return Distro( self.root, self.venv, self.requirements, self.test_requirements, self.py_version, self.project) def check_dependencies(self): self.get_distro().install_virtualenv() def create_virtualenv(self, no_site_packages=True): """Creates the virtual environment and installs PIP. Creates the virtual environment and installs PIP only into the virtual environment. """ if not os.path.isdir(self.venv): print('Creating venv...', end=' ') if no_site_packages: self.run_command(['virtualenv', '-q', '--no-site-packages', self.venv]) else: self.run_command(['virtualenv', '-q', self.venv]) print('done.') else: print("venv already exists...") pass def pip_install(self, *args): self.run_command(['tools/with_venv.sh', 'pip', 'install', '--upgrade'] + list(args), redirect_output=False) def install_dependencies(self): print('Installing dependencies with pip (this can take a while)...') # First things first, make sure our venv has the latest pip and # setuptools and pbr self.pip_install('pip>=1.4') self.pip_install('setuptools') self.pip_install('pbr') self.pip_install('-r', self.requirements, '-r', self.test_requirements) def parse_args(self, argv): """Parses command-line arguments.""" parser = optparse.OptionParser() parser.add_option('-n', '--no-site-packages', action='store_true', help="Do not inherit packages from global Python " "install") return parser.parse_args(argv[1:])[0] class Distro(InstallVenv): def check_cmd(self, cmd): return bool(self.run_command(['which', cmd], check_exit_code=False).strip()) def install_virtualenv(self): if self.check_cmd('virtualenv'): return if self.check_cmd('easy_install'): print('Installing virtualenv via easy_install...', end=' ') if self.run_command(['easy_install', 'virtualenv']): print('Succeeded') return else: print('Failed') self.die('ERROR: virtualenv not found.\n\n%s development' ' requires virtualenv, please install it using your' ' favorite package management tool' % self.project) class Fedora(Distro): """This covers all Fedora-based distributions. Includes: Fedora, RHEL, CentOS, Scientific Linux """ def check_pkg(self, pkg): return self.run_command_with_code(['rpm', '-q', pkg], check_exit_code=False)[1] == 0 def install_virtualenv(self): if self.check_cmd('virtualenv'): return if not self.check_pkg('python-virtualenv'): self.die("Please install 'python-virtualenv'.") super(Fedora, self).install_virtualenv() python-heatclient-0.2.8/README.rst0000664000175400017540000000107512304731347020014 0ustar jenkinsjenkins00000000000000Python bindings to the Heat orchestration API ============================================= This is a client library for Heat built on the Heat orchestration API. It provides a Python API (the ``heatclient`` module) and a command-line tool (``heat``). Development takes place via the usual OpenStack processes as outlined in the `OpenStack wiki `_. The master repository is on `GitHub `_. See release notes and more at ``_. python-heatclient-0.2.8/openstack-common.conf0000664000175400017540000000034712304731347022452 0ustar jenkinsjenkins00000000000000[DEFAULT] # The list of modules to copy from openstack-common modules=importutils,gettextutils,strutils,apiclient.base,apiclient.exceptions module=py3kcompat # The base module to hold the copy of openstack.common base=heatclient python-heatclient-0.2.8/PKG-INFO0000664000175400017540000000256012304731421017413 0ustar jenkinsjenkins00000000000000Metadata-Version: 1.1 Name: python-heatclient Version: 0.2.8 Summary: OpenStack Orchestration API Client Library Home-page: http://www.openstack.org/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description: Python bindings to the Heat orchestration API ============================================= This is a client library for Heat built on the Heat orchestration API. It provides a Python API (the ``heatclient`` module) and a command-line tool (``heat``). Development takes place via the usual OpenStack processes as outlined in the `OpenStack wiki `_. The master repository is on `GitHub `_. See release notes and more at ``_. 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 :: 2.6 python-heatclient-0.2.8/.testr.conf0000664000175400017540000000022712304731347020411 0ustar jenkinsjenkins00000000000000[DEFAULT] test_command=${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list python-heatclient-0.2.8/python_heatclient.egg-info/0000775000175400017540000000000012304731421023526 5ustar jenkinsjenkins00000000000000python-heatclient-0.2.8/python_heatclient.egg-info/dependency_links.txt0000664000175400017540000000000112304731421027574 0ustar jenkinsjenkins00000000000000 python-heatclient-0.2.8/python_heatclient.egg-info/not-zip-safe0000664000175400017540000000000112304731417025761 0ustar jenkinsjenkins00000000000000 python-heatclient-0.2.8/python_heatclient.egg-info/PKG-INFO0000664000175400017540000000256012304731421024626 0ustar jenkinsjenkins00000000000000Metadata-Version: 1.1 Name: python-heatclient Version: 0.2.8 Summary: OpenStack Orchestration API Client Library Home-page: http://www.openstack.org/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description: Python bindings to the Heat orchestration API ============================================= This is a client library for Heat built on the Heat orchestration API. It provides a Python API (the ``heatclient`` module) and a command-line tool (``heat``). Development takes place via the usual OpenStack processes as outlined in the `OpenStack wiki `_. The master repository is on `GitHub `_. See release notes and more at ``_. 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 :: 2.6 python-heatclient-0.2.8/python_heatclient.egg-info/entry_points.txt0000664000175400017540000000006012304731421027020 0ustar jenkinsjenkins00000000000000[console_scripts] heat = heatclient.shell:main python-heatclient-0.2.8/python_heatclient.egg-info/top_level.txt0000664000175400017540000000001312304731421026252 0ustar jenkinsjenkins00000000000000heatclient python-heatclient-0.2.8/python_heatclient.egg-info/requires.txt0000664000175400017540000000016612304731421026131 0ustar jenkinsjenkins00000000000000pbr>=0.6,<1.0 iso8601>=0.1.8 PrettyTable>=0.7,<0.8 python-keystoneclient>=0.6.0 PyYAML>=3.1.0 six>=1.4.1 requests>=1.1python-heatclient-0.2.8/python_heatclient.egg-info/SOURCES.txt0000664000175400017540000000462312304731421025417 0ustar jenkinsjenkins00000000000000.testr.conf AUTHORS CONTRIBUTING.rst ChangeLog LICENSE MANIFEST.in README.rst openstack-common.conf requirements.txt run_tests.sh setup.cfg setup.py test-requirements.txt tox.ini doc/.gitignore doc/Makefile doc/source/conf.py doc/source/index.rst doc/source/ext/gen_ref.py doc/source/man/heat.rst heatclient/__init__.py heatclient/client.py heatclient/exc.py heatclient/shell.py heatclient/common/__init__.py heatclient/common/environment_format.py heatclient/common/http.py heatclient/common/template_format.py heatclient/common/template_utils.py heatclient/common/utils.py heatclient/openstack/__init__.py heatclient/openstack/common/__init__.py heatclient/openstack/common/gettextutils.py heatclient/openstack/common/importutils.py heatclient/openstack/common/jsonutils.py heatclient/openstack/common/strutils.py heatclient/openstack/common/timeutils.py heatclient/openstack/common/apiclient/__init__.py heatclient/openstack/common/apiclient/base.py heatclient/openstack/common/apiclient/exceptions.py heatclient/openstack/common/py3kcompat/__init__.py heatclient/openstack/common/py3kcompat/urlutils.py heatclient/tests/__init__.py heatclient/tests/fakes.py heatclient/tests/test_build_info.py heatclient/tests/test_common_http.py heatclient/tests/test_environment_format.py heatclient/tests/test_events.py heatclient/tests/test_resource_types.py heatclient/tests/test_resources.py heatclient/tests/test_shell.py heatclient/tests/test_software_configs.py heatclient/tests/test_software_deployments.py heatclient/tests/test_stacks.py heatclient/tests/test_template_format.py heatclient/tests/test_template_utils.py heatclient/tests/test_utils.py heatclient/tests/v1/__init__.py heatclient/tests/var/adopt_stack_data.json heatclient/tests/var/minimal.template heatclient/v1/__init__.py heatclient/v1/actions.py heatclient/v1/build_info.py heatclient/v1/client.py heatclient/v1/events.py heatclient/v1/resource_types.py heatclient/v1/resources.py heatclient/v1/shell.py heatclient/v1/software_configs.py heatclient/v1/software_deployments.py heatclient/v1/stacks.py python_heatclient.egg-info/PKG-INFO python_heatclient.egg-info/SOURCES.txt python_heatclient.egg-info/dependency_links.txt python_heatclient.egg-info/entry_points.txt python_heatclient.egg-info/not-zip-safe python_heatclient.egg-info/requires.txt python_heatclient.egg-info/top_level.txt tools/heat.bash_completion tools/install_venv.py tools/install_venv_common.py tools/with_venv.shpython-heatclient-0.2.8/LICENSE0000664000175400017540000002363612304731347017341 0ustar jenkinsjenkins00000000000000 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. python-heatclient-0.2.8/requirements.txt0000664000175400017540000000020012304731350021570 0ustar jenkinsjenkins00000000000000pbr>=0.6,<1.0 argparse iso8601>=0.1.8 PrettyTable>=0.7,<0.8 python-keystoneclient>=0.6.0 PyYAML>=3.1.0 six>=1.4.1 requests>=1.1 python-heatclient-0.2.8/heatclient/0000775000175400017540000000000012304731421020433 5ustar jenkinsjenkins00000000000000python-heatclient-0.2.8/heatclient/common/0000775000175400017540000000000012304731421021723 5ustar jenkinsjenkins00000000000000python-heatclient-0.2.8/heatclient/common/template_format.py0000664000175400017540000000442012304731350025461 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # 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 yaml HEAT_VERSIONS = (u'2012-12-12',) CFN_VERSIONS = (u'2010-09-09',) if hasattr(yaml, 'CSafeLoader'): yaml_loader = yaml.CSafeLoader else: yaml_loader = yaml.SafeLoader if hasattr(yaml, 'CSafeDumper'): yaml_dumper = yaml.CSafeDumper else: yaml_dumper = yaml.SafeDumper def _construct_yaml_str(self, node): # Override the default string handling function # to always return unicode objects return self.construct_scalar(node) yaml_loader.add_constructor(u'tag:yaml.org,2002:str', _construct_yaml_str) # Unquoted dates like 2013-05-23 in yaml files get loaded as objects of type # datetime.data which causes problems in API layer when being processed by # openstack.common.jsonutils. Therefore, make unicode string out of timestamps # until jsonutils can handle dates. yaml_loader.add_constructor(u'tag:yaml.org,2002:timestamp', _construct_yaml_str) def parse(tmpl_str): '''Takes a string and returns a dict containing the parsed structure. This includes determination of whether the string is using the JSON or YAML format. ''' if tmpl_str.startswith('{'): tpl = json.loads(tmpl_str) else: try: tpl = yaml.load(tmpl_str, Loader=yaml_loader) except yaml.YAMLError as yea: raise ValueError(yea) else: if tpl is None: tpl = {} # Looking for supported version keys in the loaded template if not ('HeatTemplateFormatVersion' in tpl or 'heat_template_version' in tpl or 'AWSTemplateFormatVersion' in tpl): raise ValueError("Template format version not found.") return tpl python-heatclient-0.2.8/heatclient/common/environment_format.py0000664000175400017540000000315512304731347026224 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # 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 heatclient.common.template_format import yaml_loader import yaml SECTIONS = (PARAMETERS, RESOURCE_REGISTRY) = \ ('parameters', 'resource_registry') def parse(env_str): '''Takes a string and returns a dict containing the parsed structure. This includes determination of whether the string is using the YAML format. ''' try: env = yaml.load(env_str, Loader=yaml_loader) except yaml.YAMLError as yea: raise ValueError(yea) else: if env is None: env = {} elif not isinstance(env, dict): raise ValueError('The environment is not a valid ' 'YAML mapping data type.') for param in env: if param not in SECTIONS: raise ValueError('environment has wrong section "%s"' % param) return env def default_for_missing(env): '''Checks a parsed environment for missing sections. ''' for param in SECTIONS: if param not in env: env[param] = {} python-heatclient-0.2.8/heatclient/common/template_utils.py0000664000175400017540000001215712304731350025337 0ustar jenkinsjenkins00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # 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 six from heatclient.common import environment_format from heatclient.common import template_format from heatclient import exc from heatclient.openstack.common.py3kcompat import urlutils def get_template_contents(template_file=None, template_url=None, template_object=None, object_request=None): # Transform a bare file path to a file:// URL. if template_file: template_url = normalise_file_path_to_url(template_file) if template_url: tpl = urlutils.urlopen(template_url).read() elif template_object: template_url = template_object tpl = object_request and object_request('GET', template_object) else: raise exc.CommandError('Need to specify exactly one of ' '--template-file, --template-url ' 'or --template-object') if not tpl: raise exc.CommandError('Could not fetch template from %s' % template_url) try: if isinstance(tpl, six.binary_type): tpl = tpl.decode('utf-8') template = template_format.parse(tpl) except ValueError as e: raise exc.CommandError( 'Error parsing template %s %s' % (template_url, e)) files = {} tmpl_base_url = base_url_for_url(template_url) resolve_template_get_files(template, files, tmpl_base_url) return files, template def resolve_template_get_files(template, files, template_base_url): def ignore_if(key, value): if key != 'get_file': return True if not isinstance(value, six.string_types): return True def recurse_if(value): return isinstance(value, (dict, list)) get_file_contents(template, files, template_base_url, ignore_if, recurse_if) def get_file_contents(from_data, files, base_url=None, ignore_if=None, recurse_if=None): if recurse_if and recurse_if(from_data): if isinstance(from_data, dict): recurse_data = six.itervalues(from_data) else: recurse_data = from_data for value in recurse_data: get_file_contents(value, files, base_url, ignore_if, recurse_if) if isinstance(from_data, dict): for key, value in iter(from_data.items()): if ignore_if and ignore_if(key, value): continue if base_url and not base_url.endswith('/'): base_url = base_url + '/' str_url = urlutils.urljoin(base_url, value) try: files[str_url] = urlutils.urlopen(str_url).read() except urlutils.URLError: raise exc.CommandError('Could not fetch contents for %s' % str_url) # replace the data value with the normalised absolute URL from_data[key] = str_url def base_url_for_url(url): parsed = urlutils.urlparse(url) parsed_dir = os.path.dirname(parsed.path) return urlutils.urljoin(url, parsed_dir) def normalise_file_path_to_url(path): if urlutils.urlparse(path).scheme: return path path = os.path.abspath(path) return urlutils.urljoin('file:', urlutils.pathname2url(path)) def process_environment_and_files(env_path=None, template=None, template_url=None): files = {} env = {} if env_path: env_url = normalise_file_path_to_url(env_path) env_base_url = base_url_for_url(env_url) raw_env = urlutils.urlopen(env_url).read() env = environment_format.parse(raw_env) resolve_environment_urls( env.get('resource_registry'), files, env_base_url) return files, env def resolve_environment_urls(resource_registry, files, env_base_url): if resource_registry is None: return rr = resource_registry base_url = rr.get('base_url', env_base_url) def ignore_if(key, value): if key == 'base_url': return True if isinstance(value, dict): return True if '::' in value: # Built in providers like: "X::Compute::Server" # don't need downloading. return True get_file_contents(rr, files, base_url, ignore_if) for res_name, res_dict in iter(rr.get('resources', {}).items()): res_base_url = res_dict.get('base_url', base_url) get_file_contents(res_dict, files, res_base_url, ignore_if) python-heatclient-0.2.8/heatclient/common/utils.py0000664000175400017540000001221712304731350023441 0ustar jenkinsjenkins00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # 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 __future__ import print_function import os import prettytable import sys import textwrap import uuid import yaml from heatclient import exc from heatclient.openstack.common import importutils from heatclient.openstack.common import jsonutils supported_formats = { "json": lambda x: jsonutils.dumps(x, indent=2), "yaml": yaml.safe_dump } # Decorator for cli-args def arg(*args, **kwargs): def _decorator(func): # Because of the semantics of decorator composition if we just append # to the options list positional options will appear to be backwards. func.__dict__.setdefault('arguments', []).insert(0, (args, kwargs)) return func return _decorator def link_formatter(links): return '\n'.join([l.get('href', '') for l in links or []]) def json_formatter(js): return jsonutils.dumps(js, indent=2) def text_wrap_formatter(d): return '\n'.join(textwrap.wrap(d or '', 55)) def newline_list_formatter(r): return '\n'.join(r or []) def print_list(objs, fields, field_labels=None, formatters={}, sortby=None): field_labels = field_labels or fields pt = prettytable.PrettyTable([f for f in field_labels], caching=False, print_empty=False) pt.align = 'l' for o in objs: row = [] for field in fields: if field in formatters: row.append(formatters[field](o)) else: data = getattr(o, field, None) or '' row.append(data) pt.add_row(row) if sortby is None: print(pt.get_string()) else: print(pt.get_string(sortby=field_labels[sortby])) def print_dict(d, formatters={}): pt = prettytable.PrettyTable(['Property', 'Value'], caching=False, print_empty=False) pt.align = 'l' for field in d.keys(): if field in formatters: pt.add_row([field, formatters[field](d[field])]) else: pt.add_row([field, d[field]]) print(pt.get_string(sortby='Property')) def find_resource(manager, name_or_id): """Helper for the _find_* methods.""" # first try to get entity as integer id try: if isinstance(name_or_id, int) or name_or_id.isdigit(): return manager.get(int(name_or_id)) except exc.NotFound: pass # now try to get entity as uuid try: uuid.UUID(str(name_or_id)) return manager.get(name_or_id) except (ValueError, exc.NotFound): pass # finally try to find entity by name try: return manager.find(name=name_or_id) except exc.NotFound: msg = "No %s with a name or ID of '%s' exists." % \ (manager.resource_class.__name__.lower(), name_or_id) raise exc.CommandError(msg) def env(*vars, **kwargs): """Search for the first defined of possibly many env vars Returns the first environment variable defined in vars, or returns the default defined in kwargs. """ for v in vars: value = os.environ.get(v) if value: return value return kwargs.get('default', '') def import_versioned_module(version, submodule=None): module = 'heatclient.v%s' % version if submodule: module = '.'.join((module, submodule)) return importutils.import_module(module) def exit(msg=''): if msg: print(msg, file=sys.stderr) sys.exit(1) def format_parameters(params): '''Reformat parameters into dict of format expected by the API.''' if not params: return {} # expect multiple invocations of --parameters but fall back # to ; delimited if only one --parameters is specified if len(params) == 1: params = params[0].split(';') parameters = {} for p in params: try: (n, v) = p.split(('='), 1) except ValueError: msg = '%s(%s). %s.' % ('Malformed parameter', p, 'Use the key=value format') raise exc.CommandError(msg) if n not in parameters: parameters[n] = v else: if not isinstance(parameters[n], list): parameters[n] = [parameters[n]] parameters[n].append(v) return parameters def format_output(output, format='yaml'): """Format the supplied dict as specified.""" output_format = format.lower() try: return supported_formats[output_format](output) except KeyError: raise exc.HTTPUnsupported("The format(%s) is unsupported." % output_format) python-heatclient-0.2.8/heatclient/common/http.py0000664000175400017540000002351712304731350023265 0ustar jenkinsjenkins00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # 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 logging import os import socket import requests import six from heatclient import exc from heatclient.openstack.common import jsonutils from heatclient.openstack.common.py3kcompat import urlutils from heatclient.openstack.common import strutils LOG = logging.getLogger(__name__) if not LOG.handlers: LOG.addHandler(logging.StreamHandler()) USER_AGENT = 'python-heatclient' CHUNKSIZE = 1024 * 64 # 64kB def get_system_ca_file(): """Return path to system default CA file.""" # Standard CA file locations for Debian/Ubuntu, RedHat/Fedora, # Suse, FreeBSD/OpenBSD, MacOSX, and the bundled ca ca_path = ['/etc/ssl/certs/ca-certificates.crt', '/etc/pki/tls/certs/ca-bundle.crt', '/etc/ssl/ca-bundle.pem', '/etc/ssl/cert.pem', '/System/Library/OpenSSL/certs/cacert.pem', requests.certs.where()] for ca in ca_path: LOG.debug("Looking for ca file %s", ca) if os.path.exists(ca): LOG.debug("Using ca file %s", ca) return ca LOG.warn("System ca file could not be found.") class HTTPClient(object): def __init__(self, endpoint, **kwargs): self.endpoint = endpoint self.auth_url = kwargs.get('auth_url') self.auth_token = kwargs.get('token') self.username = kwargs.get('username') self.password = kwargs.get('password') self.region_name = kwargs.get('region_name') self.include_pass = kwargs.get('include_pass') self.endpoint_url = endpoint self.cert_file = kwargs.get('cert_file') self.key_file = kwargs.get('key_file') self.ssl_connection_params = { 'ca_file': kwargs.get('ca_file'), 'cert_file': kwargs.get('cert_file'), 'key_file': kwargs.get('key_file'), 'insecure': kwargs.get('insecure'), } self.verify_cert = None if urlutils.urlparse(endpoint).scheme == "https": if kwargs.get('insecure'): self.verify_cert = False else: self.verify_cert = kwargs.get('ca_file', get_system_ca_file()) def log_curl_request(self, method, url, kwargs): curl = ['curl -i -X %s' % method] for (key, value) in kwargs['headers'].items(): header = '-H \'%s: %s\'' % (strutils.safe_decode(key), strutils.safe_decode(value)) curl.append(header) conn_params_fmt = [ ('key_file', '--key %s'), ('cert_file', '--cert %s'), ('ca_file', '--cacert %s'), ] for (key, fmt) in conn_params_fmt: value = self.ssl_connection_params.get(key) if value: curl.append(fmt % value) if self.ssl_connection_params.get('insecure'): curl.append('-k') if 'data' in kwargs: curl.append('-d \'%s\'' % kwargs['data']) curl.append('%s%s' % (self.endpoint, url)) LOG.debug(' '.join(curl)) @staticmethod def log_http_response(resp): status = (resp.raw.version / 10.0, resp.status_code, resp.reason) dump = ['\nHTTP/%.1f %s %s' % status] dump.extend(['%s: %s' % (k, v) for k, v in resp.headers.items()]) dump.append('') if resp.content: content = resp.content if isinstance(content, six.binary_type): content = content.decode() dump.extend([content, '']) LOG.debug('\n'.join(dump)) def _http_request(self, url, method, **kwargs): """Send an http request with the specified characteristics. Wrapper around requests.request to handle tasks such as setting headers and error handling. """ # Copy the kwargs so we can reuse the original in case of redirects kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {})) kwargs['headers'].setdefault('User-Agent', USER_AGENT) if self.auth_token: kwargs['headers'].setdefault('X-Auth-Token', self.auth_token) else: kwargs['headers'].update(self.credentials_headers()) if self.auth_url: kwargs['headers'].setdefault('X-Auth-Url', self.auth_url) if self.region_name: kwargs['headers'].setdefault('X-Region-Name', self.region_name) if self.include_pass and not 'X-Auth-Key' in kwargs['headers']: kwargs['headers'].update(self.credentials_headers()) self.log_curl_request(method, url, kwargs) if self.cert_file and self.key_file: kwargs['cert'] = (self.cert_file, self.key_file) if self.verify_cert is not None: kwargs['verify'] = self.verify_cert # Since requests does not follow the RFC when doing redirection to sent # back the same method on a redirect we are simply bypassing it. For # example if we do a DELETE/POST/PUT on a URL and we get a 302 RFC says # that we should follow that URL with the same method as before, # requests doesn't follow that and send a GET instead for the method. # Hopefully this could be fixed as they say in a comment in a future # point version i.e: 3.x # See issue: https://github.com/kennethreitz/requests/issues/1704 allow_redirects = False try: resp = requests.request( method, self.endpoint_url + url, allow_redirects=allow_redirects, **kwargs) except socket.gaierror as e: message = ("Error finding address for %(url)s: %(e)s" % {'url': self.endpoint_url + url, 'e': e}) raise exc.InvalidEndpoint(message=message) except (socket.error, socket.timeout) as e: endpoint = self.endpoint message = ("Error communicating with %(endpoint)s %(e)s" % {'endpoint': endpoint, 'e': e}) raise exc.CommunicationError(message=message) self.log_http_response(resp) if not 'X-Auth-Key' in kwargs['headers'] and \ (resp.status_code == 401 or (resp.status_code == 500 and "(HTTP 401)" in resp.content)): raise exc.HTTPUnauthorized("Authentication failed. Please try" " again with option " "--include-password or export " "HEAT_INCLUDE_PASSWORD=1\n%s" % resp.content) elif 400 <= resp.status_code < 600: raise exc.from_response(resp) elif resp.status_code in (301, 302, 305): # Redirected. Reissue the request to the new location. location = resp.headers.get('location') if location is None: message = "Location not returned with 302" raise exc.InvalidEndpoint(message=message) elif location.startswith(self.endpoint): # shave off the endpoint, it will be prepended when we recurse location = location[len(self.endpoint):] else: message = "Prohibited endpoint redirect %s" % location raise exc.InvalidEndpoint(message=message) return self._http_request(location, method, **kwargs) elif resp.status_code == 300: raise exc.from_response(resp) return resp def credentials_headers(self): creds = {} if self.username: creds['X-Auth-User'] = self.username if self.password: creds['X-Auth-Key'] = self.password return creds def json_request(self, method, url, **kwargs): kwargs.setdefault('headers', {}) kwargs['headers'].setdefault('Content-Type', 'application/json') kwargs['headers'].setdefault('Accept', 'application/json') if 'data' in kwargs: kwargs['data'] = jsonutils.dumps(kwargs['data']) resp = self._http_request(url, method, **kwargs) body = resp.content if 'application/json' in resp.headers.get('content-type', ''): try: body = resp.json() except ValueError: LOG.error('Could not decode response body as JSON') else: body = None return resp, body def raw_request(self, method, url, **kwargs): kwargs.setdefault('headers', {}) kwargs['headers'].setdefault('Content-Type', 'application/octet-stream') return self._http_request(url, method, **kwargs) def client_request(self, method, url, **kwargs): resp, body = self.json_request(method, url, **kwargs) return resp def head(self, url, **kwargs): return self.client_request("HEAD", url, **kwargs) def get(self, url, **kwargs): return self.client_request("GET", url, **kwargs) def post(self, url, **kwargs): return self.client_request("POST", url, **kwargs) def put(self, url, **kwargs): return self.client_request("PUT", url, **kwargs) def delete(self, url, **kwargs): return self.raw_request("DELETE", url, **kwargs) def patch(self, url, **kwargs): return self.client_request("PATCH", url, **kwargs) python-heatclient-0.2.8/heatclient/common/__init__.py0000664000175400017540000000000012304731347024031 0ustar jenkinsjenkins00000000000000python-heatclient-0.2.8/heatclient/tests/0000775000175400017540000000000012304731421021575 5ustar jenkinsjenkins00000000000000python-heatclient-0.2.8/heatclient/tests/test_shell.py0000664000175400017540000013567212304731350024334 0ustar jenkinsjenkins00000000000000# 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 re import six import sys import fixtures import tempfile import testscenarios import testtools from heatclient.openstack.common import jsonutils from heatclient.openstack.common.py3kcompat import urlutils from heatclient.openstack.common import strutils from mox3 import mox from keystoneclient.v2_0 import client as ksclient from heatclient.common import http from heatclient import exc import heatclient.shell from heatclient.tests import fakes load_tests = testscenarios.load_tests_apply_scenarios TEST_VAR_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), 'var')) class TestCase(testtools.TestCase): def set_fake_env(self, fake_env): client_env = ('OS_USERNAME', 'OS_PASSWORD', 'OS_TENANT_ID', 'OS_TENANT_NAME', 'OS_AUTH_URL', 'OS_REGION_NAME', 'OS_AUTH_TOKEN', 'OS_NO_CLIENT_AUTH', 'OS_SERVICE_TYPE', 'OS_ENDPOINT_TYPE', 'HEAT_URL') for key in client_env: self.useFixture( fixtures.EnvironmentVariable(key, fake_env.get(key))) # required for testing with Python 2.6 def assertRegexpMatches(self, text, expected_regexp, msg=None): """Fail the test unless the text matches the regular expression.""" if isinstance(expected_regexp, six.string_types): expected_regexp = re.compile(expected_regexp) if not expected_regexp.search(text): msg = msg or "Regexp didn't match" msg = '%s: %r not found in %r' % ( msg, expected_regexp.pattern, text) raise self.failureException(msg) def shell_error(self, argstr, error_match): orig = sys.stderr sys.stderr = six.StringIO() _shell = heatclient.shell.HeatShell() e = self.assertRaises(Exception, _shell.main, argstr.split()) self.assertRegexpMatches(e.__str__(), error_match) err = sys.stderr.getvalue() sys.stderr.close() sys.stderr = orig return err class EnvVarTest(TestCase): scenarios = [ ('username', dict( remove='OS_USERNAME', err='You must provide a username')), ('password', dict( remove='OS_PASSWORD', err='You must provide a password')), ('tenant_name', dict( remove='OS_TENANT_NAME', err='You must provide a tenant_id')), ('auth_url', dict( remove='OS_AUTH_URL', err='You must provide an auth url')), ] def test_missing_auth(self): fake_env = { 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_NAME': 'tenant_name', 'OS_AUTH_URL': 'http://no.where', } fake_env[self.remove] = None self.set_fake_env(fake_env) self.shell_error('stack-list', self.err) class EnvVarTestToken(TestCase): scenarios = [ ('tenant_id', dict( remove='OS_TENANT_ID', err='You must provide a tenant_id')), ('auth_url', dict( remove='OS_AUTH_URL', err='You must provide an auth url')), ] def test_missing_auth(self): fake_env = { 'OS_AUTH_TOKEN': 'atoken', 'OS_TENANT_ID': 'tenant_id', 'OS_AUTH_URL': 'http://no.where', } fake_env[self.remove] = None self.set_fake_env(fake_env) self.shell_error('stack-list', self.err) class ShellParamValidationTest(TestCase): scenarios = [ ('create', dict( command='create ts -P "a!b"', err='Malformed parameter')), ('stack-create', dict( command='stack-create ts -P "ab"', err='Malformed parameter')), ('update', dict( command='update ts -P "a~b"', err='Malformed parameter')), ('stack-update', dict( command='stack-update ts -P "a-b"', err='Malformed parameter')), ('validate', dict( command='validate -P "a=b;c"', err='Malformed parameter')), ('template-validate', dict( command='template-validate -P "a$b"', err='Malformed parameter')), ] def setUp(self): super(ShellParamValidationTest, self).setUp() self.m = mox.Mox() self.addCleanup(self.m.VerifyAll) self.addCleanup(self.m.UnsetStubs) def test_bad_parameters(self): self.m.StubOutWithMock(ksclient, 'Client') self.m.StubOutWithMock(http.HTTPClient, 'json_request') fakes.script_keystone_client() self.m.ReplayAll() fake_env = { 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_NAME': 'tenant_name', 'OS_AUTH_URL': 'http://no.where', } self.set_fake_env(fake_env) template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') cmd = '%s --template-file=%s ' % (self.command, template_file) self.shell_error(cmd, self.err) class ShellValidationTest(TestCase): def setUp(self): super(ShellValidationTest, self).setUp() self.m = mox.Mox() self.addCleanup(self.m.VerifyAll) self.addCleanup(self.m.UnsetStubs) def test_failed_auth(self): self.m.StubOutWithMock(ksclient, 'Client') self.m.StubOutWithMock(http.HTTPClient, 'json_request') fakes.script_keystone_client() failed_msg = 'Unable to authenticate user with credentials provided' http.HTTPClient.json_request( 'GET', '/stacks?').AndRaise(exc.Unauthorized(failed_msg)) self.m.ReplayAll() fake_env = { 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_NAME': 'tenant_name', 'OS_AUTH_URL': 'http://no.where', } self.set_fake_env(fake_env) self.shell_error('stack-list', failed_msg) def test_stack_create_validation(self): self.m.StubOutWithMock(ksclient, 'Client') self.m.StubOutWithMock(http.HTTPClient, 'json_request') fakes.script_keystone_client() self.m.ReplayAll() fake_env = { 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_NAME': 'tenant_name', 'OS_AUTH_URL': 'http://no.where', } self.set_fake_env(fake_env) self.shell_error( 'stack-create teststack ' '--parameters="InstanceType=m1.large;DBUsername=wp;' 'DBPassword=verybadpassword;KeyName=heat_key;' 'LinuxDistribution=F17"', 'Need to specify exactly one of') class ShellBase(TestCase): def setUp(self): super(ShellBase, self).setUp() self.m = mox.Mox() self.m.StubOutWithMock(ksclient, 'Client') self.m.StubOutWithMock(http.HTTPClient, 'json_request') self.m.StubOutWithMock(http.HTTPClient, 'raw_request') self.addCleanup(self.m.VerifyAll) self.addCleanup(self.m.UnsetStubs) # Some tests set exc.verbose = 1, so reset on cleanup def unset_exc_verbose(): exc.verbose = 0 self.addCleanup(unset_exc_verbose) def shell(self, argstr): orig = sys.stdout try: sys.stdout = six.StringIO() _shell = heatclient.shell.HeatShell() _shell.main(argstr.split()) except SystemExit: exc_type, exc_value, exc_traceback = sys.exc_info() self.assertEqual(0, exc_value.code) finally: out = sys.stdout.getvalue() sys.stdout.close() sys.stdout = orig return out class ShellTestCommon(ShellBase): def setUp(self): super(ShellTestCommon, self).setUp() def test_help_unknown_command(self): self.assertRaises(exc.CommandError, self.shell, 'help foofoo') def test_help(self): required = [ '^usage: heat', '(?m)^See "heat help COMMAND" for help on a specific command', ] for argstr in ['--help', 'help']: help_text = self.shell(argstr) for r in required: self.assertRegexpMatches(help_text, r) def test_debug_switch_raises_error(self): fakes.script_keystone_client() http.HTTPClient.json_request( 'GET', '/stacks?').AndRaise(exc.Unauthorized("FAIL")) self.m.ReplayAll() fake_env = { 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_NAME': 'tenant_name', 'OS_AUTH_URL': 'http://no.where', } self.set_fake_env(fake_env) args = ['--debug', 'stack-list'] self.assertRaises(exc.Unauthorized, heatclient.shell.main, args) def test_dash_d_switch_raises_error(self): fakes.script_keystone_client() http.HTTPClient.json_request( 'GET', '/stacks?').AndRaise(exc.CommandError("FAIL")) self.m.ReplayAll() fake_env = { 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_NAME': 'tenant_name', 'OS_AUTH_URL': 'http://no.where', } self.set_fake_env(fake_env) args = ['-d', 'stack-list'] self.assertRaises(exc.CommandError, heatclient.shell.main, args) def test_no_debug_switch_no_raises_errors(self): fakes.script_keystone_client() http.HTTPClient.json_request( 'GET', '/stacks?').AndRaise(exc.Unauthorized("FAIL")) self.m.ReplayAll() fake_env = { 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_NAME': 'tenant_name', 'OS_AUTH_URL': 'http://no.where', } self.set_fake_env(fake_env) args = ['stack-list'] self.assertRaises(SystemExit, heatclient.shell.main, args) def test_help_on_subcommand(self): required = [ '^usage: heat stack-list', "(?m)^List the user's stacks", ] argstrings = [ 'help stack-list', ] for argstr in argstrings: help_text = self.shell(argstr) for r in required: self.assertRegexpMatches(help_text, r) class ShellTestUserPass(ShellBase): def setUp(self): super(ShellTestUserPass, self).setUp() self._set_fake_env() # Patch os.environ to avoid required auth info. def _set_fake_env(self): fake_env = { 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_NAME': 'tenant_name', 'OS_AUTH_URL': 'http://no.where', } self.set_fake_env(fake_env) def _script_keystone_client(self): fakes.script_keystone_client() def test_stack_list(self): self._script_keystone_client() fakes.script_heat_list() self.m.ReplayAll() list_text = self.shell('stack-list') required = [ 'id', 'stack_status', 'creation_time', 'teststack', '1', 'CREATE_COMPLETE', 'IN_PROGRESS', ] for r in required: self.assertRegexpMatches(list_text, r) def test_stack_list_with_args(self): self._script_keystone_client() expected_url = ('/stacks?' 'status=COMPLETE&status=FAILED' '&marker=fake_id&limit=2') fakes.script_heat_list(expected_url) self.m.ReplayAll() list_text = self.shell('stack-list' ' --limit 2' ' --marker fake_id' ' --filters=status=COMPLETE' ' --filters=status=FAILED') required = [ 'teststack', 'teststack2', ] for r in required: self.assertRegexpMatches(list_text, r) def test_parsable_error(self): message = "The Stack (bad) could not be found." resp_dict = { "explanation": "The resource could not be found.", "code": 404, "error": { "message": message, "type": "StackNotFound", "traceback": "", }, "title": "Not Found" } self._script_keystone_client() fakes.script_heat_error(jsonutils.dumps(resp_dict)) self.m.ReplayAll() e = self.assertRaises(exc.HTTPException, self.shell, "stack-show bad") self.assertEqual("ERROR: " + message, str(e)) def test_parsable_verbose(self): message = "The Stack (bad) could not be found." resp_dict = { "explanation": "The resource could not be found.", "code": 404, "error": { "message": message, "type": "StackNotFound", "traceback": "", }, "title": "Not Found" } self._script_keystone_client() fakes.script_heat_error(jsonutils.dumps(resp_dict)) self.m.ReplayAll() exc.verbose = 1 e = self.assertRaises(exc.HTTPException, self.shell, "stack-show bad") self.assertIn(message, str(e)) def test_parsable_malformed_error(self): invalid_json = "ERROR: {Invalid JSON Error." self._script_keystone_client() fakes.script_heat_error(invalid_json) self.m.ReplayAll() e = self.assertRaises(exc.HTTPException, self.shell, "stack-show bad") self.assertEqual("ERROR: " + invalid_json, str(e)) def test_parsable_malformed_error_missing_message(self): missing_message = { "explanation": "The resource could not be found.", "code": 404, "error": { "type": "StackNotFound", "traceback": "", }, "title": "Not Found" } self._script_keystone_client() fakes.script_heat_error(jsonutils.dumps(missing_message)) self.m.ReplayAll() e = self.assertRaises(exc.HTTPException, self.shell, "stack-show bad") self.assertEqual("ERROR: Internal Error", str(e)) def test_parsable_malformed_error_missing_traceback(self): message = "The Stack (bad) could not be found." resp_dict = { "explanation": "The resource could not be found.", "code": 404, "error": { "message": message, "type": "StackNotFound", }, "title": "Not Found" } self._script_keystone_client() fakes.script_heat_error(jsonutils.dumps(resp_dict)) self.m.ReplayAll() exc.verbose = 1 e = self.assertRaises(exc.HTTPException, self.shell, "stack-show bad") self.assertEqual("ERROR: The Stack (bad) could not be found.\n", str(e)) def test_stack_show(self): self._script_keystone_client() resp_dict = {"stack": { "id": "1", "stack_name": "teststack", "stack_status": 'CREATE_COMPLETE', "creation_time": "2012-10-25T01:58:47Z" }} resp = fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, jsonutils.dumps(resp_dict)) http.HTTPClient.json_request( 'GET', '/stacks/teststack/1').AndReturn((resp, resp_dict)) self.m.ReplayAll() list_text = self.shell('stack-show teststack/1') required = [ 'id', 'stack_name', 'stack_status', 'creation_time', 'teststack', 'CREATE_COMPLETE', '2012-10-25T01:58:47Z' ] for r in required: self.assertRegexpMatches(list_text, r) def test_stack_abandon(self): self._script_keystone_client() resp_dict = {"stack": { "id": "1", "stack_name": "teststack", "stack_status": 'CREATE_COMPLETE', "creation_time": "2012-10-25T01:58:47Z" }} abandoned_stack = { "action": "CREATE", "status": "COMPLETE", "name": "teststack", "id": "1", "resources": { "foo": { "name": "foo", "resource_id": "test-res-id", "action": "CREATE", "status": "COMPLETE", "resource_data": {}, "metadata": {}, } } } resp = fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, jsonutils.dumps(resp_dict)) http.HTTPClient.json_request( 'GET', '/stacks/teststack/1').AndReturn((resp, resp_dict)) http.HTTPClient.json_request( 'DELETE', '/stacks/teststack/1/abandon').AndReturn((resp, abandoned_stack)) self.m.ReplayAll() abandon_resp = self.shell('stack-abandon teststack/1') self.assertEqual(abandoned_stack, jsonutils.loads(abandon_resp)) def _output_fake_response(self): self._script_keystone_client() resp_dict = {"stack": { "id": "1", "stack_name": "teststack", "stack_status": 'CREATE_COMPLETE', "creation_time": "2012-10-25T01:58:47Z", "outputs": [ { "output_value": "value1", "output_key": "output1", "description": "test output 1", }, { "output_value": ["output", "value", "2"], "output_key": "output2", "description": "test output 2", }, ], "creation_time": "2012-10-25T01:58:47Z" }} resp = fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, jsonutils.dumps(resp_dict)) http.HTTPClient.json_request( 'GET', '/stacks/teststack/1').AndReturn((resp, resp_dict)) self.m.ReplayAll() def test_output_list(self): self._output_fake_response() list_text = self.shell('output-list teststack/1') for r in ['output1', 'output2']: self.assertRegexpMatches(list_text, r) def test_output_show(self): self._output_fake_response() list_text = self.shell('output-show teststack/1 output1') self.assertRegexpMatches(list_text, 'value1') def test_template_show_cfn(self): self._script_keystone_client() template_data = open(os.path.join(TEST_VAR_DIR, 'minimal.template')).read() resp = fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, template_data) resp_dict = jsonutils.loads(template_data) http.HTTPClient.json_request( 'GET', '/stacks/teststack/template').AndReturn((resp, resp_dict)) self.m.ReplayAll() show_text = self.shell('template-show teststack') required = [ '{', ' "AWSTemplateFormatVersion": "2010-09-09"', ' "Outputs": {}', ' "Resources": {}', ' "Parameters": {}', '}' ] for r in required: self.assertRegexpMatches(show_text, r) def test_template_show_hot(self): self._script_keystone_client() resp_dict = {"heat_template_version": "2013-05-23", "parameters": {}, "resources": {}, "outputs": {}} resp = fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, jsonutils.dumps(resp_dict)) http.HTTPClient.json_request( 'GET', '/stacks/teststack/template').AndReturn((resp, resp_dict)) self.m.ReplayAll() show_text = self.shell('template-show teststack') required = [ "heat_template_version: '2013-05-23'", "outputs: {}", "parameters: {}", "resources: {}" ] for r in required: self.assertRegexpMatches(show_text, r) def test_stack_create(self): self._script_keystone_client() resp = fakes.FakeHTTPResponse( 201, 'Created', {'location': 'http://no.where/v1/tenant_id/stacks/teststack2/2'}, None) http.HTTPClient.json_request( 'POST', '/stacks', data=mox.IgnoreArg(), headers={'X-Auth-Key': 'password', 'X-Auth-User': 'username'} ).AndReturn((resp, None)) fakes.script_heat_list() self.m.ReplayAll() template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') create_text = self.shell( 'stack-create teststack ' '--template-file=%s ' '--parameters="InstanceType=m1.large;DBUsername=wp;' 'DBPassword=verybadpassword;KeyName=heat_key;' 'LinuxDistribution=F17"' % template_file) required = [ 'stack_name', 'id', 'teststack', '1' ] for r in required: self.assertRegexpMatches(create_text, r) def test_stack_create_url(self): self._script_keystone_client() resp = fakes.FakeHTTPResponse( 201, 'Created', {'location': 'http://no.where/v1/tenant_id/stacks/teststack2/2'}, None) self.m.StubOutWithMock(urlutils, 'urlopen') urlutils.urlopen('http://no.where/minimal.template').AndReturn( six.StringIO('{"AWSTemplateFormatVersion" : "2010-09-09"}')) http.HTTPClient.json_request( 'POST', '/stacks', data=mox.IgnoreArg(), headers={'X-Auth-Key': 'password', 'X-Auth-User': 'username'} ).AndReturn((resp, None)) fakes.script_heat_list() self.m.ReplayAll() create_text = self.shell( 'stack-create teststack ' '--template-url=http://no.where/minimal.template ' '--parameters="InstanceType=m1.large;DBUsername=wp;' 'DBPassword=verybadpassword;KeyName=heat_key;' 'LinuxDistribution=F17"') required = [ 'stack_name', 'id', 'teststack2', '2' ] for r in required: self.assertRegexpMatches(create_text, r) def test_stack_create_object(self): self._script_keystone_client() template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') template_data = open(template_file).read() http.HTTPClient.raw_request( 'GET', 'http://no.where/container/minimal.template', ).AndReturn(template_data) resp = fakes.FakeHTTPResponse( 201, 'Created', {'location': 'http://no.where/v1/tenant_id/stacks/teststack2/2'}, None) http.HTTPClient.json_request( 'POST', '/stacks', data=mox.IgnoreArg(), headers={'X-Auth-Key': 'password', 'X-Auth-User': 'username'} ).AndReturn((resp, None)) fakes.script_heat_list() self.m.ReplayAll() create_text = self.shell( 'stack-create teststack2 ' '--template-object=http://no.where/container/minimal.template ' '--parameters="InstanceType=m1.large;DBUsername=wp;' 'DBPassword=verybadpassword;KeyName=heat_key;' 'LinuxDistribution=F17"') required = [ 'stack_name', 'id', 'teststack2', '2' ] for r in required: self.assertRegexpMatches(create_text, r) def test_stack_adopt(self): self._script_keystone_client() resp = fakes.FakeHTTPResponse( 201, 'Created', {'location': 'http://no.where/v1/tenant_id/stacks/teststack/1'}, None) http.HTTPClient.json_request( 'POST', '/stacks', data=mox.IgnoreArg(), headers={'X-Auth-Key': 'password', 'X-Auth-User': 'username'} ).AndReturn((resp, None)) fakes.script_heat_list() self.m.ReplayAll() template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') adopt_data_file = os.path.join(TEST_VAR_DIR, 'adopt_stack_data.json') adopt_text = self.shell( 'stack-adopt teststack ' '--template-file=%s ' '--adopt-file=%s ' '--parameters="InstanceType=m1.large;DBUsername=wp;' 'DBPassword=verybadpassword;KeyName=heat_key;' 'LinuxDistribution=F17"' % (template_file, adopt_data_file)) required = [ 'stack_name', 'id', 'teststack', '1' ] for r in required: self.assertRegexpMatches(adopt_text, r) def test_stack_adopt_without_data(self): failed_msg = 'Need to specify --adopt-file' self._script_keystone_client() self.m.ReplayAll() template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') self.shell_error( 'stack-adopt teststack ' '--template-file=%s ' % template_file, failed_msg) def test_stack_update(self): self._script_keystone_client() resp = fakes.FakeHTTPResponse( 202, 'Accepted', {}, 'The request is accepted for processing.') http.HTTPClient.json_request( 'PUT', '/stacks/teststack2/2', data=mox.IgnoreArg(), headers={'X-Auth-Key': 'password', 'X-Auth-User': 'username'} ).AndReturn((resp, None)) fakes.script_heat_list() self.m.ReplayAll() template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') update_text = self.shell( 'stack-update teststack2/2 ' '--template-file=%s ' '--parameters="InstanceType=m1.large;DBUsername=wp;' 'DBPassword=verybadpassword;KeyName=heat_key;' 'LinuxDistribution=F17"' % template_file) required = [ 'stack_name', 'id', 'teststack2', '1' ] for r in required: self.assertRegexpMatches(update_text, r) def test_stack_delete(self): self._script_keystone_client() resp = fakes.FakeHTTPResponse( 204, 'No Content', {}, None) http.HTTPClient.raw_request( 'DELETE', '/stacks/teststack2/2', ).AndReturn((resp, None)) fakes.script_heat_list() self.m.ReplayAll() delete_text = self.shell('stack-delete teststack2/2') required = [ 'stack_name', 'id', 'teststack', '1' ] for r in required: self.assertRegexpMatches(delete_text, r) def test_stack_delete_multiple(self): self._script_keystone_client() resp = fakes.FakeHTTPResponse( 204, 'No Content', {}, None) http.HTTPClient.raw_request( 'DELETE', '/stacks/teststack1/1', ).AndReturn((resp, None)) http.HTTPClient.raw_request( 'DELETE', '/stacks/teststack2/2', ).AndReturn((resp, None)) fakes.script_heat_list() self.m.ReplayAll() delete_text = self.shell('stack-delete teststack1/1 teststack2/2') required = [ 'stack_name', 'id', 'teststack', '1' ] for r in required: self.assertRegexpMatches(delete_text, r) def test_build_info(self): self._script_keystone_client() resp_dict = { 'build_info': { 'api': {'revision': 'api_revision'}, 'engine': {'revision': 'engine_revision'} } } resp_string = jsonutils.dumps(resp_dict) headers = {'content-type': 'application/json'} http_resp = fakes.FakeHTTPResponse(200, 'OK', headers, resp_string) response = (http_resp, resp_dict) http.HTTPClient.json_request('GET', '/build_info').AndReturn(response) self.m.ReplayAll() build_info_text = self.shell('build-info') required = [ 'api', 'engine', 'revision', 'api_revision', 'engine_revision', ] for r in required: self.assertRegexpMatches(build_info_text, r) class ShellTestEvents(ShellBase): def setUp(self): super(ShellTestEvents, self).setUp() self._set_fake_env() # Patch os.environ to avoid required auth info. def _set_fake_env(self): fake_env = { 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_NAME': 'tenant_name', 'OS_AUTH_URL': 'http://no.where', } self.set_fake_env(fake_env) def _script_keystone_client(self): fakes.script_keystone_client() scenarios = [ ('integer_id', dict( event_id_one='24', event_id_two='42')), ('uuid_id', dict( event_id_one='3d68809e-c4aa-4dc9-a008-933823d2e44f', event_id_two='43b68bae-ed5d-4aed-a99f-0b3d39c2418a'))] def test_event_list(self): self._script_keystone_client() resp_dict = {"events": [ {"event_time": "2013-12-05T14:14:30Z", "id": self.event_id_one, "links": [{"href": "http://heat.example.com:8004/foo", "rel": "self"}, {"href": "http://heat.example.com:8004/foo2", "rel": "resource"}, {"href": "http://heat.example.com:8004/foo3", "rel": "stack"}], "logical_resource_id": "aResource", "physical_resource_id": None, "resource_name": "aResource", "resource_status": "CREATE_IN_PROGRESS", "resource_status_reason": "state changed"}, {"event_time": "2013-12-05T14:14:30Z", "id": self.event_id_two, "links": [{"href": "http://heat.example.com:8004/foo", "rel": "self"}, {"href": "http://heat.example.com:8004/foo2", "rel": "resource"}, {"href": "http://heat.example.com:8004/foo3", "rel": "stack"}], "logical_resource_id": "aResource", "physical_resource_id": "bce15ec4-8919-4a02-8a90-680960fb3731", "resource_name": "aResource", "resource_status": "CREATE_COMPLETE", "resource_status_reason": "state changed"}]} resp = fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, jsonutils.dumps(resp_dict)) stack_id = 'teststack/1' resource_name = 'testresource/1' http.HTTPClient.json_request( 'GET', '/stacks/%s/resources/%s/events' % ( urlutils.quote(stack_id, ''), urlutils.quote(strutils.safe_encode( resource_name), ''))).AndReturn((resp, resp_dict)) self.m.ReplayAll() event_list_text = self.shell('event-list {0} --resource {1}'.format( stack_id, resource_name)) required = [ 'resource_name', 'id', 'resource_status_reason', 'resource_status', 'event_time', 'aResource', self.event_id_one, self.event_id_two, 'state changed', 'CREATE_IN_PROGRESS', 'CREATE_COMPLETE', '2013-12-05T14:14:30Z', '2013-12-05T14:14:30Z', ] for r in required: self.assertRegexpMatches(event_list_text, r) def test_event_show(self): self._script_keystone_client() resp_dict = {"event": {"event_time": "2013-12-05T14:14:30Z", "id": self.event_id_one, "links": [{"href": "http://heat.example.com:8004/foo", "rel": "self"}, {"href": "http://heat.example.com:8004/foo2", "rel": "resource"}, {"href": "http://heat.example.com:8004/foo3", "rel": "stack"}], "logical_resource_id": "aResource", "physical_resource_id": None, "resource_name": "aResource", "resource_properties": {"admin_user": "im_powerful", "availability_zone": "nova"}, "resource_status": "CREATE_IN_PROGRESS", "resource_status_reason": "state changed", "resource_type": "OS::Nova::Server" }} resp = fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, jsonutils.dumps(resp_dict)) stack_id = 'teststack/1' resource_name = 'testresource/1' http.HTTPClient.json_request( 'GET', '/stacks/%s/resources/%s/events/%s' % ( urlutils.quote(stack_id, ''), urlutils.quote(strutils.safe_encode( resource_name), ''), urlutils.quote(self.event_id_one, '') )).AndReturn((resp, resp_dict)) self.m.ReplayAll() event_list_text = self.shell('event-show {0} {1} {2}'.format( stack_id, resource_name, self.event_id_one)) required = [ 'Property', 'Value', 'event_time', '2013-12-05T14:14:30Z', 'id', self.event_id_one, 'links', 'http://heat.example.com:8004/foo[0-9]', 'logical_resource_id', 'physical_resource_id', 'resource_name', 'aResource', 'resource_properties', 'admin_user', 'availability_zone', 'resource_status', 'CREATE_IN_PROGRESS', 'resource_status_reason', 'state changed', 'resource_type', 'OS::Nova::Server', ] for r in required: self.assertRegexpMatches(event_list_text, r) class ShellTestResources(ShellBase): def setUp(self): super(ShellTestResources, self).setUp() self._set_fake_env() # Patch os.environ to avoid required auth info. def _set_fake_env(self): fake_env = { 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_NAME': 'tenant_name', 'OS_AUTH_URL': 'http://no.where', } self.set_fake_env(fake_env) def _script_keystone_client(self): fakes.script_keystone_client() def test_resource_list(self): self._script_keystone_client() resp_dict = {"resources": [ {"links": [{"href": "http://heat.example.com:8004/foo", "rel": "self"}, {"href": "http://heat.example.com:8004/foo2", "rel": "resource"}], "logical_resource_id": "aResource", "physical_resource_id": "43b68bae-ed5d-4aed-a99f-0b3d39c2418a", "resource_name": "aResource", "resource_status": "CREATE_COMPLETE", "resource_status_reason": "state changed", "resource_type": "OS::Nova::Server", "updated_time": "2014-01-06T16:14:26Z"}]} resp = fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, jsonutils.dumps(resp_dict)) stack_id = 'teststack/1' http.HTTPClient.json_request( 'GET', '/stacks/%s/resources' % ( stack_id)).AndReturn((resp, resp_dict)) self.m.ReplayAll() resource_list_text = self.shell('resource-list {0}'.format(stack_id)) required = [ 'resource_name', 'resource_type', 'resource_status', 'updated_time', 'aResource', 'OS::Nova::Server', 'CREATE_COMPLETE', '2014-01-06T16:14:26Z' ] for r in required: self.assertRegexpMatches(resource_list_text, r) def test_resource_show(self): self._script_keystone_client() resp_dict = {"resource": {"description": "", "links": [{"href": "http://heat.example.com:8004/foo", "rel": "self"}, {"href": "http://heat.example.com:8004/foo2", "rel": "resource"}], "logical_resource_id": "aResource", "physical_resource_id": "43b68bae-ed5d-4aed-a99f-0b3d39c2418a", "required_by": [], "resource_name": "aResource", "resource_status": "CREATE_COMPLETE", "resource_status_reason": "state changed", "resource_type": "OS::Nova::Server", "updated_time": "2014-01-06T16:14:26Z"}} resp = fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, jsonutils.dumps(resp_dict)) stack_id = 'teststack/1' resource_name = 'aResource' http.HTTPClient.json_request( 'GET', '/stacks/%s/resources/%s' % ( urlutils.quote(stack_id, ''), urlutils.quote(strutils.safe_encode( resource_name), '') )).AndReturn((resp, resp_dict)) self.m.ReplayAll() resource_show_text = self.shell('resource-show {0} {1}'.format( stack_id, resource_name)) required = [ 'description', 'links', 'http://heat.example.com:8004/foo[0-9]', 'logical_resource_id', 'aResource', 'physical_resource_id', '43b68bae-ed5d-4aed-a99f-0b3d39c2418a', 'required_by', 'resource_name', 'aResource', 'resource_status', 'CREATE_COMPLETE', 'resource_status_reason', 'state changed', 'resource_type', 'OS::Nova::Server', 'updated_time', '2014-01-06T16:14:26Z', ] for r in required: self.assertRegexpMatches(resource_show_text, r) def test_resource_signal(self): self._script_keystone_client() resp = fakes.FakeHTTPResponse( 200, 'OK', {}, '') stack_id = 'teststack/1' resource_name = 'aResource' http.HTTPClient.json_request( 'POST', '/stacks/%s/resources/%s/signal' % ( urlutils.quote(stack_id, ''), urlutils.quote(strutils.safe_encode( resource_name), '') ), data={'message': 'Content'}).AndReturn((resp, '')) self.m.ReplayAll() text = self.shell( 'resource-signal {0} {1} -D {{"message":"Content"}}'.format( stack_id, resource_name)) self.assertEqual("", text) def test_resource_signal_no_data(self): self._script_keystone_client() resp = fakes.FakeHTTPResponse( 200, 'OK', {}, '') stack_id = 'teststack/1' resource_name = 'aResource' http.HTTPClient.json_request( 'POST', '/stacks/%s/resources/%s/signal' % ( urlutils.quote(stack_id, ''), urlutils.quote(strutils.safe_encode( resource_name), '') ), data=None).AndReturn((resp, '')) self.m.ReplayAll() text = self.shell( 'resource-signal {0} {1}'.format(stack_id, resource_name)) self.assertEqual("", text) def test_resource_signal_no_json(self): self._script_keystone_client() stack_id = 'teststack/1' resource_name = 'aResource' self.m.ReplayAll() error = self.assertRaises( exc.CommandError, self.shell, 'resource-signal {0} {1} -D [2'.format( stack_id, resource_name)) self.assertIn('Data should be in JSON format', str(error)) def test_resource_signal_no_dict(self): self._script_keystone_client() stack_id = 'teststack/1' resource_name = 'aResource' self.m.ReplayAll() error = self.assertRaises( exc.CommandError, self.shell, 'resource-signal {0} {1} -D "message"'.format( stack_id, resource_name)) self.assertEqual('Data should be a JSON dict', str(error)) def test_resource_signal_both_data(self): self._script_keystone_client() stack_id = 'teststack/1' resource_name = 'aResource' self.m.ReplayAll() error = self.assertRaises( exc.CommandError, self.shell, 'resource-signal {0} {1} -D "message" -f foo'.format( stack_id, resource_name)) self.assertEqual('Can only specify one of data and data-file', str(error)) def test_resource_signal_data_file(self): self._script_keystone_client() resp = fakes.FakeHTTPResponse( 200, 'OK', {}, '') stack_id = 'teststack/1' resource_name = 'aResource' http.HTTPClient.json_request( 'POST', '/stacks/%s/resources/%s/signal' % ( urlutils.quote(stack_id, ''), urlutils.quote(strutils.safe_encode( resource_name), '') ), data={'message': 'Content'}).AndReturn((resp, '')) self.m.ReplayAll() with tempfile.NamedTemporaryFile() as data_file: data_file.write('{"message":"Content"}') data_file.flush() text = self.shell( 'resource-signal {0} {1} -f {2}'.format( stack_id, resource_name, data_file.name)) self.assertEqual("", text) class ShellTestBuildInfo(ShellBase): def setUp(self): super(ShellTestBuildInfo, self).setUp() self._set_fake_env() def _set_fake_env(self): '''Patch os.environ to avoid required auth info.''' fake_env = { 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_NAME': 'tenant_name', 'OS_AUTH_URL': 'http://no.where', } self.set_fake_env(fake_env) def test_build_info(self): fakes.script_keystone_client() resp_dict = { 'build_info': { 'api': {'revision': 'api_revision'}, 'engine': {'revision': 'engine_revision'} } } resp_string = jsonutils.dumps(resp_dict) headers = {'content-type': 'application/json'} http_resp = fakes.FakeHTTPResponse(200, 'OK', headers, resp_string) response = (http_resp, resp_dict) http.HTTPClient.json_request('GET', '/build_info').AndReturn(response) self.m.ReplayAll() build_info_text = self.shell('build-info') required = [ 'api', 'engine', 'revision', 'api_revision', 'engine_revision', ] for r in required: self.assertRegexpMatches(build_info_text, r) class ShellTestToken(ShellTestUserPass): # Rerun all ShellTestUserPass test with token auth def setUp(self): self.token = 'a_token' super(ShellTestToken, self).setUp() def _set_fake_env(self): fake_env = { 'OS_AUTH_TOKEN': self.token, 'OS_TENANT_ID': 'tenant_id', 'OS_AUTH_URL': 'http://no.where', # Note we also set username/password, because create/update # pass them even if we have a token to support storing credentials # Hopefully at some point we can remove this and move to only # storing trust id's in heat-engine instead.. 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password' } self.set_fake_env(fake_env) def _script_keystone_client(self): fakes.script_keystone_client(token=self.token) class ShellTestStandaloneToken(ShellTestUserPass): # Rerun all ShellTestUserPass test in standalone mode, where we # specify --os-no-client-auth, a token and Heat endpoint def setUp(self): self.token = 'a_token' super(ShellTestStandaloneToken, self).setUp() def _set_fake_env(self): fake_env = { 'OS_AUTH_TOKEN': self.token, 'OS_NO_CLIENT_AUTH': 'True', 'HEAT_URL': 'http://no.where', # Note we also set username/password, because create/update # pass them even if we have a token to support storing credentials # Hopefully at some point we can remove this and move to only # storing trust id's in heat-engine instead.. 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password' } self.set_fake_env(fake_env) def _script_keystone_client(self): # The StanaloneMode shouldn't need any keystoneclient stubbing pass def test_bad_template_file(self): failed_msg = 'Error parsing template ' with tempfile.NamedTemporaryFile() as bad_json_file: bad_json_file.write(b"{foo:}") bad_json_file.flush() self.shell_error("stack-create ts -f %s" % bad_json_file.name, failed_msg) with tempfile.NamedTemporaryFile() as bad_json_file: bad_json_file.write(b'{"foo": None}') bad_json_file.flush() self.shell_error("stack-create ts -f %s" % bad_json_file.name, failed_msg) python-heatclient-0.2.8/heatclient/tests/test_template_format.py0000664000175400017540000000321112304731350026367 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # 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 mock import testscenarios import testtools import yaml from heatclient.common import template_format load_tests = testscenarios.load_tests_apply_scenarios class YamlParseExceptions(testtools.TestCase): scenarios = [ ('scanner', dict(raised_exception=yaml.scanner.ScannerError())), ('parser', dict(raised_exception=yaml.parser.ParserError())), ('reader', dict(raised_exception=yaml.reader.ReaderError('', '', '', '', ''))), ] def test_parse_to_value_exception(self): text = 'not important' with mock.patch.object(yaml, 'load') as yaml_loader: yaml_loader.side_effect = self.raised_exception self.assertRaises(ValueError, template_format.parse, text) def test_parse_no_version_format(self): yaml = '' self.assertRaises(ValueError, template_format.parse, yaml) yaml2 = '''Parameters: {} Mappings: {} Resources: {} Outputs: {} ''' self.assertRaises(ValueError, template_format.parse, yaml2) python-heatclient-0.2.8/heatclient/tests/test_environment_format.py0000664000175400017540000000477512304731347027146 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # 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 heatclient.common import environment_format import mock import testscenarios import testtools import yaml load_tests = testscenarios.load_tests_apply_scenarios class YamlEnvironmentTest(testtools.TestCase): def test_minimal_yaml(self): yaml1 = '' yaml2 = ''' parameters: {} resource_registry: {} ''' tpl1 = environment_format.parse(yaml1) environment_format.default_for_missing(tpl1) tpl2 = environment_format.parse(yaml2) self.assertEqual(tpl2, tpl1) def test_wrong_sections(self): env = ''' parameters: {} resource_regis: {} ''' self.assertRaises(ValueError, environment_format.parse, env) def test_bad_yaml(self): env = ''' parameters: } ''' self.assertRaises(ValueError, environment_format.parse, env) def test_parse_string_environment(self): env = 'just string' expect = 'The environment is not a valid YAML mapping data type.' msg = self.assertRaises(ValueError, environment_format.parse, env) self.assertIn(expect, msg) def test_parse_document(self): env = '["foo" , "bar"]' expect = 'The environment is not a valid YAML mapping data type.' msg = self.assertRaises(ValueError, environment_format.parse, env) self.assertIn(expect, msg) class YamlParseExceptions(testtools.TestCase): scenarios = [ ('scanner', dict(raised_exception=yaml.scanner.ScannerError())), ('parser', dict(raised_exception=yaml.parser.ParserError())), ('reader', dict(raised_exception=yaml.reader.ReaderError('', '', '', '', ''))), ] def test_parse_to_value_exception(self): text = 'not important' with mock.patch.object(yaml, 'load') as yaml_loader: yaml_loader.side_effect = self.raised_exception self.assertRaises(ValueError, environment_format.parse, text) python-heatclient-0.2.8/heatclient/tests/test_events.py0000664000175400017540000001044412304731347024524 0ustar jenkinsjenkins00000000000000# Copyright 2013 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. from heatclient.v1.events import EventManager from mock import MagicMock from mock import patch from mox3 import mox import testtools class EventManagerTest(testtools.TestCase): def setUp(self): super(EventManagerTest, self).setUp() self.m = mox.Mox() self.addCleanup(self.m.UnsetStubs) self.addCleanup(self.m.ResetAll) def test_list_event(self): stack_id = 'teststack', resource_name = 'testresource' manager = EventManager(None) self.m.StubOutWithMock(manager, '_resolve_stack_id') manager._resolve_stack_id(stack_id).AndReturn('teststack/abcd1234') self.m.ReplayAll() manager._list = MagicMock() manager.list(stack_id, resource_name) # Make sure url is correct. manager._list.assert_called_once_with('/stacks/teststack%2Fabcd1234/' 'resources/testresource/events', "events") def test_list_event_with_unicode_resource_name(self): stack_id = 'teststack', resource_name = u'\u5de5\u4f5c' manager = EventManager(None) self.m.StubOutWithMock(manager, '_resolve_stack_id') manager._resolve_stack_id(stack_id).AndReturn('teststack/abcd1234') self.m.ReplayAll() manager._list = MagicMock() manager.list(stack_id, resource_name) # Make sure url is correct. manager._list.assert_called_once_with('/stacks/teststack%2Fabcd1234/' 'resources/%E5%B7%A5%E4%BD%9C/' 'events', "events") def test_list_event_with_none_resource_name(self): stack_id = 'teststack', manager = EventManager(None) manager._list = MagicMock() manager.list(stack_id) # Make sure url is correct. manager._list.assert_called_once_with('/stacks/teststack/' 'events', "events") def test_get_event(self): fields = {'stack_id': 'teststack', 'resource_name': 'testresource', 'event_id': '1'} class FakeAPI(object): """Fake API and ensure request url is correct.""" def json_request(self, *args, **kwargs): expect = ('GET', '/stacks/teststack%2Fabcd1234/resources' '/testresource/events/1') assert args == expect return {}, {'event': []} manager = EventManager(FakeAPI()) with patch('heatclient.v1.events.Event'): self.m.StubOutWithMock(manager, '_resolve_stack_id') manager._resolve_stack_id('teststack').AndReturn( 'teststack/abcd1234') self.m.ReplayAll() manager.get(**fields) def test_get_event_with_unicode_resource_name(self): fields = {'stack_id': 'teststack', 'resource_name': u'\u5de5\u4f5c', 'event_id': '1'} class FakeAPI(object): """Fake API and ensure request url is correct.""" def json_request(self, *args, **kwargs): expect = ('GET', '/stacks/teststack%2Fabcd1234/resources' '/%E5%B7%A5%E4%BD%9C/events/1') assert args == expect return {}, {'event': []} manager = EventManager(FakeAPI()) with patch('heatclient.v1.events.Event'): self.m.StubOutWithMock(manager, '_resolve_stack_id') manager._resolve_stack_id('teststack').AndReturn( 'teststack/abcd1234') self.m.ReplayAll() manager.get(**fields) python-heatclient-0.2.8/heatclient/tests/test_common_http.py0000664000175400017540000005315612304731350025550 0ustar jenkinsjenkins00000000000000#-*- 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 logging import os import socket import requests import testtools from heatclient.common import http from heatclient import exc from heatclient.tests import fakes from mox3 import mox class HttpClientTest(testtools.TestCase): # Patch os.environ to avoid required auth info. def setUp(self): super(HttpClientTest, self).setUp() self.m = mox.Mox() self.m.StubOutWithMock(requests, 'request') self.addCleanup(self.m.UnsetStubs) self.addCleanup(self.m.ResetAll) def test_http_raw_request(self): headers = {'Content-Type': 'application/octet-stream', 'User-Agent': 'python-heatclient'} # Record a 200 mock_conn = http.requests.request('GET', 'http://example.com:8004', allow_redirects=False, headers=headers) mock_conn.AndReturn( fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/octet-stream'}, '')) # Replay, create client, assert self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004') resp = client.raw_request('GET', '') self.assertEqual(200, resp.status_code) self.assertEqual('', ''.join([x for x in resp.content])) self.m.VerifyAll() def test_token_or_credentials(self): # Record a 200 fake200 = fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/octet-stream'}, '') # no token or credentials mock_conn = http.requests.request( 'GET', 'http://example.com:8004', allow_redirects=False, headers={'Content-Type': 'application/octet-stream', 'User-Agent': 'python-heatclient'}) mock_conn.AndReturn(fake200) # credentials mock_conn = http.requests.request( 'GET', 'http://example.com:8004', allow_redirects=False, headers={'Content-Type': 'application/octet-stream', 'User-Agent': 'python-heatclient', 'X-Auth-Key': 'pass', 'X-Auth-User': 'user'}) mock_conn.AndReturn(fake200) # token suppresses credentials mock_conn = http.requests.request( 'GET', 'http://example.com:8004', allow_redirects=False, headers={'Content-Type': 'application/octet-stream', 'User-Agent': 'python-heatclient', 'X-Auth-Token': 'abcd1234'}) mock_conn.AndReturn(fake200) # Replay, create client, assert self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004') resp = client.raw_request('GET', '') self.assertEqual(200, resp.status_code) client.username = 'user' client.password = 'pass' resp = client.raw_request('GET', '') self.assertEqual(200, resp.status_code) client.auth_token = 'abcd1234' resp = client.raw_request('GET', '') self.assertEqual(200, resp.status_code) self.m.VerifyAll() def test_include_pass(self): # Record a 200 fake200 = fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/octet-stream'}, '') # no token or credentials mock_conn = http.requests.request( 'GET', 'http://example.com:8004', allow_redirects=False, headers={'Content-Type': 'application/octet-stream', 'User-Agent': 'python-heatclient'}) mock_conn.AndReturn(fake200) # credentials mock_conn = http.requests.request( 'GET', 'http://example.com:8004', allow_redirects=False, headers={'Content-Type': 'application/octet-stream', 'User-Agent': 'python-heatclient', 'X-Auth-Key': 'pass', 'X-Auth-User': 'user'}) mock_conn.AndReturn(fake200) # token suppresses credentials mock_conn = http.requests.request( 'GET', 'http://example.com:8004', allow_redirects=False, headers={'Content-Type': 'application/octet-stream', 'User-Agent': 'python-heatclient', 'X-Auth-Token': 'abcd1234', 'X-Auth-Key': 'pass', 'X-Auth-User': 'user'}) mock_conn.AndReturn(fake200) # Replay, create client, assert self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004') resp = client.raw_request('GET', '') self.assertEqual(200, resp.status_code) client.username = 'user' client.password = 'pass' client.include_pass = True resp = client.raw_request('GET', '') self.assertEqual(200, resp.status_code) client.auth_token = 'abcd1234' resp = client.raw_request('GET', '') self.assertEqual(200, resp.status_code) self.m.VerifyAll() def test_not_include_pass(self): # Record a 200 fake500 = fakes.FakeHTTPResponse( 500, 'ERROR', {'content-type': 'application/octet-stream'}, '(HTTP 401)') # no token or credentials mock_conn = http.requests.request( 'GET', 'http://example.com:8004', allow_redirects=False, headers={'Content-Type': 'application/octet-stream', 'User-Agent': 'python-heatclient'}) mock_conn.AndReturn(fake500) # Replay, create client, assert self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004') e = self.assertRaises(exc.HTTPUnauthorized, client.raw_request, 'GET', '') self.assertIn('include-password', str(e)) def test_region_name(self): # Record a 200 fake200 = fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/octet-stream'}, '') # Specify region name mock_conn = http.requests.request( 'GET', 'http://example.com:8004', allow_redirects=False, headers={'Content-Type': 'application/octet-stream', 'X-Region-Name': 'RegionOne', 'User-Agent': 'python-heatclient'}) mock_conn.AndReturn(fake200) # Replay, create client, assert self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004') client.region_name = 'RegionOne' resp = client.raw_request('GET', '') self.assertEqual(200, resp.status_code) self.m.VerifyAll() def test_http_json_request(self): # Record a 200 mock_conn = http.requests.request( 'GET', 'http://example.com:8004', allow_redirects=False, headers={'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-heatclient'}) mock_conn.AndReturn( fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, '{}')) # Replay, create client, assert self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004') resp, body = client.json_request('GET', '') self.assertEqual(200, resp.status_code) self.assertEqual({}, body) self.m.VerifyAll() def test_http_json_request_w_req_body(self): # Record a 200 mock_conn = http.requests.request( 'GET', 'http://example.com:8004', body='test-body', allow_redirects=False, headers={'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-heatclient'}) mock_conn.AndReturn( fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, '{}')) # Replay, create client, assert self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004') resp, body = client.json_request('GET', '', body='test-body') self.assertEqual(200, resp.status_code) self.assertEqual({}, body) self.m.VerifyAll() def test_http_json_request_non_json_resp_cont_type(self): # Record a 200 mock_conn = http.requests.request( 'GET', 'http://example.com:8004', body='test-body', allow_redirects=False, headers={'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-heatclient'}) mock_conn.AndReturn( fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'not/json'}, '{}')) # Replay, create client, assert self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004') resp, body = client.json_request('GET', '', body='test-body') self.assertEqual(200, resp.status_code) self.assertIsNone(body) self.m.VerifyAll() def test_http_json_request_invalid_json(self): # Record a 200 mock_conn = http.requests.request( 'GET', 'http://example.com:8004', allow_redirects=False, headers={'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-heatclient'}) mock_conn.AndReturn( fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, 'invalid-json')) # Replay, create client, assert self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004') resp, body = client.json_request('GET', '') self.assertEqual(200, resp.status_code) self.assertEqual('invalid-json', body) self.m.VerifyAll() def test_http_manual_redirect_delete(self): mock_conn = http.requests.request( 'DELETE', 'http://example.com:8004/foo', allow_redirects=False, headers={'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-heatclient'}) mock_conn.AndReturn( fakes.FakeHTTPResponse( 302, 'Found', {'location': 'http://example.com:8004/foo/bar'}, '')) mock_conn = http.requests.request( 'DELETE', 'http://example.com:8004/foo/bar', allow_redirects=False, headers={'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-heatclient'}) mock_conn.AndReturn( fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, '{}')) self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004/foo') resp, body = client.json_request('DELETE', '') self.assertEqual(200, resp.status_code) self.m.VerifyAll() def test_http_manual_redirect_post(self): mock_conn = http.requests.request( 'POST', 'http://example.com:8004/foo', allow_redirects=False, headers={'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-heatclient'}) mock_conn.AndReturn( fakes.FakeHTTPResponse( 302, 'Found', {'location': 'http://example.com:8004/foo/bar'}, '')) mock_conn = http.requests.request( 'POST', 'http://example.com:8004/foo/bar', allow_redirects=False, headers={'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-heatclient'}) mock_conn.AndReturn( fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, '{}')) self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004/foo') resp, body = client.json_request('POST', '') self.assertEqual(200, resp.status_code) self.m.VerifyAll() def test_http_manual_redirect_put(self): mock_conn = http.requests.request( 'PUT', 'http://example.com:8004/foo', allow_redirects=False, headers={'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-heatclient'}) mock_conn.AndReturn( fakes.FakeHTTPResponse( 302, 'Found', {'location': 'http://example.com:8004/foo/bar'}, '')) mock_conn = http.requests.request( 'PUT', 'http://example.com:8004/foo/bar', allow_redirects=False, headers={'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-heatclient'}) mock_conn.AndReturn( fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, '{}')) self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004/foo') resp, body = client.json_request('PUT', '') self.assertEqual(200, resp.status_code) self.m.VerifyAll() def test_http_manual_redirect_prohibited(self): mock_conn = http.requests.request( 'DELETE', 'http://example.com:8004/foo', allow_redirects=False, headers={'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-heatclient'}) mock_conn.AndReturn( fakes.FakeHTTPResponse( 302, 'Found', {'location': 'http://example.com:8004/'}, '')) self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004/foo') self.assertRaises(exc.InvalidEndpoint, client.json_request, 'DELETE', '') self.m.VerifyAll() def test_http_json_request_redirect(self): # Record the 302 mock_conn = http.requests.request( 'GET', 'http://example.com:8004', allow_redirects=False, headers={'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-heatclient'}) mock_conn.AndReturn( fakes.FakeHTTPResponse( 302, 'Found', {'location': 'http://example.com:8004'}, '')) # Record the following 200 mock_conn = http.requests.request( 'GET', 'http://example.com:8004', allow_redirects=False, headers={'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-heatclient'}) mock_conn.AndReturn( fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, '{}')) # Replay, create client, assert self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004') resp, body = client.json_request('GET', '') self.assertEqual(resp.status_code, 200) self.assertEqual(body, {}) self.m.VerifyAll() def test_http_404_json_request(self): # Record a 404 mock_conn = http.requests.request( 'GET', 'http://example.com:8004', allow_redirects=False, headers={'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-heatclient'}) mock_conn.AndReturn( fakes.FakeHTTPResponse( 404, 'OK', {'content-type': 'application/json'}, '{}')) # Replay, create client, assert self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004') e = self.assertRaises(exc.HTTPNotFound, client.json_request, 'GET', '') # Assert that the raised exception can be converted to string self.assertIsNotNone(str(e)) self.m.VerifyAll() def test_http_300_json_request(self): # Record a 300 mock_conn = http.requests.request( 'GET', 'http://example.com:8004', allow_redirects=False, headers={'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-heatclient'}) mock_conn.AndReturn( fakes.FakeHTTPResponse( 300, 'OK', {'content-type': 'application/json'}, '{}')) # Replay, create client, assert self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004') e = self.assertRaises( exc.HTTPMultipleChoices, client.json_request, 'GET', '') # Assert that the raised exception can be converted to string self.assertIsNotNone(str(e)) self.m.VerifyAll() def test_fake_json_request(self): headers = {'User-Agent': 'python-heatclient'} mock_conn = http.requests.request('GET', 'fake://example.com:8004/', allow_redirects=False, headers=headers) mock_conn.AndRaise(socket.gaierror) self.m.ReplayAll() client = http.HTTPClient('fake://example.com:8004') self.assertRaises(exc.InvalidEndpoint, client._http_request, "/", "GET") self.m.VerifyAll() def test_debug_curl_command(self): self.m.StubOutWithMock(logging.Logger, 'debug') ssl_connection_params = {'ca_file': 'TEST_CA', 'cert_file': 'TEST_CERT', 'key_file': 'TEST_KEY', 'insecure': 'TEST_NSA'} headers = {'key': 'value'} mock_logging_debug = logging.Logger.debug( "curl -i -X GET -H 'key: value' --key TEST_KEY " "--cert TEST_CERT --cacert TEST_CA " "-k http://foo/bar" ) mock_logging_debug.AndReturn(None) self.m.ReplayAll() client = http.HTTPClient('http://foo') client.ssl_connection_params = ssl_connection_params client.log_curl_request('GET', '/bar', {'headers': headers}) self.m.VerifyAll() def test_http_request_socket_error(self): headers = {'User-Agent': 'python-heatclient'} mock_conn = http.requests.request('GET', 'http://example.com:8004/', allow_redirects=False, headers=headers) mock_conn.AndRaise(socket.error) self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004') self.assertRaises(exc.CommunicationError, client._http_request, "/", "GET") self.m.VerifyAll() def test_http_request_socket_timeout(self): headers = {'User-Agent': 'python-heatclient'} mock_conn = http.requests.request('GET', 'http://example.com:8004/', allow_redirects=False, headers=headers) mock_conn.AndRaise(socket.timeout) self.m.ReplayAll() client = http.HTTPClient('http://example.com:8004') self.assertRaises(exc.CommunicationError, client._http_request, "/", "GET") self.m.VerifyAll() def test_get_system_ca_file(self): chosen = '/etc/ssl/certs/ca-certificates.crt' self.m.StubOutWithMock(os.path, 'exists') os.path.exists(chosen).AndReturn(chosen) self.m.ReplayAll() ca = http.get_system_ca_file() self.assertEqual(ca, chosen) self.m.VerifyAll() def test_insecure_verify_cert_None(self): client = http.HTTPClient('https://foo', insecure=True) self.assertFalse(client.verify_cert) def test_passed_cert_to_verify_cert(self): client = http.HTTPClient('https://foo', ca_file="NOWHERE") self.assertEqual(client.verify_cert, "NOWHERE") self.m.StubOutWithMock(http, 'get_system_ca_file') http.get_system_ca_file().AndReturn("SOMEWHERE") self.m.ReplayAll() client = http.HTTPClient('https://foo') self.assertEqual(client.verify_cert, "SOMEWHERE") def test_curl_log_i18n_headers(self): self.m.StubOutWithMock(logging.Logger, 'debug') kwargs = {'headers': {'Key': 'foo\xe3\x8a\x8e'}} mock_logging_debug = logging.Logger.debug( u"curl -i -X GET -H 'Key: foo㊎' http://somewhere" ) mock_logging_debug.AndReturn(None) self.m.ReplayAll() client = http.HTTPClient('http://somewhere') client.log_curl_request("GET", '', kwargs=kwargs) self.m.VerifyAll() python-heatclient-0.2.8/heatclient/tests/fakes.py0000664000175400017540000001016212304731347023247 0ustar jenkinsjenkins00000000000000# 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 heatclient.common import http from heatclient import exc from heatclient.openstack.common import jsonutils from keystoneclient.v2_0 import client as ksclient def script_keystone_client(token=None): if token: ksclient.Client(auth_url='http://no.where', insecure=False, tenant_id='tenant_id', token=token).AndReturn(FakeKeystone(token)) else: ksclient.Client(auth_url='http://no.where', insecure=False, password='password', tenant_name='tenant_name', username='username').AndReturn(FakeKeystone( 'abcd1234')) def script_heat_list(url=None): if url is None: url = '/stacks?' resp_dict = {"stacks": [ { "id": "1", "stack_name": "teststack", "stack_status": 'CREATE_COMPLETE', "creation_time": "2012-10-25T01:58:47Z" }, { "id": "2", "stack_name": "teststack2", "stack_status": 'IN_PROGRESS', "creation_time": "2012-10-25T01:58:47Z" }] } resp = FakeHTTPResponse(200, 'success, you', {'content-type': 'application/json'}, jsonutils.dumps(resp_dict)) http.HTTPClient.json_request('GET', url).AndReturn((resp, resp_dict)) def script_heat_normal_error(): resp_dict = { "explanation": "The resource could not be found.", "code": 404, "error": { "message": "The Stack (bad) could not be found.", "type": "StackNotFound", "traceback": "", }, "title": "Not Found" } resp = FakeHTTPResponse(400, 'The resource could not be found', {'content-type': 'application/json'}, jsonutils.dumps(resp_dict)) http.HTTPClient.json_request('GET', '/stacks/bad').AndRaise( exc.from_response(resp)) def script_heat_error(resp_string): resp = FakeHTTPResponse(400, 'The resource could not be found', {'content-type': 'application/json'}, resp_string) http.HTTPClient.json_request('GET', '/stacks/bad').AndRaise( exc.from_response(resp)) def fake_headers(): return {'X-Auth-Token': 'abcd1234', 'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-heatclient'} class FakeServiceCatalog(): def url_for(self, endpoint_type, service_type): return 'http://192.168.1.5:8004/v1/f14b41234' class FakeKeystone(): service_catalog = FakeServiceCatalog() def __init__(self, auth_token): self.auth_token = auth_token class FakeRaw(): version = 110 class FakeHTTPResponse(): version = 1.1 def __init__(self, status_code, reason, headers, content): self.headers = headers self.content = content self.status_code = status_code self.reason = reason self.raw = FakeRaw() def getheader(self, name, default=None): return self.headers.get(name, default) def getheaders(self): return self.headers.items() def read(self, amt=None): b = self.content self.content = None return b def iter_content(self, chunksize): return self.content def json(self): return jsonutils.loads(self.content) python-heatclient-0.2.8/heatclient/tests/test_software_deployments.py0000664000175400017540000001454512304731347027503 0ustar jenkinsjenkins00000000000000# 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 mock import testtools from heatclient.v1.software_deployments import SoftwareDeployment from heatclient.v1.software_deployments import SoftwareDeploymentManager class SoftwareDeploymentTest(testtools.TestCase): def setUp(self): super(SoftwareDeploymentTest, self).setUp() deployment_id = 'bca6871d-86c0-4aff-b792-58a1f6947b57' self.deployment = SoftwareDeployment( mock.MagicMock(), info={'id': deployment_id}) self.deployment_id = deployment_id def test_delete(self): self.deployment.manager.delete.return_value = None self.assertIsNone(self.deployment.delete()) kwargs = self.deployment.manager.delete.call_args[1] self.assertEqual(self.deployment_id, kwargs['deployment_id']) def test_update(self): self.assertEqual( "" % self.deployment_id, str(self.deployment)) self.deployment.manager.update.return_value = None config_id = 'd00ba4aa-db33-42e1-92f4-2a6469260107' self.assertIsNone(self.deployment.update(config_id=config_id)) kwargs = self.deployment.manager.update.call_args[1] self.assertEqual(self.deployment_id, kwargs['deployment_id']) self.assertEqual(config_id, kwargs['config_id']) class SoftwareDeploymentManagerTest(testtools.TestCase): def setUp(self): super(SoftwareDeploymentManagerTest, self).setUp() self.manager = SoftwareDeploymentManager(mock.MagicMock()) def test_list(self): server_id = 'fc01f89f-e151-4dc5-9c28-543c0d20ed6a' self.manager.client.json_request.return_value = ( {}, {'software_deployments': []}) result = self.manager.list(server_id=server_id) self.assertEqual([], result) call_args = self.manager.client.get.call_args self.assertEqual( ('/software_deployments?server_id=%s' % server_id,), *call_args) def test_metadata(self): server_id = 'fc01f89f-e151-4dc5-9c28-543c0d20ed6a' metadata = { 'group1': [{'foo': 'bar'}], 'group2': [{'foo': 'bar'}, {'bar': 'baz'}], } self.manager.client.json_request.return_value = ( {}, {'metadata': metadata}) result = self.manager.metadata(server_id=server_id) self.assertEqual(metadata, result) call_args = self.manager.client.json_request.call_args self.assertEqual( '/software_deployments/metadata/%s' % server_id, call_args[0][1]) def test_get(self): deployment_id = 'bca6871d-86c0-4aff-b792-58a1f6947b57' config_id = 'd00ba4aa-db33-42e1-92f4-2a6469260107' server_id = 'fb322564-7927-473d-8aad-68ae7fbf2abf' data = { 'id': deployment_id, 'server_id': server_id, 'input_values': {}, 'output_values': {}, 'action': 'INIT', 'status': 'COMPLETE', 'status_reason': None, 'signal_id': None, 'config_id': config_id, 'config': '#!/bin/bash', 'name': 'config_mysql', 'group': 'Heat::Shell', 'inputs': [], 'outputs': [], 'options': []} self.manager.client.json_request.return_value = ( {}, {'software_deployment': data}) result = self.manager.get(deployment_id=deployment_id) self.assertEqual(SoftwareDeployment(self.manager, data), result) call_args = self.manager.client.json_request.call_args self.assertEqual( ('GET', '/software_deployments/%s' % deployment_id), *call_args) def test_create(self): deployment_id = 'bca6871d-86c0-4aff-b792-58a1f6947b57' config_id = 'd00ba4aa-db33-42e1-92f4-2a6469260107' server_id = 'fb322564-7927-473d-8aad-68ae7fbf2abf' body = { 'server_id': server_id, 'input_values': {}, 'action': 'INIT', 'status': 'COMPLETE', 'status_reason': None, 'signal_id': None, 'config_id': config_id} data = body.copy() data['id'] = deployment_id self.manager.client.json_request.return_value = ( {}, {'software_deployment': data}) result = self.manager.create(**body) self.assertEqual(SoftwareDeployment(self.manager, data), result) args, kwargs = self.manager.client.json_request.call_args self.assertEqual('POST', args[0]) self.assertEqual('/software_deployments', args[1]) self.assertEqual({'data': body}, kwargs) def test_delete(self): deployment_id = 'bca6871d-86c0-4aff-b792-58a1f6947b57' self.manager.delete(deployment_id) call_args = self.manager.client.delete.call_args self.assertEqual( ('/software_deployments/%s' % deployment_id,), *call_args) def test_update(self): deployment_id = 'bca6871d-86c0-4aff-b792-58a1f6947b57' config_id = 'd00ba4aa-db33-42e1-92f4-2a6469260107' server_id = 'fb322564-7927-473d-8aad-68ae7fbf2abf' body = { 'server_id': server_id, 'input_values': {}, 'action': 'DEPLOYED', 'status': 'COMPLETE', 'status_reason': None, 'signal_id': None, 'config_id': config_id} data = body.copy() data['id'] = deployment_id self.manager.client.json_request.return_value = ( {}, {'software_deployment': data}) result = self.manager.update(deployment_id, **body) self.assertEqual(SoftwareDeployment(self.manager, data), result) args, kwargs = self.manager.client.json_request.call_args self.assertEqual('PUT', args[0]) self.assertEqual('/software_deployments/%s' % deployment_id, args[1]) self.assertEqual({'data': body}, kwargs) python-heatclient-0.2.8/heatclient/tests/test_software_configs.py0000664000175400017540000000670712304731347026571 0ustar jenkinsjenkins00000000000000# 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 mock import testtools from heatclient.v1.software_configs import SoftwareConfig from heatclient.v1.software_configs import SoftwareConfigManager class SoftwareConfigTest(testtools.TestCase): def setUp(self): super(SoftwareConfigTest, self).setUp() config_id = 'bca6871d-86c0-4aff-b792-58a1f6947b57' self.config = SoftwareConfig(mock.MagicMock(), info={'id': config_id}) self.config_id = config_id def test_delete(self): self.config.manager.delete.return_value = None self.assertIsNone(self.config.delete()) kwargs = self.config.manager.delete.call_args[1] self.assertEqual(self.config_id, kwargs['config_id']) def test_data(self): self.assertEqual( "" % self.config_id, str(self.config)) self.config.manager.data.return_value = None self.config.data(name='config_mysql') kwargs = self.config.manager.data.call_args[1] self.assertEqual('config_mysql', kwargs['name']) class SoftwareConfigManagerTest(testtools.TestCase): def setUp(self): super(SoftwareConfigManagerTest, self).setUp() self.manager = SoftwareConfigManager(mock.MagicMock()) def test_get(self): config_id = 'bca6871d-86c0-4aff-b792-58a1f6947b57' data = { 'id': config_id, 'name': 'config_mysql', 'group': 'Heat::Shell', 'config': '#!/bin/bash', 'inputs': [], 'ouputs': [], 'options': []} self.manager.client.json_request.return_value = ( {}, {'software_config': data}) result = self.manager.get(config_id=config_id) self.assertEqual(SoftwareConfig(self.manager, data), result) call_args = self.manager.client.json_request.call_args self.assertEqual( ('GET', '/software_configs/%s' % config_id), *call_args) def test_create(self): config_id = 'bca6871d-86c0-4aff-b792-58a1f6947b57' body = { 'name': 'config_mysql', 'group': 'Heat::Shell', 'config': '#!/bin/bash', 'inputs': [], 'ouputs': [], 'options': []} data = body.copy() data['id'] = config_id self.manager.client.json_request.return_value = ( {}, {'software_config': data}) result = self.manager.create(**body) self.assertEqual(SoftwareConfig(self.manager, data), result) args, kargs = self.manager.client.json_request.call_args self.assertEqual('POST', args[0]) self.assertEqual('/software_configs', args[1]) self.assertEqual({'data': body}, kargs) def test_delete(self): config_id = 'bca6871d-86c0-4aff-b792-58a1f6947b57' self.manager.delete(config_id) call_args = self.manager.client.delete.call_args self.assertEqual( ('/software_configs/%s' % config_id,), *call_args) python-heatclient-0.2.8/heatclient/tests/test_stacks.py0000664000175400017540000002073212304731347024511 0ustar jenkinsjenkins00000000000000# 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 heatclient.v1.stacks import Stack from heatclient.v1.stacks import StackManager from mock import MagicMock import testscenarios from testscenarios.scenarios import multiply_scenarios import testtools load_tests = testscenarios.load_tests_apply_scenarios def mock_stack(manager, stack_name, stack_id): return Stack(manager, { "id": stack_id, "stack_name": stack_name, "links": [{ "href": "http://192.0.2.1:8004/v1/1234/stacks/%s/%s" % ( stack_name, stack_id), "rel": "self"}], "description": "No description", "stack_status_reason": "Stack create completed successfully", "creation_time": "2013-08-04T20:57:55Z", "updated_time": "2013-08-04T20:57:55Z", "stack_status": "CREATE_COMPLETE" }) class StackStatusActionTest(testtools.TestCase): scenarios = multiply_scenarios([ ('CREATE', dict(action='CREATE')), ('DELETE', dict(action='DELETE')), ('UPDATE', dict(action='UPDATE')), ('ROLLBACK', dict(action='ROLLBACK')), ('SUSPEND', dict(action='SUSPEND')), ('RESUME', dict(action='RESUME')) ], [ ('IN_PROGRESS', dict(status='IN_PROGRESS')), ('FAILED', dict(status='FAILED')), ('COMPLETE', dict(status='COMPLETE')) ]) def test_status_action(self): stack_status = '%s_%s' % (self.action, self.status) stack = mock_stack(None, 'stack_1', 'abcd1234') stack.stack_status = stack_status self.assertEqual(self.action, stack.action) self.assertEqual(self.status, stack.status) class StackIdentifierTest(testtools.TestCase): def test_stack_identifier(self): stack = mock_stack(None, 'the_stack', 'abcd1234') self.assertEqual('the_stack/abcd1234', stack.identifier) class StackOperationsTest(testtools.TestCase): def test_delete_stack(self): manager = MagicMock() stack = mock_stack(manager, 'the_stack', 'abcd1234') stack.delete() manager.delete.assert_called_once_with('the_stack/abcd1234') def test_abandon_stack(self): manager = MagicMock() stack = mock_stack(manager, 'the_stack', 'abcd1234') stack.abandon() manager.abandon.assert_called_once_with('the_stack/abcd1234') def test_get_stack(self): manager = MagicMock() stack = mock_stack(manager, 'the_stack', 'abcd1234') stack.get() manager.get.assert_called_once_with('the_stack/abcd1234') def test_update_stack(self): manager = MagicMock() stack = mock_stack(manager, 'the_stack', 'abcd1234') stack.update() manager.update.assert_called_once_with('the_stack/abcd1234') def test_create_stack(self): manager = MagicMock() stack = mock_stack(manager, 'the_stack', 'abcd1234') stack = stack.create() manager.create.assert_called_once_with('the_stack/abcd1234') class StackManagerNoPaginationTest(testtools.TestCase): scenarios = [ ('total_0', dict(total=0)), ('total_1', dict(total=1)), ('total_9', dict(total=9)), ('total_10', dict(total=10)), ('total_11', dict(total=11)), ('total_19', dict(total=19)), ('total_20', dict(total=20)), ('total_21', dict(total=21)), ('total_49', dict(total=49)), ('total_50', dict(total=50)), ('total_51', dict(total=51)), ('total_95', dict(total=95)), ] # absolute limit for results returned limit = 50 def mock_manager(self): manager = StackManager(None) manager._list = MagicMock() def mock_list(*args, **kwargs): def results(): for i in range(0, self.total): stack_name = 'stack_%s' % (i + 1) stack_id = 'abcd1234-%s' % (i + 1) yield mock_stack(manager, stack_name, stack_id) return list(results()) manager._list.side_effect = mock_list return manager def test_stack_list_no_pagination(self): manager = self.mock_manager() results = list(manager.list()) manager._list.assert_called_once_with( '/stacks?', 'stacks') # paginate is not specified, so the total # results is always returned self.assertEqual(self.total, len(results)) if self.total > 0: self.assertEqual('stack_1', results[0].stack_name) self.assertEqual('stack_%s' % self.total, results[-1].stack_name) class StackManagerPaginationTest(testtools.TestCase): scenarios = [ ('0_offset_0', dict( offset=0, total=0, results=((0, 0),) )), ('1_offset_0', dict( offset=0, total=1, results=((0, 1),) )), ('9_offset_0', dict( offset=0, total=9, results=((0, 9),) )), ('10_offset_0', dict( offset=0, total=10, results=((0, 10), (10, 10)) )), ('11_offset_0', dict( offset=0, total=11, results=((0, 10), (10, 11)) )), ('11_offset_10', dict( offset=10, total=11, results=((10, 11),) )), ('19_offset_10', dict( offset=10, total=19, results=((10, 19),) )), ('20_offset_10', dict( offset=10, total=20, results=((10, 20), (20, 20)) )), ('21_offset_10', dict( offset=10, total=21, results=((10, 20), (20, 21)) )), ('21_offset_0', dict( offset=0, total=21, results=((0, 10), (10, 20), (20, 21)) )), ('21_offset_20', dict( offset=20, total=21, results=((20, 21),) )), ('95_offset_90', dict( offset=90, total=95, results=((90, 95),) )), ] # absolute limit for results returned limit = 50 def mock_manager(self): manager = StackManager(None) manager._list = MagicMock() def mock_list(arg_url, arg_response_key): try: result = self.results[self.result_index] except IndexError: return [] self.result_index = self.result_index + 1 limit_string = 'limit=%s' % self.limit self.assertIn(limit_string, arg_url) offset = result[0] if offset > 0: offset_string = 'marker=abcd1234-%s' % offset self.assertIn(offset_string, arg_url) def results(): for i in range(*result): self.limit -= 1 stack_name = 'stack_%s' % (i + 1) stack_id = 'abcd1234-%s' % (i + 1) yield mock_stack(manager, stack_name, stack_id) return list(results()) manager._list.side_effect = mock_list return manager def test_stack_list_pagination(self): manager = self.mock_manager() list_params = {'limit': self.limit} if self.offset > 0: marker = 'abcd1234-%s' % self.offset list_params['marker'] = marker self.result_index = 0 results = list(manager.list(**list_params)) # assert that the list method has been called enough times self.assertEqual(len(self.results), self.result_index) last_result = min(self.limit, self.total - self.offset) # one or more list calls have been recomposed into a single list self.assertEqual(last_result, len(results)) if last_result > 0: self.assertEqual('stack_%s' % (self.offset + 1), results[0].stack_name) self.assertEqual('stack_%s' % (self.offset + last_result), results[-1].stack_name) python-heatclient-0.2.8/heatclient/tests/test_utils.py0000664000175400017540000001267312304731347024366 0ustar jenkinsjenkins00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # 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 heatclient.common import utils from heatclient import exc import testtools class shellTest(testtools.TestCase): def test_format_parameter_none(self): self.assertEqual({}, utils.format_parameters(None)) def test_format_parameters(self): p = utils.format_parameters([ 'InstanceType=m1.large;DBUsername=wp;' 'DBPassword=verybadpassword;KeyName=heat_key;' 'LinuxDistribution=F17']) self.assertEqual({'InstanceType': 'm1.large', 'DBUsername': 'wp', 'DBPassword': 'verybadpassword', 'KeyName': 'heat_key', 'LinuxDistribution': 'F17' }, p) def test_format_parameters_split(self): p = utils.format_parameters([ 'KeyName=heat_key;' 'DnsSecKey=hsgx1m31PbamNF4WEcHlwjIlCGgifOdoB58/wwC7a4oAONQ/fDV5ct' 'qrYBoLlKHhTfkyQEw9iVScKYZbbMtMNg==;' 'UpstreamDNS=8.8.8.8']) self.assertEqual({'KeyName': 'heat_key', 'DnsSecKey': 'hsgx1m31PbamNF4WEcHlwjIlCGgifOdoB58/ww' 'C7a4oAONQ/fDV5ctqrYBoLlKHhTfkyQEw9iVScKYZbbMtMNg==', 'UpstreamDNS': '8.8.8.8'}, p) def test_format_parameters_multiple(self): p = utils.format_parameters([ 'KeyName=heat_key', 'DnsSecKey=hsgx1m31PbamNF4WEcHlwjIlCGgifOdoB58/wwC7a4oAONQ/fDV5ct' 'qrYBoLlKHhTfkyQEw9iVScKYZbbMtMNg==', 'UpstreamDNS=8.8.8.8']) self.assertEqual({'KeyName': 'heat_key', 'DnsSecKey': 'hsgx1m31PbamNF4WEcHlwjIlCGgifOdoB58/ww' 'C7a4oAONQ/fDV5ctqrYBoLlKHhTfkyQEw9iVScKYZbbMtMNg==', 'UpstreamDNS': '8.8.8.8'}, p) def test_format_parameters_multiple_semicolon_values(self): p = utils.format_parameters([ 'KeyName=heat_key', 'DnsSecKey=hsgx1m31;PbaNF4WEcHlwj;IlCGgfOdoB;58/ww7a4oAO;NQ/fD==', 'UpstreamDNS=8.8.8.8']) self.assertEqual({'KeyName': 'heat_key', 'DnsSecKey': 'hsgx1m31;PbaNF4WEcHlwj;IlCGgfOdoB;58/' 'ww7a4oAO;NQ/fD==', 'UpstreamDNS': '8.8.8.8'}, p) def test_format_parameters_multiple_values_per_pamaters(self): p = utils.format_parameters([ 'status=COMPLETE', 'status=FAILED']) self.assertIn('status', p) self.assertIn('COMPLETE', p['status']) self.assertIn('FAILED', p['status']) def test_format_parameter_bad_parameter(self): params = ['KeyName=heat_key;UpstreamDNS8.8.8.8'] ex = self.assertRaises(exc.CommandError, utils.format_parameters, params) self.assertEqual('Malformed parameter(UpstreamDNS8.8.8.8). ' 'Use the key=value format.', str(ex)) def test_format_multiple_bad_parameter(self): params = ['KeyName=heat_key', 'UpstreamDNS8.8.8.8'] ex = self.assertRaises(exc.CommandError, utils.format_parameters, params) self.assertEqual('Malformed parameter(UpstreamDNS8.8.8.8). ' 'Use the key=value format.', str(ex)) def test_link_formatter(self): self.assertEqual('', utils.link_formatter(None)) self.assertEqual('', utils.link_formatter([])) self.assertEqual( 'http://foo.example.com\nhttp://bar.example.com', utils.link_formatter([ {'href': 'http://foo.example.com'}, {'href': 'http://bar.example.com'}])) self.assertEqual( '\n', utils.link_formatter([ {'hrf': 'http://foo.example.com'}, {}])) def test_json_formatter(self): self.assertEqual('null', utils.json_formatter(None)) self.assertEqual('{}', utils.json_formatter({})) self.assertEqual('{\n "foo": "bar"\n}', utils.json_formatter({"foo": "bar"})) def test_text_wrap_formatter(self): self.assertEqual('', utils.text_wrap_formatter(None)) self.assertEqual('', utils.text_wrap_formatter('')) self.assertEqual('one two three', utils.text_wrap_formatter('one two three')) self.assertEqual( 'one two three four five six seven eight nine ten eleven\ntwelve', utils.text_wrap_formatter( ('one two three four five six seven ' 'eight nine ten eleven twelve'))) def test_newline_list_formatter(self): self.assertEqual('', utils.newline_list_formatter(None)) self.assertEqual('', utils.newline_list_formatter([])) self.assertEqual('one\ntwo', utils.newline_list_formatter(['one', 'two'])) python-heatclient-0.2.8/heatclient/tests/var/0000775000175400017540000000000012304731421022365 5ustar jenkinsjenkins00000000000000python-heatclient-0.2.8/heatclient/tests/var/adopt_stack_data.json0000664000175400017540000000014212304731347026551 0ustar jenkinsjenkins00000000000000{ "action": "CREATE", "status": "COMPLETE", "name": "teststack", "resources": {} }python-heatclient-0.2.8/heatclient/tests/var/minimal.template0000664000175400017540000000016412304731347025560 0ustar jenkinsjenkins00000000000000{ "AWSTemplateFormatVersion" : "2010-09-09", "Parameters" : { }, "Resources" : { }, "Outputs" : { } } python-heatclient-0.2.8/heatclient/tests/test_resource_types.py0000664000175400017540000000313512304731347026272 0ustar jenkinsjenkins00000000000000# # 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 mock import testtools from heatclient.v1.resource_types import ResourceTypeManager class ResourceTypeManagerTest(testtools.TestCase): def test_list_types(self): manager = ResourceTypeManager(None) manager._list = mock.MagicMock() manager.list() manager._list.assert_called_once_with('/resource_types', 'resource_types') def test_get(self): resource_type = u'OS::Nova::KeyPair' class FakeAPI(object): """Fake API and ensure request url is correct.""" def __init__(self, *args, **kwargs): self.requests = [] def json_request(self, *args, **kwargs): self.requests.append(args) return {}, {'attributes': [], 'properties': []} test_api = FakeAPI() manager = ResourceTypeManager(test_api) manager.get(resource_type) expect = ('GET', '/resource_types/OS%3A%3ANova%3A%3AKeyPair') self.assertIn(expect, test_api.requests) python-heatclient-0.2.8/heatclient/tests/test_build_info.py0000664000175400017540000000243412304731347025332 0ustar jenkinsjenkins00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # 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 mock import testtools from heatclient.v1.build_info import BuildInfoManager class BuildInfoManagerTest(testtools.TestCase): def setUp(self): super(BuildInfoManagerTest, self).setUp() self.client = mock.Mock() self.client.json_request.return_value = ('resp', 'body') self.manager = BuildInfoManager(self.client) def test_build_info_makes_a_call_to_the_api(self): self.manager.build_info() self.client.json_request.assert_called_once_with('GET', '/build_info') def test_build_info_returns_the_response_body(self): response = self.manager.build_info() self.assertEqual('body', response) python-heatclient-0.2.8/heatclient/tests/test_template_utils.py0000664000175400017540000004305212304731350026246 0ustar jenkinsjenkins00000000000000# 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 mox3 import mox import os import six import tempfile import testtools from testtools.matchers import MatchesRegex import yaml from heatclient.common import template_utils from heatclient import exc from heatclient.openstack.common.py3kcompat import urlutils class ShellEnvironmentTest(testtools.TestCase): def setUp(self): super(ShellEnvironmentTest, self).setUp() self.m = mox.Mox() self.addCleanup(self.m.VerifyAll) self.addCleanup(self.m.UnsetStubs) def collect_links(self, env, content, url, env_base_url=''): jenv = yaml.safe_load(env) files = {} if url: self.m.StubOutWithMock(urlutils, 'urlopen') urlutils.urlopen(url).AndReturn(six.StringIO(content)) self.m.ReplayAll() template_utils.resolve_environment_urls( jenv.get('resource_registry'), files, env_base_url) if url: self.assertEqual(files[url], content) def test_process_environment_file(self): self.m.StubOutWithMock(urlutils, 'urlopen') env_file = '/home/my/dir/env.yaml' env = ''' resource_registry: "OS::Thingy": "file:///home/b/a.yaml" ''' tmpl = '{"foo": "bar"}' urlutils.urlopen('file://%s' % env_file).AndReturn( six.StringIO(env)) urlutils.urlopen('file:///home/b/a.yaml').AndReturn( six.StringIO(tmpl)) self.m.ReplayAll() files, env_dict = template_utils.process_environment_and_files( env_file) self.assertEqual( {'resource_registry': { 'OS::Thingy': 'file:///home/b/a.yaml'}}, env_dict) self.assertEqual('{"foo": "bar"}', files['file:///home/b/a.yaml']) def test_process_environment_relative_file(self): self.m.StubOutWithMock(urlutils, 'urlopen') env_file = '/home/my/dir/env.yaml' env_url = 'file:///home/my/dir/env.yaml' env = ''' resource_registry: "OS::Thingy": a.yaml ''' tmpl = '{"foo": "bar"}' urlutils.urlopen(env_url).AndReturn( six.StringIO(env)) urlutils.urlopen('file:///home/my/dir/a.yaml').AndReturn( six.StringIO(tmpl)) self.m.ReplayAll() self.assertEqual( env_url, template_utils.normalise_file_path_to_url(env_file)) self.assertEqual( 'file:///home/my/dir', template_utils.base_url_for_url(env_url)) files, env_dict = template_utils.process_environment_and_files( env_file) self.assertEqual( {'resource_registry': { 'OS::Thingy': 'file:///home/my/dir/a.yaml'}}, env_dict) self.assertEqual( '{"foo": "bar"}', files['file:///home/my/dir/a.yaml']) def test_process_environment_relative_file_up(self): self.m.StubOutWithMock(urlutils, 'urlopen') env_file = '/home/my/dir/env.yaml' env_url = 'file:///home/my/dir/env.yaml' env = ''' resource_registry: "OS::Thingy": ../bar/a.yaml ''' tmpl = '{"foo": "bar"}' urlutils.urlopen(env_url).AndReturn( six.StringIO(env)) urlutils.urlopen('file:///home/my/bar/a.yaml').AndReturn( six.StringIO(tmpl)) self.m.ReplayAll() env_url = 'file://%s' % env_file self.assertEqual( env_url, template_utils.normalise_file_path_to_url(env_file)) self.assertEqual( 'file:///home/my/dir', template_utils.base_url_for_url(env_url)) files, env_dict = template_utils.process_environment_and_files( env_file) self.assertEqual( {'resource_registry': { 'OS::Thingy': 'file:///home/my/bar/a.yaml'}}, env_dict) self.assertEqual( '{"foo": "bar"}', files['file:///home/my/bar/a.yaml']) def test_process_environment_url(self): env = ''' resource_registry: "OS::Thingy": "a.yaml" ''' url = 'http://no.where/some/path/to/file.yaml' tmpl_url = 'http://no.where/some/path/to/a.yaml' tmpl = '{"foo": "bar"}' self.m.StubOutWithMock(urlutils, 'urlopen') urlutils.urlopen(url).AndReturn(six.StringIO(env)) urlutils.urlopen(tmpl_url).AndReturn(six.StringIO(tmpl)) self.m.ReplayAll() files, env_dict = template_utils.process_environment_and_files( url) self.assertEqual({'resource_registry': {'OS::Thingy': tmpl_url}}, env_dict) self.assertEqual(tmpl, files[tmpl_url]) def test_process_environment_empty_file(self): self.m.StubOutWithMock(urlutils, 'urlopen') env_file = '/home/my/dir/env.yaml' env = '' urlutils.urlopen('file://%s' % env_file).AndReturn(six.StringIO(env)) self.m.ReplayAll() files, env_dict = template_utils.process_environment_and_files( env_file) self.assertEqual({}, env_dict) self.assertEqual({}, files) def test_no_process_environment_and_files(self): files, env = template_utils.process_environment_and_files() self.assertEqual({}, env) self.assertEqual({}, files) def test_global_files(self): a = "A's contents." url = 'file:///home/b/a.yaml' env = ''' resource_registry: "OS::Thingy": "%s" ''' % url self.collect_links(env, a, url) def test_nested_files(self): a = "A's contents." url = 'file:///home/b/a.yaml' env = ''' resource_registry: resources: freddy: "OS::Thingy": "%s" ''' % url self.collect_links(env, a, url) def test_http_url(self): a = "A's contents." url = 'http://no.where/container/a.yaml' env = ''' resource_registry: "OS::Thingy": "%s" ''' % url self.collect_links(env, a, url) def test_with_base_url(self): a = "A's contents." url = 'ftp://no.where/container/a.yaml' env = ''' resource_registry: base_url: "ftp://no.where/container/" resources: server_for_me: "OS::Thingy": a.yaml ''' self.collect_links(env, a, url) def test_with_built_in_provider(self): a = "A's contents." env = ''' resource_registry: resources: server_for_me: "OS::Thingy": OS::Compute::Server ''' self.collect_links(env, a, None) def test_with_env_file_base_url_file(self): a = "A's contents." url = 'file:///tmp/foo/a.yaml' env = ''' resource_registry: resources: server_for_me: "OS::Thingy": a.yaml ''' env_base_url = 'file:///tmp/foo' self.collect_links(env, a, url, env_base_url) def test_with_env_file_base_url_http(self): a = "A's contents." url = 'http://no.where/path/to/a.yaml' env = ''' resource_registry: resources: server_for_me: "OS::Thingy": to/a.yaml ''' env_base_url = 'http://no.where/path' self.collect_links(env, a, url, env_base_url) def test_unsupported_protocol(self): env = ''' resource_registry: "OS::Thingy": "sftp://no.where/dev/null/a.yaml" ''' jenv = yaml.safe_load(env) fields = {'files': {}} self.assertRaises(exc.CommandError, template_utils.get_file_contents, jenv['resource_registry'], fields) class TestGetTemplateContents(testtools.TestCase): def setUp(self): super(TestGetTemplateContents, self).setUp() self.m = mox.Mox() self.addCleanup(self.m.VerifyAll) self.addCleanup(self.m.UnsetStubs) def test_get_template_contents_file(self): with tempfile.NamedTemporaryFile() as tmpl_file: tmpl = b'{"AWSTemplateFormatVersion" : "2010-09-09",' \ b' "foo": "bar"}' tmpl_file.write(tmpl) tmpl_file.flush() files, tmpl_parsed = template_utils.get_template_contents( tmpl_file.name) self.assertEqual({"AWSTemplateFormatVersion": "2010-09-09", "foo": "bar"}, tmpl_parsed) self.assertEqual({}, files) def test_get_template_contents_file_empty(self): with tempfile.NamedTemporaryFile() as tmpl_file: ex = self.assertRaises( exc.CommandError, template_utils.get_template_contents, tmpl_file.name) self.assertEqual( str(ex), 'Could not fetch template from file://%s' % tmpl_file.name) def test_get_template_contents_file_none(self): ex = self.assertRaises( exc.CommandError, template_utils.get_template_contents) self.assertEqual( str(ex), ('Need to specify exactly one of --template-file, ' '--template-url or --template-object')) def test_get_template_contents_parse_error(self): with tempfile.NamedTemporaryFile() as tmpl_file: tmpl = b'{"foo": "bar"' tmpl_file.write(tmpl) tmpl_file.flush() ex = self.assertRaises( exc.CommandError, template_utils.get_template_contents, tmpl_file.name) self.assertThat( str(ex), MatchesRegex( 'Error parsing template file://%s ' % tmpl_file.name)) def test_get_template_contents_url(self): tmpl = '{"AWSTemplateFormatVersion" : "2010-09-09", "foo": "bar"}' url = 'http://no.where/path/to/a.yaml' self.m.StubOutWithMock(urlutils, 'urlopen') urlutils.urlopen(url).AndReturn(six.StringIO(tmpl)) self.m.ReplayAll() files, tmpl_parsed = template_utils.get_template_contents( template_url=url) self.assertEqual({"AWSTemplateFormatVersion": "2010-09-09", "foo": "bar"}, tmpl_parsed) self.assertEqual({}, files) def test_get_template_contents_object(self): tmpl = '{"AWSTemplateFormatVersion" : "2010-09-09", "foo": "bar"}' url = 'http://no.where/path/to/a.yaml' self.m.ReplayAll() self.object_requested = False def object_request(method, object_url): self.object_requested = True self.assertEqual('GET', method) self.assertEqual('http://no.where/path/to/a.yaml', object_url) return tmpl files, tmpl_parsed = template_utils.get_template_contents( template_object=url, object_request=object_request) self.assertEqual({"AWSTemplateFormatVersion": "2010-09-09", "foo": "bar"}, tmpl_parsed) self.assertEqual({}, files) self.assertTrue(self.object_requested) class TestTemplateGetFileFunctions(testtools.TestCase): hot_template = '''heat_template_version: 2013-05-23 resources: resource1: type: type1 properties: foo: {get_file: foo.yaml} bar: get_file: 'http://localhost/bar.yaml' resource2: type: type1 properties: baz: - {get_file: baz/baz1.yaml} - {get_file: baz/baz2.yaml} - {get_file: baz/baz3.yaml} ignored_list: {get_file: [ignore, me]} ignored_dict: {get_file: {ignore: me}} ignored_none: {get_file: } ''' def setUp(self): super(TestTemplateGetFileFunctions, self).setUp() self.m = mox.Mox() self.addCleanup(self.m.VerifyAll) self.addCleanup(self.m.UnsetStubs) def test_hot_template(self): self.m.StubOutWithMock(urlutils, 'urlopen') tmpl_file = '/home/my/dir/template.yaml' url = 'file:///home/my/dir/template.yaml' urlutils.urlopen(url).AndReturn( six.StringIO(self.hot_template)) urlutils.urlopen( 'http://localhost/bar.yaml').InAnyOrder().AndReturn( six.StringIO('bar contents')) urlutils.urlopen( 'file:///home/my/dir/foo.yaml').InAnyOrder().AndReturn( six.StringIO('foo contents')) urlutils.urlopen( 'file:///home/my/dir/baz/baz1.yaml').InAnyOrder().AndReturn( six.StringIO('baz1 contents')) urlutils.urlopen( 'file:///home/my/dir/baz/baz2.yaml').InAnyOrder().AndReturn( six.StringIO('baz2 contents')) urlutils.urlopen( 'file:///home/my/dir/baz/baz3.yaml').InAnyOrder().AndReturn( six.StringIO('baz3 contents')) self.m.ReplayAll() files, tmpl_parsed = template_utils.get_template_contents( template_file=tmpl_file) self.assertEqual({ 'http://localhost/bar.yaml': 'bar contents', 'file:///home/my/dir/foo.yaml': 'foo contents', 'file:///home/my/dir/baz/baz1.yaml': 'baz1 contents', 'file:///home/my/dir/baz/baz2.yaml': 'baz2 contents', 'file:///home/my/dir/baz/baz3.yaml': 'baz3 contents', }, files) self.assertEqual({ 'heat_template_version': '2013-05-23', 'resources': { 'resource1': { 'type': 'type1', 'properties': { 'bar': {'get_file': 'http://localhost/bar.yaml'}, 'foo': {'get_file': 'file:///home/my/dir/foo.yaml'}, }, }, 'resource2': { 'type': 'type1', 'properties': { 'baz': [ {'get_file': 'file:///home/my/dir/baz/baz1.yaml'}, {'get_file': 'file:///home/my/dir/baz/baz2.yaml'}, {'get_file': 'file:///home/my/dir/baz/baz3.yaml'}, ], 'ignored_list': {'get_file': ['ignore', 'me']}, 'ignored_dict': {'get_file': {'ignore': 'me'}}, 'ignored_none': {'get_file': None}, }, } } }, tmpl_parsed) self.m.VerifyAll() def test_hot_template_outputs(self): self.m.StubOutWithMock(urlutils, 'urlopen') tmpl_file = '/home/my/dir/template.yaml' url = 'file://%s' % tmpl_file contents = str('heat_template_version: 2013-05-23\n' 'outputs:\n' ' contents:\n' ' value:\n' ' get_file: template.yaml\n') urlutils.urlopen(url).AndReturn(six.StringIO(contents)) urlutils.urlopen(url).AndReturn(six.StringIO(contents)) self.m.ReplayAll() files, tmpl_parsed = template_utils.get_template_contents( template_file=tmpl_file) self.assertEqual({url: contents}, files) self.m.VerifyAll() class TestURLFunctions(testtools.TestCase): def setUp(self): super(TestURLFunctions, self).setUp() self.m = mox.Mox() self.addCleanup(self.m.VerifyAll) self.addCleanup(self.m.UnsetStubs) def test_normalise_file_path_to_url_relative(self): self.assertEqual( 'file://%s/foo' % os.getcwd(), template_utils.normalise_file_path_to_url( 'foo')) def test_normalise_file_path_to_url_absolute(self): self.assertEqual( 'file:///tmp/foo', template_utils.normalise_file_path_to_url( '/tmp/foo')) def test_normalise_file_path_to_url_file(self): self.assertEqual( 'file:///tmp/foo', template_utils.normalise_file_path_to_url( 'file:///tmp/foo')) def test_normalise_file_path_to_url_http(self): self.assertEqual( 'http://localhost/foo', template_utils.normalise_file_path_to_url( 'http://localhost/foo')) def test_base_url_for_url(self): self.assertEqual( 'file:///foo/bar', template_utils.base_url_for_url( 'file:///foo/bar/baz')) self.assertEqual( 'file:///foo/bar', template_utils.base_url_for_url( 'file:///foo/bar/baz.txt')) self.assertEqual( 'file:///foo/bar', template_utils.base_url_for_url( 'file:///foo/bar/')) self.assertEqual( 'file:///', template_utils.base_url_for_url( 'file:///')) self.assertEqual( 'file:///', template_utils.base_url_for_url( 'file:///foo')) self.assertEqual( 'http://foo/bar', template_utils.base_url_for_url( 'http://foo/bar/')) self.assertEqual( 'http://foo/bar', template_utils.base_url_for_url( 'http://foo/bar/baz.template')) python-heatclient-0.2.8/heatclient/tests/__init__.py0000664000175400017540000000000012304731347023703 0ustar jenkinsjenkins00000000000000python-heatclient-0.2.8/heatclient/tests/v1/0000775000175400017540000000000012304731421022123 5ustar jenkinsjenkins00000000000000python-heatclient-0.2.8/heatclient/tests/v1/__init__.py0000664000175400017540000000000012304731347024231 0ustar jenkinsjenkins00000000000000python-heatclient-0.2.8/heatclient/tests/test_resources.py0000664000175400017540000000757412304731350025236 0ustar jenkinsjenkins00000000000000# Copyright 2013 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. from heatclient.v1.resources import ResourceManager from mox3 import mox import testtools class ResourceManagerTest(testtools.TestCase): def setUp(self): super(ResourceManagerTest, self).setUp() self.m = mox.Mox() self.addCleanup(self.m.UnsetStubs) self.addCleanup(self.m.ResetAll) def _base_test(self, expect, key): class FakeAPI(object): """Fake API and ensure request url is correct.""" def get(self, *args, **kwargs): assert ('GET', args[0]) == expect def json_request(self, *args, **kwargs): assert args == expect ret = key and {key: []} or {} return {}, {key: ret} manager = ResourceManager(FakeAPI()) self.m.StubOutWithMock(manager, '_resolve_stack_id') manager._resolve_stack_id('teststack').AndReturn('teststack/abcd1234') self.m.ReplayAll() return manager def test_get_event(self): fields = {'stack_id': 'teststack', 'resource_name': 'testresource'} expect = ('GET', '/stacks/teststack%2Fabcd1234/resources' '/testresource') key = 'resource' manager = self._base_test(expect, key) manager.get(**fields) def test_get_event_with_unicode_resource_name(self): fields = {'stack_id': 'teststack', 'resource_name': u'\u5de5\u4f5c'} expect = ('GET', '/stacks/teststack%2Fabcd1234/resources' '/%E5%B7%A5%E4%BD%9C') key = 'resource' manager = self._base_test(expect, key) manager.get(**fields) def test_list(self): fields = {'stack_id': 'teststack'} expect = ('/stacks/teststack/resources') key = 'resources' class FakeResponse(object): def json(self): return {key: {}} class FakeClient(object): def get(self, *args, **kwargs): assert args[0] == expect return FakeResponse() manager = ResourceManager(FakeClient()) self.m.StubOutWithMock(manager, '_resolve_stack_id') manager._resolve_stack_id('teststack').AndReturn('teststack/abcd1234') self.m.ReplayAll() manager.list(**fields) def test_metadata(self): fields = {'stack_id': 'teststack', 'resource_name': 'testresource'} expect = ('GET', '/stacks/teststack%2Fabcd1234/resources' '/testresource/metadata') key = 'metadata' manager = self._base_test(expect, key) manager.metadata(**fields) def test_generate_template(self): fields = {'resource_name': 'testresource'} expect = ('GET', '/resource_types/testresource/template') key = None manager = self._base_test(expect, key) manager.generate_template(**fields) def test_signal(self): fields = {'stack_id': 'teststack', 'resource_name': 'testresource', 'data': 'Some content'} expect = ('POST', '/stacks/teststack%2Fabcd1234/resources' '/testresource/signal') key = 'signal' manager = self._base_test(expect, key) manager.signal(**fields) python-heatclient-0.2.8/heatclient/shell.py0000664000175400017540000004202012304731347022121 0ustar jenkinsjenkins00000000000000# 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. """ Command-line interface to the Heat API. """ from __future__ import print_function import argparse import logging import six import sys from keystoneclient.v2_0 import client as ksclient import heatclient from heatclient import client as heat_client from heatclient.common import utils from heatclient import exc from heatclient.openstack.common import strutils logger = logging.getLogger(__name__) class HeatShell(object): def get_base_parser(self): parser = argparse.ArgumentParser( prog='heat', description=__doc__.strip(), epilog='See "heat help COMMAND" ' 'for help on a specific command.', add_help=False, formatter_class=HelpFormatter, ) # Global arguments parser.add_argument('-h', '--help', action='store_true', help=argparse.SUPPRESS) parser.add_argument('--version', action='version', version=heatclient.__version__, help="Shows the client version and exits.") parser.add_argument('-d', '--debug', default=bool(utils.env('HEATCLIENT_DEBUG')), action='store_true', help='Defaults to env[HEATCLIENT_DEBUG].') parser.add_argument('-v', '--verbose', default=False, action="store_true", help="Print more verbose output.") parser.add_argument('-k', '--insecure', default=False, action='store_true', help="Explicitly allow the client to perform " "\"insecure\" SSL (https) requests. The server's " "certificate will not be verified against any " "certificate authorities. " "This option should be used with caution.") parser.add_argument('--cert-file', help='Path of certificate file to use in SSL ' 'connection. This file can optionally be ' 'prepended with the private key.') parser.add_argument('--key-file', help='Path of client key to use in SSL connection.' 'This option is not necessary if your key is' ' prepended to your cert file.') parser.add_argument('--ca-file', help='Path of CA SSL certificate(s) used to verify' ' the remote server\'s certificate. Without this' ' option the client looks' ' for the default system CA certificates.') parser.add_argument('--timeout', default=600, help='Number of seconds to wait for a response.') parser.add_argument('--os-username', default=utils.env('OS_USERNAME'), help='Defaults to env[OS_USERNAME].') parser.add_argument('--os_username', help=argparse.SUPPRESS) parser.add_argument('--os-password', default=utils.env('OS_PASSWORD'), help='Defaults to env[OS_PASSWORD].') parser.add_argument('--os_password', help=argparse.SUPPRESS) parser.add_argument('--os-tenant-id', default=utils.env('OS_TENANT_ID'), help='Defaults to env[OS_TENANT_ID].') parser.add_argument('--os_tenant_id', help=argparse.SUPPRESS) parser.add_argument('--os-tenant-name', default=utils.env('OS_TENANT_NAME'), help='Defaults to env[OS_TENANT_NAME].') parser.add_argument('--os_tenant_name', help=argparse.SUPPRESS) parser.add_argument('--os-auth-url', default=utils.env('OS_AUTH_URL'), help='Defaults to env[OS_AUTH_URL].') parser.add_argument('--os_auth_url', help=argparse.SUPPRESS) parser.add_argument('--os-region-name', default=utils.env('OS_REGION_NAME'), help='Defaults to env[OS_REGION_NAME].') parser.add_argument('--os_region_name', help=argparse.SUPPRESS) parser.add_argument('--os-auth-token', default=utils.env('OS_AUTH_TOKEN'), help='Defaults to env[OS_AUTH_TOKEN].') parser.add_argument('--os_auth_token', help=argparse.SUPPRESS) parser.add_argument('--os-no-client-auth', default=utils.env('OS_NO_CLIENT_AUTH'), action='store_true', help="Do not contact keystone for a token. " "Defaults to env[OS_NO_CLIENT_AUTH].") parser.add_argument('--heat-url', default=utils.env('HEAT_URL'), help='Defaults to env[HEAT_URL].') parser.add_argument('--heat_url', help=argparse.SUPPRESS) parser.add_argument('--heat-api-version', default=utils.env('HEAT_API_VERSION', default='1'), help='Defaults to env[HEAT_API_VERSION] or 1.') parser.add_argument('--heat_api_version', help=argparse.SUPPRESS) parser.add_argument('--os-service-type', default=utils.env('OS_SERVICE_TYPE'), help='Defaults to env[OS_SERVICE_TYPE].') parser.add_argument('--os_service_type', help=argparse.SUPPRESS) parser.add_argument('--os-endpoint-type', default=utils.env('OS_ENDPOINT_TYPE'), help='Defaults to env[OS_ENDPOINT_TYPE].') parser.add_argument('--os_endpoint_type', help=argparse.SUPPRESS) # This unused option should remain so that scripts that # use it do not break. It is suppressed so it will not # appear in the help. parser.add_argument('-t', '--token-only', default=bool(False), action='store_true', help=argparse.SUPPRESS) parser.add_argument('--include-password', default=bool(utils.env('HEAT_INCLUDE_PASSWORD')), action='store_true', help='Send os-username and os-password to heat.') return parser def get_subcommand_parser(self, version): parser = self.get_base_parser() self.subcommands = {} subparsers = parser.add_subparsers(metavar='') submodule = utils.import_versioned_module(version, 'shell') self._find_actions(subparsers, submodule) self._find_actions(subparsers, self) self._add_bash_completion_subparser(subparsers) return parser def _add_bash_completion_subparser(self, subparsers): subparser = subparsers.add_parser( 'bash_completion', add_help=False, formatter_class=HelpFormatter ) self.subcommands['bash_completion'] = subparser subparser.set_defaults(func=self.do_bash_completion) def _find_actions(self, subparsers, actions_module): for attr in (a for a in dir(actions_module) if a.startswith('do_')): # I prefer to be hyphen-separated instead of underscores. command = attr[3:].replace('_', '-') callback = getattr(actions_module, attr) desc = callback.__doc__ or '' help = desc.strip().split('\n')[0] arguments = getattr(callback, 'arguments', []) subparser = subparsers.add_parser(command, help=help, description=desc, add_help=False, formatter_class=HelpFormatter) subparser.add_argument('-h', '--help', action='help', help=argparse.SUPPRESS) self.subcommands[command] = subparser for (args, kwargs) in arguments: subparser.add_argument(*args, **kwargs) subparser.set_defaults(func=callback) def _get_ksclient(self, **kwargs): """Get an endpoint and auth token from Keystone. :param username: name of user :param password: user's password :param tenant_id: unique identifier of tenant :param tenant_name: name of tenant :param auth_url: endpoint to authenticate against :param token: token to use instead of username/password """ kc_args = {'auth_url': kwargs.get('auth_url'), 'insecure': kwargs.get('insecure')} if kwargs.get('tenant_id'): kc_args['tenant_id'] = kwargs.get('tenant_id') else: kc_args['tenant_name'] = kwargs.get('tenant_name') if kwargs.get('token'): kc_args['token'] = kwargs.get('token') else: kc_args['username'] = kwargs.get('username') kc_args['password'] = kwargs.get('password') return ksclient.Client(**kc_args) def _get_endpoint(self, client, **kwargs): """Get an endpoint using the provided keystone client.""" if kwargs.get('region_name'): return client.service_catalog.url_for( service_type=kwargs.get('service_type') or 'orchestration', attr='region', filter_value=kwargs.get('region_name'), endpoint_type=kwargs.get('endpoint_type') or 'publicURL') return client.service_catalog.url_for( service_type=kwargs.get('service_type') or 'orchestration', endpoint_type=kwargs.get('endpoint_type') or 'publicURL') def _setup_logging(self, debug): log_lvl = logging.DEBUG if debug else logging.ERROR logging.basicConfig( format="%(levelname)s (%(module)s:%(lineno)d) %(message)s", level=log_lvl) def _setup_verbose(self, verbose): if verbose: exc.verbose = 1 def main(self, argv): # Parse args once to find version parser = self.get_base_parser() (options, args) = parser.parse_known_args(argv) self._setup_logging(options.debug) self._setup_verbose(options.verbose) # build available subcommands based on version api_version = options.heat_api_version subcommand_parser = self.get_subcommand_parser(api_version) self.parser = subcommand_parser # Handle top-level --help/-h before attempting to parse # a command off the command line if options.help or not argv: self.do_help(options) return 0 # Parse args again and call whatever callback was selected args = subcommand_parser.parse_args(argv) # Short-circuit and deal with help command right away. if args.func == self.do_help: self.do_help(args) return 0 elif args.func == self.do_bash_completion: self.do_bash_completion(args) return 0 if not args.os_username and not args.os_auth_token: raise exc.CommandError("You must provide a username via" " either --os-username or env[OS_USERNAME]" " or a token via --os-auth-token or" " env[OS_AUTH_TOKEN]") if not args.os_password and not args.os_auth_token: raise exc.CommandError("You must provide a password via" " either --os-password or env[OS_PASSWORD]" " or a token via --os-auth-token or" " env[OS_AUTH_TOKEN]") if args.os_no_client_auth: if not args.heat_url: raise exc.CommandError("If you specify --os-no-client-auth" " you must also specify a Heat API URL" " via either --heat-url or" " env[HEAT_URL]") else: # Tenant name or ID is needed to make keystoneclient retrieve a # service catalog, it's not required if os_no_client_auth is # specified, neither is the auth URL if not (args.os_tenant_id or args.os_tenant_name): raise exc.CommandError("You must provide a tenant_id via" " either --os-tenant-id or via" " env[OS_TENANT_ID]") if not args.os_auth_url: raise exc.CommandError("You must provide an auth url via" " either --os-auth-url or via" " env[OS_AUTH_URL]") kwargs = { 'username': args.os_username, 'password': args.os_password, 'token': args.os_auth_token, 'tenant_id': args.os_tenant_id, 'tenant_name': args.os_tenant_name, 'auth_url': args.os_auth_url, 'service_type': args.os_service_type, 'endpoint_type': args.os_endpoint_type, 'insecure': args.insecure, 'include_pass': args.include_password } endpoint = args.heat_url if not args.os_no_client_auth: _ksclient = self._get_ksclient(**kwargs) token = args.os_auth_token or _ksclient.auth_token kwargs = { 'token': token, 'insecure': args.insecure, 'timeout': args.timeout, 'ca_file': args.ca_file, 'cert_file': args.cert_file, 'key_file': args.key_file, 'username': args.os_username, 'password': args.os_password, 'endpoint_type': args.os_endpoint_type, 'include_pass': args.include_password } if args.os_region_name: kwargs['region_name'] = args.os_region_name if not endpoint: endpoint = self._get_endpoint(_ksclient, **kwargs) client = heat_client.Client(api_version, endpoint, **kwargs) args.func(client, args) def do_bash_completion(self, args): """Prints all of the commands and options to stdout. The heat.bash_completion script doesn't have to hard code them. """ commands = set() options = set() for sc_str, sc in self.subcommands.items(): commands.add(sc_str) for option in list(sc._optionals._option_string_actions): options.add(option) commands.remove('bash-completion') commands.remove('bash_completion') print(' '.join(commands | options)) @utils.arg('command', metavar='', nargs='?', help='Display help for .') def do_help(self, args): """Display help about this program or one of its subcommands.""" if getattr(args, 'command', None): if args.command in self.subcommands: self.subcommands[args.command].print_help() else: raise exc.CommandError("'%s' is not a valid subcommand" % args.command) else: self.parser.print_help() class HelpFormatter(argparse.HelpFormatter): def start_section(self, heading): # Title-case the headings heading = '%s%s' % (heading[0].upper(), heading[1:]) super(HelpFormatter, self).start_section(heading) def main(args=None): try: if args is None: args = sys.argv[1:] HeatShell().main(args) except Exception as e: if '--debug' in args or '-d' in args: raise else: print(strutils.safe_encode(six.text_type(e)), file=sys.stderr) sys.exit(1) if __name__ == "__main__": main() python-heatclient-0.2.8/heatclient/openstack/0000775000175400017540000000000012304731421022422 5ustar jenkinsjenkins00000000000000python-heatclient-0.2.8/heatclient/openstack/common/0000775000175400017540000000000012304731421023712 5ustar jenkinsjenkins00000000000000python-heatclient-0.2.8/heatclient/openstack/common/apiclient/0000775000175400017540000000000012304731421025662 5ustar jenkinsjenkins00000000000000python-heatclient-0.2.8/heatclient/openstack/common/apiclient/base.py0000664000175400017540000003716012304731350027156 0ustar jenkinsjenkins00000000000000# Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 OpenStack Foundation # Copyright 2012 Grid Dynamics # Copyright 2013 OpenStack Foundation # All Rights Reserved. # # 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. """ Base utilities to build API operation managers and objects on top of. """ # E1102: %s is not callable # pylint: disable=E1102 import abc import copy import six from heatclient.openstack.common.apiclient import exceptions from heatclient.openstack.common.py3kcompat import urlutils from heatclient.openstack.common import strutils def getid(obj): """Return id if argument is a Resource. Abstracts the common pattern of allowing both an object or an object's ID (UUID) as a parameter when dealing with relationships. """ try: if obj.uuid: return obj.uuid except AttributeError: pass try: return obj.id except AttributeError: return obj # TODO(aababilov): call run_hooks() in HookableMixin's child classes class HookableMixin(object): """Mixin so classes can register and run hooks.""" _hooks_map = {} @classmethod def add_hook(cls, hook_type, hook_func): """Add a new hook of specified type. :param cls: class that registers hooks :param hook_type: hook type, e.g., '__pre_parse_args__' :param hook_func: hook function """ if hook_type not in cls._hooks_map: cls._hooks_map[hook_type] = [] cls._hooks_map[hook_type].append(hook_func) @classmethod def run_hooks(cls, hook_type, *args, **kwargs): """Run all hooks of specified type. :param cls: class that registers hooks :param hook_type: hook type, e.g., '__pre_parse_args__' :param **args: args to be passed to every hook function :param **kwargs: kwargs to be passed to every hook function """ hook_funcs = cls._hooks_map.get(hook_type) or [] for hook_func in hook_funcs: hook_func(*args, **kwargs) class BaseManager(HookableMixin): """Basic manager type providing common operations. Managers interact with a particular type of API (servers, flavors, images, etc.) and provide CRUD operations for them. """ resource_class = None def __init__(self, client): """Initializes BaseManager with `client`. :param client: instance of BaseClient descendant for HTTP requests """ super(BaseManager, self).__init__() self.client = client def _list(self, url, response_key, obj_class=None, json=None): """List the collection. :param url: a partial URL, e.g., '/servers' :param response_key: the key to be looked up in response dictionary, e.g., 'servers' :param obj_class: class for constructing the returned objects (self.resource_class will be used by default) :param json: data that will be encoded as JSON and passed in POST request (GET will be sent by default) """ if json: body = self.client.post(url, json=json).json() else: body = self.client.get(url).json() if obj_class is None: obj_class = self.resource_class data = body[response_key] # NOTE(ja): keystone returns values as list as {'values': [ ... ]} # unlike other services which just return the list... try: data = data['values'] except (KeyError, TypeError): pass return [obj_class(self, res, loaded=True) for res in data if res] def _get(self, url, response_key): """Get an object from collection. :param url: a partial URL, e.g., '/servers' :param response_key: the key to be looked up in response dictionary, e.g., 'server' """ body = self.client.get(url).json() return self.resource_class(self, body[response_key], loaded=True) def _head(self, url): """Retrieve request headers for an object. :param url: a partial URL, e.g., '/servers' """ resp = self.client.head(url) return resp.status_code == 204 def _post(self, url, json, response_key, return_raw=False): """Create an object. :param url: a partial URL, e.g., '/servers' :param json: data that will be encoded as JSON and passed in POST request (GET will be sent by default) :param response_key: the key to be looked up in response dictionary, e.g., 'servers' :param return_raw: flag to force returning raw JSON instead of Python object of self.resource_class """ body = self.client.post(url, json=json).json() if return_raw: return body[response_key] return self.resource_class(self, body[response_key]) def _put(self, url, json=None, response_key=None): """Update an object with PUT method. :param url: a partial URL, e.g., '/servers' :param json: data that will be encoded as JSON and passed in POST request (GET will be sent by default) :param response_key: the key to be looked up in response dictionary, e.g., 'servers' """ resp = self.client.put(url, json=json) # PUT requests may not return a body if resp.content: body = resp.json() if response_key is not None: return self.resource_class(self, body[response_key]) else: return self.resource_class(self, body) def _patch(self, url, json=None, response_key=None): """Update an object with PATCH method. :param url: a partial URL, e.g., '/servers' :param json: data that will be encoded as JSON and passed in POST request (GET will be sent by default) :param response_key: the key to be looked up in response dictionary, e.g., 'servers' """ body = self.client.patch(url, json=json).json() if response_key is not None: return self.resource_class(self, body[response_key]) else: return self.resource_class(self, body) def _delete(self, url): """Delete an object. :param url: a partial URL, e.g., '/servers/my-server' """ return self.client.delete(url) @six.add_metaclass(abc.ABCMeta) class ManagerWithFind(BaseManager): """Manager with additional `find()`/`findall()` methods.""" @abc.abstractmethod def list(self): pass def find(self, **kwargs): """Find a single item with attributes matching ``**kwargs``. This isn't very efficient: it loads the entire list then filters on the Python side. """ matches = self.findall(**kwargs) num_matches = len(matches) if num_matches == 0: msg = "No %s matching %s." % (self.resource_class.__name__, kwargs) raise exceptions.NotFound(msg) elif num_matches > 1: raise exceptions.NoUniqueMatch() else: return matches[0] def findall(self, **kwargs): """Find all items with attributes matching ``**kwargs``. This isn't very efficient: it loads the entire list then filters on the Python side. """ found = [] searches = kwargs.items() for obj in self.list(): try: if all(getattr(obj, attr) == value for (attr, value) in searches): found.append(obj) except AttributeError: continue return found class CrudManager(BaseManager): """Base manager class for manipulating entities. Children of this class are expected to define a `collection_key` and `key`. - `collection_key`: Usually a plural noun by convention (e.g. `entities`); used to refer collections in both URL's (e.g. `/v3/entities`) and JSON objects containing a list of member resources (e.g. `{'entities': [{}, {}, {}]}`). - `key`: Usually a singular noun by convention (e.g. `entity`); used to refer to an individual member of the collection. """ collection_key = None key = None def build_url(self, base_url=None, **kwargs): """Builds a resource URL for the given kwargs. Given an example collection where `collection_key = 'entities'` and `key = 'entity'`, the following URL's could be generated. By default, the URL will represent a collection of entities, e.g.:: /entities If kwargs contains an `entity_id`, then the URL will represent a specific member, e.g.:: /entities/{entity_id} :param base_url: if provided, the generated URL will be appended to it """ url = base_url if base_url is not None else '' url += '/%s' % self.collection_key # do we have a specific entity? entity_id = kwargs.get('%s_id' % self.key) if entity_id is not None: url += '/%s' % entity_id return url def _filter_kwargs(self, kwargs): """Drop null values and handle ids.""" for key, ref in six.iteritems(kwargs.copy()): if ref is None: kwargs.pop(key) else: if isinstance(ref, Resource): kwargs.pop(key) kwargs['%s_id' % key] = getid(ref) return kwargs def create(self, **kwargs): kwargs = self._filter_kwargs(kwargs) return self._post( self.build_url(**kwargs), {self.key: kwargs}, self.key) def get(self, **kwargs): kwargs = self._filter_kwargs(kwargs) return self._get( self.build_url(**kwargs), self.key) def head(self, **kwargs): kwargs = self._filter_kwargs(kwargs) return self._head(self.build_url(**kwargs)) def list(self, base_url=None, **kwargs): """List the collection. :param base_url: if provided, the generated URL will be appended to it """ kwargs = self._filter_kwargs(kwargs) return self._list( '%(base_url)s%(query)s' % { 'base_url': self.build_url(base_url=base_url, **kwargs), 'query': '?%s' % urlutils.urlencode(kwargs) if kwargs else '', }, self.collection_key) def put(self, base_url=None, **kwargs): """Update an element. :param base_url: if provided, the generated URL will be appended to it """ kwargs = self._filter_kwargs(kwargs) return self._put(self.build_url(base_url=base_url, **kwargs)) def update(self, **kwargs): kwargs = self._filter_kwargs(kwargs) params = kwargs.copy() params.pop('%s_id' % self.key) return self._patch( self.build_url(**kwargs), {self.key: params}, self.key) def delete(self, **kwargs): kwargs = self._filter_kwargs(kwargs) return self._delete( self.build_url(**kwargs)) def find(self, base_url=None, **kwargs): """Find a single item with attributes matching ``**kwargs``. :param base_url: if provided, the generated URL will be appended to it """ kwargs = self._filter_kwargs(kwargs) rl = self._list( '%(base_url)s%(query)s' % { 'base_url': self.build_url(base_url=base_url, **kwargs), 'query': '?%s' % urlutils.urlencode(kwargs) if kwargs else '', }, self.collection_key) num = len(rl) if num == 0: msg = "No %s matching %s." % (self.resource_class.__name__, kwargs) raise exceptions.NotFound(404, msg) elif num > 1: raise exceptions.NoUniqueMatch else: return rl[0] class Extension(HookableMixin): """Extension descriptor.""" SUPPORTED_HOOKS = ('__pre_parse_args__', '__post_parse_args__') manager_class = None def __init__(self, name, module): super(Extension, self).__init__() self.name = name self.module = module self._parse_extension_module() def _parse_extension_module(self): self.manager_class = None for attr_name, attr_value in self.module.__dict__.items(): if attr_name in self.SUPPORTED_HOOKS: self.add_hook(attr_name, attr_value) else: try: if issubclass(attr_value, BaseManager): self.manager_class = attr_value except TypeError: pass def __repr__(self): return "" % self.name class Resource(object): """Base class for OpenStack resources (tenant, user, etc.). This is pretty much just a bag for attributes. """ HUMAN_ID = False NAME_ATTR = 'name' def __init__(self, manager, info, loaded=False): """Populate and bind to a manager. :param manager: BaseManager object :param info: dictionary representing resource attributes :param loaded: prevent lazy-loading if set to True """ self.manager = manager self._info = info self._add_details(info) self._loaded = loaded def __repr__(self): reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and k != 'manager') info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) return "<%s %s>" % (self.__class__.__name__, info) @property def human_id(self): """Human-readable ID which can be used for bash completion. """ if self.NAME_ATTR in self.__dict__ and self.HUMAN_ID: return strutils.to_slug(getattr(self, self.NAME_ATTR)) return None def _add_details(self, info): for (k, v) in six.iteritems(info): try: setattr(self, k, v) self._info[k] = v except AttributeError: # In this case we already defined the attribute on the class pass def __getattr__(self, k): if k not in self.__dict__: #NOTE(bcwaldon): disallow lazy-loading if already loaded once if not self.is_loaded: self._get() return self.__getattr__(k) raise AttributeError(k) else: return self.__dict__[k] def _get(self): # set _loaded first ... so if we have to bail, we know we tried. self._loaded = True if not hasattr(self.manager, 'get'): return new = self.manager.get(self.id) if new: self._add_details(new._info) def __eq__(self, other): if not isinstance(other, Resource): return NotImplemented # two resources of different types are not equal if not isinstance(other, self.__class__): return False if hasattr(self, 'id') and hasattr(other, 'id'): return self.id == other.id return self._info == other._info @property def is_loaded(self): return self._loaded def to_dict(self): return copy.deepcopy(self._info) python-heatclient-0.2.8/heatclient/openstack/common/apiclient/exceptions.py0000664000175400017540000002703712304731350030427 0ustar jenkinsjenkins00000000000000# Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 Nebula, Inc. # Copyright 2013 Alessio Ababilov # Copyright 2013 OpenStack Foundation # All Rights Reserved. # # 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. """ Exception definitions. """ import inspect import sys import six class ClientException(Exception): """The base exception class for all exceptions this library raises. """ pass class MissingArgs(ClientException): """Supplied arguments are not sufficient for calling a function.""" def __init__(self, missing): self.missing = missing msg = "Missing argument(s): %s" % ", ".join(missing) super(MissingArgs, self).__init__(msg) class ValidationError(ClientException): """Error in validation on API client side.""" pass class UnsupportedVersion(ClientException): """User is trying to use an unsupported version of the API.""" pass class CommandError(ClientException): """Error in CLI tool.""" pass class AuthorizationFailure(ClientException): """Cannot authorize API client.""" pass class AuthPluginOptionsMissing(AuthorizationFailure): """Auth plugin misses some options.""" def __init__(self, opt_names): super(AuthPluginOptionsMissing, self).__init__( "Authentication failed. Missing options: %s" % ", ".join(opt_names)) self.opt_names = opt_names class AuthSystemNotFound(AuthorizationFailure): """User has specified a AuthSystem that is not installed.""" def __init__(self, auth_system): super(AuthSystemNotFound, self).__init__( "AuthSystemNotFound: %s" % repr(auth_system)) self.auth_system = auth_system class NoUniqueMatch(ClientException): """Multiple entities found instead of one.""" pass class EndpointException(ClientException): """Something is rotten in Service Catalog.""" pass class EndpointNotFound(EndpointException): """Could not find requested endpoint in Service Catalog.""" pass class AmbiguousEndpoints(EndpointException): """Found more than one matching endpoint in Service Catalog.""" def __init__(self, endpoints=None): super(AmbiguousEndpoints, self).__init__( "AmbiguousEndpoints: %s" % repr(endpoints)) self.endpoints = endpoints class HttpError(ClientException): """The base exception class for all HTTP exceptions. """ http_status = 0 message = "HTTP Error" def __init__(self, message=None, details=None, response=None, request_id=None, url=None, method=None, http_status=None): self.http_status = http_status or self.http_status self.message = message or self.message self.details = details self.request_id = request_id self.response = response self.url = url self.method = method formatted_string = "%s (HTTP %s)" % (self.message, self.http_status) if request_id: formatted_string += " (Request-ID: %s)" % request_id super(HttpError, self).__init__(formatted_string) class HTTPClientError(HttpError): """Client-side HTTP error. Exception for cases in which the client seems to have erred. """ message = "HTTP Client Error" class HttpServerError(HttpError): """Server-side HTTP error. Exception for cases in which the server is aware that it has erred or is incapable of performing the request. """ message = "HTTP Server Error" class BadRequest(HTTPClientError): """HTTP 400 - Bad Request. The request cannot be fulfilled due to bad syntax. """ http_status = 400 message = "Bad Request" class Unauthorized(HTTPClientError): """HTTP 401 - Unauthorized. Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet been provided. """ http_status = 401 message = "Unauthorized" class PaymentRequired(HTTPClientError): """HTTP 402 - Payment Required. Reserved for future use. """ http_status = 402 message = "Payment Required" class Forbidden(HTTPClientError): """HTTP 403 - Forbidden. The request was a valid request, but the server is refusing to respond to it. """ http_status = 403 message = "Forbidden" class NotFound(HTTPClientError): """HTTP 404 - Not Found. The requested resource could not be found but may be available again in the future. """ http_status = 404 message = "Not Found" class MethodNotAllowed(HTTPClientError): """HTTP 405 - Method Not Allowed. A request was made of a resource using a request method not supported by that resource. """ http_status = 405 message = "Method Not Allowed" class NotAcceptable(HTTPClientError): """HTTP 406 - Not Acceptable. The requested resource is only capable of generating content not acceptable according to the Accept headers sent in the request. """ http_status = 406 message = "Not Acceptable" class ProxyAuthenticationRequired(HTTPClientError): """HTTP 407 - Proxy Authentication Required. The client must first authenticate itself with the proxy. """ http_status = 407 message = "Proxy Authentication Required" class RequestTimeout(HTTPClientError): """HTTP 408 - Request Timeout. The server timed out waiting for the request. """ http_status = 408 message = "Request Timeout" class Conflict(HTTPClientError): """HTTP 409 - Conflict. Indicates that the request could not be processed because of conflict in the request, such as an edit conflict. """ http_status = 409 message = "Conflict" class Gone(HTTPClientError): """HTTP 410 - Gone. Indicates that the resource requested is no longer available and will not be available again. """ http_status = 410 message = "Gone" class LengthRequired(HTTPClientError): """HTTP 411 - Length Required. The request did not specify the length of its content, which is required by the requested resource. """ http_status = 411 message = "Length Required" class PreconditionFailed(HTTPClientError): """HTTP 412 - Precondition Failed. The server does not meet one of the preconditions that the requester put on the request. """ http_status = 412 message = "Precondition Failed" class RequestEntityTooLarge(HTTPClientError): """HTTP 413 - Request Entity Too Large. The request is larger than the server is willing or able to process. """ http_status = 413 message = "Request Entity Too Large" def __init__(self, *args, **kwargs): try: self.retry_after = int(kwargs.pop('retry_after')) except (KeyError, ValueError): self.retry_after = 0 super(RequestEntityTooLarge, self).__init__(*args, **kwargs) class RequestUriTooLong(HTTPClientError): """HTTP 414 - Request-URI Too Long. The URI provided was too long for the server to process. """ http_status = 414 message = "Request-URI Too Long" class UnsupportedMediaType(HTTPClientError): """HTTP 415 - Unsupported Media Type. The request entity has a media type which the server or resource does not support. """ http_status = 415 message = "Unsupported Media Type" class RequestedRangeNotSatisfiable(HTTPClientError): """HTTP 416 - Requested Range Not Satisfiable. The client has asked for a portion of the file, but the server cannot supply that portion. """ http_status = 416 message = "Requested Range Not Satisfiable" class ExpectationFailed(HTTPClientError): """HTTP 417 - Expectation Failed. The server cannot meet the requirements of the Expect request-header field. """ http_status = 417 message = "Expectation Failed" class UnprocessableEntity(HTTPClientError): """HTTP 422 - Unprocessable Entity. The request was well-formed but was unable to be followed due to semantic errors. """ http_status = 422 message = "Unprocessable Entity" class InternalServerError(HttpServerError): """HTTP 500 - Internal Server Error. A generic error message, given when no more specific message is suitable. """ http_status = 500 message = "Internal Server Error" # NotImplemented is a python keyword. class HttpNotImplemented(HttpServerError): """HTTP 501 - Not Implemented. The server either does not recognize the request method, or it lacks the ability to fulfill the request. """ http_status = 501 message = "Not Implemented" class BadGateway(HttpServerError): """HTTP 502 - Bad Gateway. The server was acting as a gateway or proxy and received an invalid response from the upstream server. """ http_status = 502 message = "Bad Gateway" class ServiceUnavailable(HttpServerError): """HTTP 503 - Service Unavailable. The server is currently unavailable. """ http_status = 503 message = "Service Unavailable" class GatewayTimeout(HttpServerError): """HTTP 504 - Gateway Timeout. The server was acting as a gateway or proxy and did not receive a timely response from the upstream server. """ http_status = 504 message = "Gateway Timeout" class HttpVersionNotSupported(HttpServerError): """HTTP 505 - HttpVersion Not Supported. The server does not support the HTTP protocol version used in the request. """ http_status = 505 message = "HTTP Version Not Supported" # _code_map contains all the classes that have http_status attribute. _code_map = dict( (getattr(obj, 'http_status', None), obj) for name, obj in six.iteritems(vars(sys.modules[__name__])) if inspect.isclass(obj) and getattr(obj, 'http_status', False) ) def from_response(response, method, url): """Returns an instance of :class:`HttpError` or subclass based on response. :param response: instance of `requests.Response` class :param method: HTTP method used for request :param url: URL used for request """ kwargs = { "http_status": response.status_code, "response": response, "method": method, "url": url, "request_id": response.headers.get("x-compute-request-id"), } if "retry-after" in response.headers: kwargs["retry_after"] = response.headers["retry-after"] content_type = response.headers.get("Content-Type", "") if content_type.startswith("application/json"): try: body = response.json() except ValueError: pass else: if hasattr(body, "keys"): error = body[body.keys()[0]] kwargs["message"] = error.get("message") kwargs["details"] = error.get("details") elif content_type.startswith("text/"): kwargs["details"] = response.text try: cls = _code_map[response.status_code] except KeyError: if 500 <= response.status_code < 600: cls = HttpServerError elif 400 <= response.status_code < 500: cls = HTTPClientError else: cls = HttpError return cls(**kwargs) python-heatclient-0.2.8/heatclient/openstack/common/apiclient/__init__.py0000664000175400017540000000117312304731347030004 0ustar jenkinsjenkins00000000000000# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # 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. python-heatclient-0.2.8/heatclient/openstack/common/importutils.py0000664000175400017540000000421512304731347026670 0ustar jenkinsjenkins00000000000000# Copyright 2011 OpenStack Foundation. # All Rights Reserved. # # 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 related utilities and helper functions. """ import sys import traceback def import_class(import_str): """Returns a class from a string including module and class.""" mod_str, _sep, class_str = import_str.rpartition('.') try: __import__(mod_str) return getattr(sys.modules[mod_str], class_str) except (ValueError, AttributeError): raise ImportError('Class %s cannot be found (%s)' % (class_str, traceback.format_exception(*sys.exc_info()))) def import_object(import_str, *args, **kwargs): """Import a class and return an instance of it.""" return import_class(import_str)(*args, **kwargs) def import_object_ns(name_space, import_str, *args, **kwargs): """Tries to import object from default namespace. Imports a class and return an instance of it, first by trying to find the class in a default namespace, then failing back to a full path if not found in the default namespace. """ import_value = "%s.%s" % (name_space, import_str) try: return import_class(import_value)(*args, **kwargs) except ImportError: return import_class(import_str)(*args, **kwargs) def import_module(import_str): """Import a module.""" __import__(import_str) return sys.modules[import_str] def try_import(import_str, default=None): """Try to import a module and if it fails return default.""" try: return import_module(import_str) except ImportError: return default python-heatclient-0.2.8/heatclient/openstack/common/strutils.py0000664000175400017540000001654012304731347026172 0ustar jenkinsjenkins00000000000000# Copyright 2011 OpenStack Foundation. # All Rights Reserved. # # 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. """ System-level utilities and helper functions. """ import re import sys import unicodedata import six from heatclient.openstack.common.gettextutils import _ # noqa # Used for looking up extensions of text # to their 'multiplied' byte amount BYTE_MULTIPLIERS = { '': 1, 't': 1024 ** 4, 'g': 1024 ** 3, 'm': 1024 ** 2, 'k': 1024, } BYTE_REGEX = re.compile(r'(^-?\d+)(\D*)') TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes') FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no') SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]") SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+") def int_from_bool_as_string(subject): """Interpret a string as a boolean and return either 1 or 0. Any string value in: ('True', 'true', 'On', 'on', '1') is interpreted as a boolean True. Useful for JSON-decoded stuff and config file parsing """ return bool_from_string(subject) and 1 or 0 def bool_from_string(subject, strict=False): """Interpret a string as a boolean. A case-insensitive match is performed such that strings matching 't', 'true', 'on', 'y', 'yes', or '1' are considered True and, when `strict=False`, anything else is considered False. Useful for JSON-decoded stuff and config file parsing. If `strict=True`, unrecognized values, including None, will raise a ValueError which is useful when parsing values passed in from an API call. Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'. """ if not isinstance(subject, six.string_types): subject = str(subject) lowered = subject.strip().lower() if lowered in TRUE_STRINGS: return True elif lowered in FALSE_STRINGS: return False elif strict: acceptable = ', '.join( "'%s'" % s for s in sorted(TRUE_STRINGS + FALSE_STRINGS)) msg = _("Unrecognized value '%(val)s', acceptable values are:" " %(acceptable)s") % {'val': subject, 'acceptable': acceptable} raise ValueError(msg) else: return False def safe_decode(text, incoming=None, errors='strict'): """Decodes incoming str using `incoming` if they're not already unicode. :param incoming: Text's current encoding :param errors: Errors handling policy. See here for valid values http://docs.python.org/2/library/codecs.html :returns: text or a unicode `incoming` encoded representation of it. :raises TypeError: If text is not an instance of str """ if not isinstance(text, six.string_types): raise TypeError("%s can't be decoded" % type(text)) if isinstance(text, six.text_type): return text if not incoming: incoming = (sys.stdin.encoding or sys.getdefaultencoding()) try: return text.decode(incoming, errors) except UnicodeDecodeError: # Note(flaper87) If we get here, it means that # sys.stdin.encoding / sys.getdefaultencoding # didn't return a suitable encoding to decode # text. This happens mostly when global LANG # var is not set correctly and there's no # default encoding. In this case, most likely # python will use ASCII or ANSI encoders as # default encodings but they won't be capable # of decoding non-ASCII characters. # # Also, UTF-8 is being used since it's an ASCII # extension. return text.decode('utf-8', errors) def safe_encode(text, incoming=None, encoding='utf-8', errors='strict'): """Encodes incoming str/unicode using `encoding`. If incoming is not specified, text is expected to be encoded with current python's default encoding. (`sys.getdefaultencoding`) :param incoming: Text's current encoding :param encoding: Expected encoding for text (Default UTF-8) :param errors: Errors handling policy. See here for valid values http://docs.python.org/2/library/codecs.html :returns: text or a bytestring `encoding` encoded representation of it. :raises TypeError: If text is not an instance of str """ if not isinstance(text, six.string_types): raise TypeError("%s can't be encoded" % type(text)) if not incoming: incoming = (sys.stdin.encoding or sys.getdefaultencoding()) if isinstance(text, six.text_type): if six.PY3: return text.encode(encoding, errors).decode(incoming) else: return text.encode(encoding, errors) elif text and encoding != incoming: # Decode text before encoding it with `encoding` text = safe_decode(text, incoming, errors) if six.PY3: return text.encode(encoding, errors).decode(incoming) else: return text.encode(encoding, errors) return text def to_bytes(text, default=0): """Converts a string into an integer of bytes. Looks at the last characters of the text to determine what conversion is needed to turn the input text into a byte number. Supports "B, K(B), M(B), G(B), and T(B)". (case insensitive) :param text: String input for bytes size conversion. :param default: Default return value when text is blank. """ match = BYTE_REGEX.search(text) if match: magnitude = int(match.group(1)) mult_key_org = match.group(2) if not mult_key_org: return magnitude elif text: msg = _('Invalid string format: %s') % text raise TypeError(msg) else: return default mult_key = mult_key_org.lower().replace('b', '', 1) multiplier = BYTE_MULTIPLIERS.get(mult_key) if multiplier is None: msg = _('Unknown byte multiplier: %s') % mult_key_org raise TypeError(msg) return magnitude * multiplier def to_slug(value, incoming=None, errors="strict"): """Normalize string. Convert to lowercase, remove non-word characters, and convert spaces to hyphens. Inspired by Django's `slugify` filter. :param value: Text to slugify :param incoming: Text's current encoding :param errors: Errors handling policy. See here for valid values http://docs.python.org/2/library/codecs.html :returns: slugified unicode representation of `value` :raises TypeError: If text is not an instance of str """ value = safe_decode(value, incoming, errors) # NOTE(aababilov): no need to use safe_(encode|decode) here: # encodings are always "ascii", error handling is always "ignore" # and types are always known (first: unicode; second: str) value = unicodedata.normalize("NFKD", value).encode( "ascii", "ignore").decode("ascii") value = SLUGIFY_STRIP_RE.sub("", value).strip().lower() return SLUGIFY_HYPHENATE_RE.sub("-", value) python-heatclient-0.2.8/heatclient/openstack/common/py3kcompat/0000775000175400017540000000000012304731421026004 5ustar jenkinsjenkins00000000000000python-heatclient-0.2.8/heatclient/openstack/common/py3kcompat/urlutils.py0000664000175400017540000000325512304731347030255 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2013 Canonical Ltd. # All Rights Reserved. # # 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. # """ Python2/Python3 compatibility layer for OpenStack """ import six if six.PY3: # python3 import urllib.error import urllib.parse import urllib.request urlencode = urllib.parse.urlencode urljoin = urllib.parse.urljoin quote = urllib.parse.quote parse_qsl = urllib.parse.parse_qsl unquote = urllib.parse.unquote urlparse = urllib.parse.urlparse urlsplit = urllib.parse.urlsplit urlunsplit = urllib.parse.urlunsplit urlopen = urllib.request.urlopen URLError = urllib.error.URLError pathname2url = urllib.request.pathname2url else: # python2 import urllib import urllib2 import urlparse urlencode = urllib.urlencode quote = urllib.quote unquote = urllib.unquote parse = urlparse parse_qsl = parse.parse_qsl urljoin = parse.urljoin urlparse = parse.urlparse urlsplit = parse.urlsplit urlunsplit = parse.urlunsplit urlopen = urllib2.urlopen URLError = urllib2.URLError pathname2url = urllib.pathname2url python-heatclient-0.2.8/heatclient/openstack/common/py3kcompat/__init__.py0000664000175400017540000000124512304731347030126 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2013 Canonical Ltd. # All Rights Reserved. # # 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. # python-heatclient-0.2.8/heatclient/openstack/common/timeutils.py0000664000175400017540000001406412304731347026317 0ustar jenkinsjenkins00000000000000# Copyright 2011 OpenStack Foundation. # All Rights Reserved. # # 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. """ Time related utilities and helper functions. """ import calendar import datetime import time import iso8601 import six # ISO 8601 extended time format with microseconds _ISO8601_TIME_FORMAT_SUBSECOND = '%Y-%m-%dT%H:%M:%S.%f' _ISO8601_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S' PERFECT_TIME_FORMAT = _ISO8601_TIME_FORMAT_SUBSECOND def isotime(at=None, subsecond=False): """Stringify time in ISO 8601 format.""" if not at: at = utcnow() st = at.strftime(_ISO8601_TIME_FORMAT if not subsecond else _ISO8601_TIME_FORMAT_SUBSECOND) tz = at.tzinfo.tzname(None) if at.tzinfo else 'UTC' st += ('Z' if tz == 'UTC' else tz) return st def parse_isotime(timestr): """Parse time from ISO 8601 format.""" try: return iso8601.parse_date(timestr) except iso8601.ParseError as e: raise ValueError(six.text_type(e)) except TypeError as e: raise ValueError(six.text_type(e)) def strtime(at=None, fmt=PERFECT_TIME_FORMAT): """Returns formatted utcnow.""" if not at: at = utcnow() return at.strftime(fmt) def parse_strtime(timestr, fmt=PERFECT_TIME_FORMAT): """Turn a formatted time back into a datetime.""" return datetime.datetime.strptime(timestr, fmt) def normalize_time(timestamp): """Normalize time in arbitrary timezone to UTC naive object.""" offset = timestamp.utcoffset() if offset is None: return timestamp return timestamp.replace(tzinfo=None) - offset def is_older_than(before, seconds): """Return True if before is older than seconds.""" if isinstance(before, six.string_types): before = parse_strtime(before).replace(tzinfo=None) return utcnow() - before > datetime.timedelta(seconds=seconds) def is_newer_than(after, seconds): """Return True if after is newer than seconds.""" if isinstance(after, six.string_types): after = parse_strtime(after).replace(tzinfo=None) return after - utcnow() > datetime.timedelta(seconds=seconds) def utcnow_ts(): """Timestamp version of our utcnow function.""" if utcnow.override_time is None: # NOTE(kgriffs): This is several times faster # than going through calendar.timegm(...) return int(time.time()) return calendar.timegm(utcnow().timetuple()) def utcnow(): """Overridable version of utils.utcnow.""" if utcnow.override_time: try: return utcnow.override_time.pop(0) except AttributeError: return utcnow.override_time return datetime.datetime.utcnow() def iso8601_from_timestamp(timestamp): """Returns a iso8601 formated date from timestamp.""" return isotime(datetime.datetime.utcfromtimestamp(timestamp)) utcnow.override_time = None def set_time_override(override_time=None): """Overrides utils.utcnow. Make it return a constant time or a list thereof, one at a time. :param override_time: datetime instance or list thereof. If not given, defaults to the current UTC time. """ utcnow.override_time = override_time or datetime.datetime.utcnow() def advance_time_delta(timedelta): """Advance overridden time using a datetime.timedelta.""" assert(not utcnow.override_time is None) try: for dt in utcnow.override_time: dt += timedelta except TypeError: utcnow.override_time += timedelta def advance_time_seconds(seconds): """Advance overridden time by seconds.""" advance_time_delta(datetime.timedelta(0, seconds)) def clear_time_override(): """Remove the overridden time.""" utcnow.override_time = None def marshall_now(now=None): """Make an rpc-safe datetime with microseconds. Note: tzinfo is stripped, but not required for relative times. """ if not now: now = utcnow() return dict(day=now.day, month=now.month, year=now.year, hour=now.hour, minute=now.minute, second=now.second, microsecond=now.microsecond) def unmarshall_time(tyme): """Unmarshall a datetime dict.""" return datetime.datetime(day=tyme['day'], month=tyme['month'], year=tyme['year'], hour=tyme['hour'], minute=tyme['minute'], second=tyme['second'], microsecond=tyme['microsecond']) def delta_seconds(before, after): """Return the difference between two timing objects. Compute the difference in seconds between two date, time, or datetime objects (as a float, to microsecond resolution). """ delta = after - before return total_seconds(delta) def total_seconds(delta): """Return the total seconds of datetime.timedelta object. Compute total seconds of datetime.timedelta, datetime.timedelta doesn't have method total_seconds in Python2.6, calculate it manually. """ try: return delta.total_seconds() except AttributeError: return ((delta.days * 24 * 3600) + delta.seconds + float(delta.microseconds) / (10 ** 6)) def is_soon(dt, window): """Determines if time is going to happen in the next window seconds. :params dt: the time :params window: minimum seconds to remain to consider the time not soon :return: True if expiration is within the given duration """ soon = (utcnow() + datetime.timedelta(seconds=window)) return normalize_time(dt) <= soon python-heatclient-0.2.8/heatclient/openstack/common/jsonutils.py0000664000175400017540000001510312304731347026325 0ustar jenkinsjenkins00000000000000# Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # Copyright 2011 Justin Santa Barbara # All Rights Reserved. # # 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. ''' JSON related utilities. This module provides a few things: 1) A handy function for getting an object down to something that can be JSON serialized. See to_primitive(). 2) Wrappers around loads() and dumps(). The dumps() wrapper will automatically use to_primitive() for you if needed. 3) This sets up anyjson to use the loads() and dumps() wrappers if anyjson is available. ''' import datetime import functools import inspect import itertools import json try: import xmlrpclib except ImportError: # NOTE(jaypipes): xmlrpclib was renamed to xmlrpc.client in Python3 # however the function and object call signatures # remained the same. This whole try/except block should # be removed and replaced with a call to six.moves once # six 1.4.2 is released. See http://bit.ly/1bqrVzu import xmlrpc.client as xmlrpclib import six from heatclient.openstack.common import gettextutils from heatclient.openstack.common import importutils from heatclient.openstack.common import timeutils netaddr = importutils.try_import("netaddr") _nasty_type_tests = [inspect.ismodule, inspect.isclass, inspect.ismethod, inspect.isfunction, inspect.isgeneratorfunction, inspect.isgenerator, inspect.istraceback, inspect.isframe, inspect.iscode, inspect.isbuiltin, inspect.isroutine, inspect.isabstract] _simple_types = (six.string_types + six.integer_types + (type(None), bool, float)) def to_primitive(value, convert_instances=False, convert_datetime=True, level=0, max_depth=3): """Convert a complex object into primitives. Handy for JSON serialization. We can optionally handle instances, but since this is a recursive function, we could have cyclical data structures. To handle cyclical data structures we could track the actual objects visited in a set, but not all objects are hashable. Instead we just track the depth of the object inspections and don't go too deep. Therefore, convert_instances=True is lossy ... be aware. """ # handle obvious types first - order of basic types determined by running # full tests on nova project, resulting in the following counts: # 572754 # 460353 # 379632 # 274610 # 199918 # 114200 # 51817 # 26164 # 6491 # 283 # 19 if isinstance(value, _simple_types): return value if isinstance(value, datetime.datetime): if convert_datetime: return timeutils.strtime(value) else: return value # value of itertools.count doesn't get caught by nasty_type_tests # and results in infinite loop when list(value) is called. if type(value) == itertools.count: return six.text_type(value) # FIXME(vish): Workaround for LP bug 852095. Without this workaround, # tests that raise an exception in a mocked method that # has a @wrap_exception with a notifier will fail. If # we up the dependency to 0.5.4 (when it is released) we # can remove this workaround. if getattr(value, '__module__', None) == 'mox': return 'mock' if level > max_depth: return '?' # The try block may not be necessary after the class check above, # but just in case ... try: recursive = functools.partial(to_primitive, convert_instances=convert_instances, convert_datetime=convert_datetime, level=level, max_depth=max_depth) if isinstance(value, dict): return dict((k, recursive(v)) for k, v in six.iteritems(value)) elif isinstance(value, (list, tuple)): return [recursive(lv) for lv in value] # It's not clear why xmlrpclib created their own DateTime type, but # for our purposes, make it a datetime type which is explicitly # handled if isinstance(value, xmlrpclib.DateTime): value = datetime.datetime(*tuple(value.timetuple())[:6]) if convert_datetime and isinstance(value, datetime.datetime): return timeutils.strtime(value) elif isinstance(value, gettextutils.Message): return value.data elif hasattr(value, 'iteritems'): return recursive(dict(value.iteritems()), level=level + 1) elif hasattr(value, '__iter__'): return recursive(list(value)) elif convert_instances and hasattr(value, '__dict__'): # Likely an instance of something. Watch for cycles. # Ignore class member vars. return recursive(value.__dict__, level=level + 1) elif netaddr and isinstance(value, netaddr.IPAddress): return six.text_type(value) else: if any(test(value) for test in _nasty_type_tests): return six.text_type(value) return value except TypeError: # Class objects are tricky since they may define something like # __iter__ defined but it isn't callable as list(). return six.text_type(value) def dumps(value, default=to_primitive, **kwargs): return json.dumps(value, default=default, **kwargs) def loads(s): return json.loads(s) def load(s): return json.load(s) try: import anyjson except ImportError: pass else: anyjson._modules.append((__name__, 'dumps', TypeError, 'loads', ValueError, 'load')) anyjson.force_implementation(__name__) python-heatclient-0.2.8/heatclient/openstack/common/gettextutils.py0000664000175400017540000003157212304731347027050 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 Red Hat, Inc. # Copyright 2013 IBM Corp. # All Rights Reserved. # # 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. """ gettext for openstack-common modules. Usual usage in an openstack.common module: from heatclient.openstack.common.gettextutils import _ """ import copy import gettext import logging import os import re try: import UserString as _userString except ImportError: import collections as _userString from babel import localedata import six _localedir = os.environ.get('heatclient'.upper() + '_LOCALEDIR') _t = gettext.translation('heatclient', localedir=_localedir, fallback=True) _AVAILABLE_LANGUAGES = {} USE_LAZY = False def enable_lazy(): """Convenience function for configuring _() to use lazy gettext Call this at the start of execution to enable the gettextutils._ function to use lazy gettext functionality. This is useful if your project is importing _ directly instead of using the gettextutils.install() way of importing the _ function. """ global USE_LAZY USE_LAZY = True def _(msg): if USE_LAZY: return Message(msg, 'heatclient') else: if six.PY3: return _t.gettext(msg) return _t.ugettext(msg) def install(domain, lazy=False): """Install a _() function using the given translation domain. Given a translation domain, install a _() function using gettext's install() function. The main difference from gettext.install() is that we allow overriding the default localedir (e.g. /usr/share/locale) using a translation-domain-specific environment variable (e.g. NOVA_LOCALEDIR). :param domain: the translation domain :param lazy: indicates whether or not to install the lazy _() function. The lazy _() introduces a way to do deferred translation of messages by installing a _ that builds Message objects, instead of strings, which can then be lazily translated into any available locale. """ if lazy: # NOTE(mrodden): Lazy gettext functionality. # # The following introduces a deferred way to do translations on # messages in OpenStack. We override the standard _() function # and % (format string) operation to build Message objects that can # later be translated when we have more information. # # Also included below is an example LocaleHandler that translates # Messages to an associated locale, effectively allowing many logs, # each with their own locale. def _lazy_gettext(msg): """Create and return a Message object. Lazy gettext function for a given domain, it is a factory method for a project/module to get a lazy gettext function for its own translation domain (i.e. nova, glance, cinder, etc.) Message encapsulates a string so that we can translate it later when needed. """ return Message(msg, domain) from six import moves moves.builtins.__dict__['_'] = _lazy_gettext else: localedir = '%s_LOCALEDIR' % domain.upper() if six.PY3: gettext.install(domain, localedir=os.environ.get(localedir)) else: gettext.install(domain, localedir=os.environ.get(localedir), unicode=True) class Message(_userString.UserString, object): """Class used to encapsulate translatable messages.""" def __init__(self, msg, domain): # _msg is the gettext msgid and should never change self._msg = msg self._left_extra_msg = '' self._right_extra_msg = '' self._locale = None self.params = None self.domain = domain @property def data(self): # NOTE(mrodden): this should always resolve to a unicode string # that best represents the state of the message currently localedir = os.environ.get(self.domain.upper() + '_LOCALEDIR') if self.locale: lang = gettext.translation(self.domain, localedir=localedir, languages=[self.locale], fallback=True) else: # use system locale for translations lang = gettext.translation(self.domain, localedir=localedir, fallback=True) if six.PY3: ugettext = lang.gettext else: ugettext = lang.ugettext full_msg = (self._left_extra_msg + ugettext(self._msg) + self._right_extra_msg) if self.params is not None: full_msg = full_msg % self.params return six.text_type(full_msg) @property def locale(self): return self._locale @locale.setter def locale(self, value): self._locale = value if not self.params: return # This Message object may have been constructed with one or more # Message objects as substitution parameters, given as a single # Message, or a tuple or Map containing some, so when setting the # locale for this Message we need to set it for those Messages too. if isinstance(self.params, Message): self.params.locale = value return if isinstance(self.params, tuple): for param in self.params: if isinstance(param, Message): param.locale = value return if isinstance(self.params, dict): for param in self.params.values(): if isinstance(param, Message): param.locale = value def _save_dictionary_parameter(self, dict_param): full_msg = self.data # look for %(blah) fields in string; # ignore %% and deal with the # case where % is first character on the line keys = re.findall('(?:[^%]|^)?%\((\w*)\)[a-z]', full_msg) # if we don't find any %(blah) blocks but have a %s if not keys and re.findall('(?:[^%]|^)%[a-z]', full_msg): # apparently the full dictionary is the parameter params = copy.deepcopy(dict_param) else: params = {} for key in keys: try: params[key] = copy.deepcopy(dict_param[key]) except TypeError: # cast uncopyable thing to unicode string params[key] = six.text_type(dict_param[key]) return params def _save_parameters(self, other): # we check for None later to see if # we actually have parameters to inject, # so encapsulate if our parameter is actually None if other is None: self.params = (other, ) elif isinstance(other, dict): self.params = self._save_dictionary_parameter(other) else: # fallback to casting to unicode, # this will handle the problematic python code-like # objects that cannot be deep-copied try: self.params = copy.deepcopy(other) except TypeError: self.params = six.text_type(other) return self # overrides to be more string-like def __unicode__(self): return self.data def __str__(self): if six.PY3: return self.__unicode__() return self.data.encode('utf-8') def __getstate__(self): to_copy = ['_msg', '_right_extra_msg', '_left_extra_msg', 'domain', 'params', '_locale'] new_dict = self.__dict__.fromkeys(to_copy) for attr in to_copy: new_dict[attr] = copy.deepcopy(self.__dict__[attr]) return new_dict def __setstate__(self, state): for (k, v) in state.items(): setattr(self, k, v) # operator overloads def __add__(self, other): copied = copy.deepcopy(self) copied._right_extra_msg += other.__str__() return copied def __radd__(self, other): copied = copy.deepcopy(self) copied._left_extra_msg += other.__str__() return copied def __mod__(self, other): # do a format string to catch and raise # any possible KeyErrors from missing parameters self.data % other copied = copy.deepcopy(self) return copied._save_parameters(other) def __mul__(self, other): return self.data * other def __rmul__(self, other): return other * self.data def __getitem__(self, key): return self.data[key] def __getslice__(self, start, end): return self.data.__getslice__(start, end) def __getattribute__(self, name): # NOTE(mrodden): handle lossy operations that we can't deal with yet # These override the UserString implementation, since UserString # uses our __class__ attribute to try and build a new message # after running the inner data string through the operation. # At that point, we have lost the gettext message id and can just # safely resolve to a string instead. ops = ['capitalize', 'center', 'decode', 'encode', 'expandtabs', 'ljust', 'lstrip', 'replace', 'rjust', 'rstrip', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill'] if name in ops: return getattr(self.data, name) else: return _userString.UserString.__getattribute__(self, name) def get_available_languages(domain): """Lists the available languages for the given translation domain. :param domain: the domain to get languages for """ if domain in _AVAILABLE_LANGUAGES: return copy.copy(_AVAILABLE_LANGUAGES[domain]) localedir = '%s_LOCALEDIR' % domain.upper() find = lambda x: gettext.find(domain, localedir=os.environ.get(localedir), languages=[x]) # NOTE(mrodden): en_US should always be available (and first in case # order matters) since our in-line message strings are en_US language_list = ['en_US'] # NOTE(luisg): Babel <1.0 used a function called list(), which was # renamed to locale_identifiers() in >=1.0, the requirements master list # requires >=0.9.6, uncapped, so defensively work with both. We can remove # this check when the master list updates to >=1.0, and update all projects list_identifiers = (getattr(localedata, 'list', None) or getattr(localedata, 'locale_identifiers')) locale_identifiers = list_identifiers() for i in locale_identifiers: if find(i) is not None: language_list.append(i) _AVAILABLE_LANGUAGES[domain] = language_list return copy.copy(language_list) def get_localized_message(message, user_locale): """Gets a localized version of the given message in the given locale. If the message is not a Message object the message is returned as-is. If the locale is None the message is translated to the default locale. :returns: the translated message in unicode, or the original message if it could not be translated """ translated = message if isinstance(message, Message): original_locale = message.locale message.locale = user_locale translated = six.text_type(message) message.locale = original_locale return translated class LocaleHandler(logging.Handler): """Handler that can have a locale associated to translate Messages. A quick example of how to utilize the Message class above. LocaleHandler takes a locale and a target logging.Handler object to forward LogRecord objects to after translating the internal Message. """ def __init__(self, locale, target): """Initialize a LocaleHandler :param locale: locale to use for translating messages :param target: logging.Handler object to forward LogRecord objects to after translation """ logging.Handler.__init__(self) self.locale = locale self.target = target def emit(self, record): if isinstance(record.msg, Message): # set the locale and resolve to a string record.msg.locale = self.locale self.target.emit(record) python-heatclient-0.2.8/heatclient/openstack/common/__init__.py0000664000175400017540000000000012304731347026020 0ustar jenkinsjenkins00000000000000python-heatclient-0.2.8/heatclient/openstack/__init__.py0000664000175400017540000000000012304731347024530 0ustar jenkinsjenkins00000000000000python-heatclient-0.2.8/heatclient/exc.py0000664000175400017540000001023212304731347021571 0ustar jenkinsjenkins00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import sys from heatclient.openstack.common import jsonutils verbose = 0 class BaseException(Exception): """An error occurred.""" def __init__(self, message=None): self.message = message def __str__(self): return self.message or self.__class__.__doc__ class CommandError(BaseException): """Invalid usage of CLI.""" class InvalidEndpoint(BaseException): """The provided endpoint is invalid.""" class CommunicationError(BaseException): """Unable to communicate with server.""" class HTTPException(BaseException): """Base exception for all HTTP-derived exceptions.""" code = 'N/A' def __init__(self, message=None): super(HTTPException, self).__init__(message) try: self.error = jsonutils.loads(message) if 'error' not in self.error: raise KeyError('Key "error" not exists') except KeyError: # NOTE(jianingy): If key 'error' happens not exist, # self.message becomes no sense. In this case, we # return doc of current exception class instead. self.error = {'error': {'message': self.__class__.__doc__}} except Exception: self.error = {'error': {'message': self.message or self.__class__.__doc__}} def __str__(self): message = self.error['error'].get('message', 'Internal Error') if verbose: traceback = self.error['error'].get('traceback', '') return 'ERROR: %s\n%s' % (message, traceback) else: return 'ERROR: %s' % message class HTTPMultipleChoices(HTTPException): code = 300 def __str__(self): self.details = ("Requested version of Heat API is not" "available.") return "%s (HTTP %s) %s" % (self.__class__.__name__, self.code, self.details) class BadRequest(HTTPException): """DEPRECATED.""" code = 400 class HTTPBadRequest(BadRequest): pass class Unauthorized(HTTPException): """DEPRECATED.""" code = 401 class HTTPUnauthorized(Unauthorized): pass class Forbidden(HTTPException): """DEPRECATED.""" code = 403 class HTTPForbidden(Forbidden): pass class NotFound(HTTPException): """DEPRECATED.""" code = 404 class HTTPNotFound(NotFound): pass class HTTPMethodNotAllowed(HTTPException): code = 405 class Conflict(HTTPException): """DEPRECATED.""" code = 409 class HTTPConflict(Conflict): pass class OverLimit(HTTPException): """DEPRECATED.""" code = 413 class HTTPOverLimit(OverLimit): pass class HTTPUnsupported(HTTPException): code = 415 class HTTPInternalServerError(HTTPException): code = 500 class HTTPNotImplemented(HTTPException): code = 501 class HTTPBadGateway(HTTPException): code = 502 class ServiceUnavailable(HTTPException): """DEPRECATED.""" code = 503 class HTTPServiceUnavailable(ServiceUnavailable): pass #NOTE(bcwaldon): Build a mapping of HTTP codes to corresponding exception # classes _code_map = {} for obj_name in dir(sys.modules[__name__]): if obj_name.startswith('HTTP'): obj = getattr(sys.modules[__name__], obj_name) _code_map[obj.code] = obj def from_response(response): """Return an instance of an HTTPException based on requests response.""" cls = _code_map.get(response.status_code, HTTPException) return cls(response.content) class NoTokenLookupException(Exception): """DEPRECATED.""" pass class EndpointNotFound(Exception): """DEPRECATED.""" pass python-heatclient-0.2.8/heatclient/client.py0000664000175400017540000000143512304731347022275 0ustar jenkinsjenkins00000000000000# 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 heatclient.common import utils def Client(version, *args, **kwargs): module = utils.import_versioned_module(version, 'client') client_class = getattr(module, 'Client') return client_class(*args, **kwargs) python-heatclient-0.2.8/heatclient/__init__.py0000664000175400017540000000122412304731347022552 0ustar jenkinsjenkins00000000000000# 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 pbr.version __version__ = pbr.version.VersionInfo('python-heatclient').version_string() python-heatclient-0.2.8/heatclient/v1/0000775000175400017540000000000012304731421020761 5ustar jenkinsjenkins00000000000000python-heatclient-0.2.8/heatclient/v1/build_info.py0000664000175400017540000000203412304731347023453 0ustar jenkinsjenkins00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # 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 heatclient.openstack.common.apiclient import base class BuildInfo(base.Resource): def __repr__(self): return "" % self._info def build_info(self): return self.manager.build_info() class BuildInfoManager(base.BaseManager): resource_class = BuildInfo def build_info(self): resp, body = self.client.json_request('GET', '/build_info') return body python-heatclient-0.2.8/heatclient/v1/shell.py0000664000175400017540000005746712304731350022466 0ustar jenkinsjenkins00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # 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 yaml from heatclient.common import template_utils from heatclient.common import utils from heatclient.openstack.common import jsonutils from heatclient.openstack.common.py3kcompat import urlutils import heatclient.exc as exc @utils.arg('-f', '--template-file', metavar='', help='Path to the template.') @utils.arg('-e', '--environment-file', metavar='', help='Path to the environment.') @utils.arg('-u', '--template-url', metavar='', help='URL of template.') @utils.arg('-o', '--template-object', metavar='', help='URL to retrieve template object (e.g. from swift).') @utils.arg('-c', '--create-timeout', metavar='', default=60, type=int, help='Stack creation timeout in minutes. Default: 60.') @utils.arg('-r', '--enable-rollback', default=False, action="store_true", help='Enable rollback on create/update failure.') @utils.arg('-P', '--parameters', metavar='', help='Parameter values used to create the stack. ' 'This can be specified multiple times, or once with parameters ' 'separated by a semicolon.', action='append') @utils.arg('name', metavar='', help='Name of the stack to create.') def do_create(hc, args): '''DEPRECATED! Use stack-create instead.''' do_stack_create(hc, args) @utils.arg('-f', '--template-file', metavar='', help='Path to the template.') @utils.arg('-e', '--environment-file', metavar='', help='Path to the environment.') @utils.arg('-u', '--template-url', metavar='', help='URL of template.') @utils.arg('-o', '--template-object', metavar='', help='URL to retrieve template object (e.g. from swift).') @utils.arg('-c', '--create-timeout', metavar='', default=60, type=int, help='Stack creation timeout in minutes. Default: 60.') @utils.arg('-r', '--enable-rollback', default=False, action="store_true", help='Enable rollback on create/update failure.') @utils.arg('-P', '--parameters', metavar='', help='Parameter values used to create the stack. ' 'This can be specified multiple times, or once with parameters ' 'separated by a semicolon.', action='append') @utils.arg('name', metavar='', help='Name of the stack to create.') def do_stack_create(hc, args): '''Create the stack.''' tpl_files, template = template_utils.get_template_contents( args.template_file, args.template_url, args.template_object, hc.http_client.raw_request) env_files, env = template_utils.process_environment_and_files( env_path=args.environment_file) fields = { 'stack_name': args.name, 'timeout_mins': args.create_timeout, 'disable_rollback': not(args.enable_rollback), 'parameters': utils.format_parameters(args.parameters), 'template': template, 'files': dict(list(tpl_files.items()) + list(env_files.items())), 'environment': env } hc.stacks.create(**fields) do_stack_list(hc) @utils.arg('-f', '--template-file', metavar='', help='Path to the template.') @utils.arg('-e', '--environment-file', metavar='', help='Path to the environment.') @utils.arg('-u', '--template-url', metavar='', help='URL of template.') @utils.arg('-o', '--template-object', metavar='', help='URL to retrieve template object (e.g from swift).') @utils.arg('-c', '--create-timeout', metavar='', default=60, type=int, help='Stack creation timeout in minutes. Default: 60.') @utils.arg('-a', '--adopt-file', metavar='', help='Path to adopt stack data file.') @utils.arg('-r', '--enable-rollback', default=False, action="store_true", help='Enable rollback on create/update failure.') @utils.arg('-P', '--parameters', metavar='', help='Parameter values used to create the stack. ' 'This can be specified multiple times, or once with parameters ' 'separated by a semicolon.', action='append') @utils.arg('name', metavar='', help='Name of the stack to adopt.') def do_stack_adopt(hc, args): '''Adopt a stack.''' tpl_files, template = template_utils.get_template_contents( args.template_file, args.template_url, args.template_object, hc.http_client.raw_request) env_files, env = template_utils.process_environment_and_files( env_path=args.environment_file) if not args.adopt_file: raise exc.CommandError('Need to specify --adopt-file') adopt_url = template_utils.normalise_file_path_to_url(args.adopt_file) adopt_data = urlutils.urlopen(adopt_url).read() fields = { 'stack_name': args.name, 'timeout_mins': args.create_timeout, 'disable_rollback': not(args.enable_rollback), 'adopt_stack_data': adopt_data, 'parameters': utils.format_parameters(args.parameters), 'template': template, 'files': dict(list(tpl_files.items()) + list(env_files.items())), 'environment': env } hc.stacks.create(**fields) do_stack_list(hc) @utils.arg('id', metavar='', nargs='+', help='Name or ID of stack(s) to delete.') def do_delete(hc, args): '''DEPRECATED! Use stack-delete instead.''' do_stack_delete(hc, args) @utils.arg('id', metavar='', nargs='+', help='Name or ID of stack(s) to delete.') def do_stack_delete(hc, args): '''Delete the stack(s).''' failure_count = 0 for sid in args.id: fields = {'stack_id': sid} try: hc.stacks.delete(**fields) except exc.HTTPNotFound as e: failure_count += 1 print(e) if failure_count == len(args.id): raise exc.CommandError("Unable to delete any of the specified " "stacks.") do_stack_list(hc) @utils.arg('id', metavar='', help='Name or ID of stack to abandon.') def do_stack_abandon(hc, args): '''Abandon the stack.''' fields = {'stack_id': args.id} try: stack = hc.stacks.abandon(**fields) except exc.HTTPNotFound: raise exc.CommandError('Stack not found: %s' % args.id) else: print(jsonutils.dumps(stack, indent=2)) @utils.arg('id', metavar='', help='Name or ID of stack to suspend.') def do_action_suspend(hc, args): '''Suspend the stack.''' fields = {'stack_id': args.id} try: hc.actions.suspend(**fields) except exc.HTTPNotFound: raise exc.CommandError('Stack not found: %s' % args.id) else: do_stack_list(hc) @utils.arg('id', metavar='', help='Name or ID of stack to resume.') def do_action_resume(hc, args): '''Resume the stack.''' fields = {'stack_id': args.id} try: hc.actions.resume(**fields) except exc.HTTPNotFound: raise exc.CommandError('Stack not found: %s' % args.id) else: do_stack_list(hc) @utils.arg('id', metavar='', help='Name or ID of stack to describe.') def do_describe(hc, args): '''DEPRECATED! Use stack-show instead.''' do_stack_show(hc, args) @utils.arg('id', metavar='', help='Name or ID of stack to describe.') def do_stack_show(hc, args): '''Describe the stack.''' fields = {'stack_id': args.id} try: stack = hc.stacks.get(**fields) except exc.HTTPNotFound: raise exc.CommandError('Stack not found: %s' % args.id) else: formatters = { 'description': utils.text_wrap_formatter, 'template_description': utils.text_wrap_formatter, 'stack_status_reason': utils.text_wrap_formatter, 'parameters': utils.json_formatter, 'outputs': utils.json_formatter, 'links': utils.link_formatter } utils.print_dict(stack.to_dict(), formatters=formatters) @utils.arg('-f', '--template-file', metavar='', help='Path to the template.') @utils.arg('-e', '--environment-file', metavar='', help='Path to the environment.') @utils.arg('-u', '--template-url', metavar='', help='URL of template.') @utils.arg('-o', '--template-object', metavar='', help='URL to retrieve template object (e.g. from swift).') @utils.arg('-P', '--parameters', metavar='', help='Parameter values used to create the stack. ' 'This can be specified multiple times, or once with parameters ' 'separated by a semicolon.', action='append') @utils.arg('id', metavar='', help='Name or ID of stack to update.') def do_update(hc, args): '''DEPRECATED! Use stack-update instead.''' do_stack_update(hc, args) @utils.arg('-f', '--template-file', metavar='', help='Path to the template.') @utils.arg('-e', '--environment-file', metavar='', help='Path to the environment.') @utils.arg('-u', '--template-url', metavar='', help='URL of template.') @utils.arg('-o', '--template-object', metavar='', help='URL to retrieve template object (e.g. from swift).') @utils.arg('-P', '--parameters', metavar='', help='Parameter values used to create the stack. ' 'This can be specified multiple times, or once with parameters ' 'separated by a semicolon.', action='append') @utils.arg('id', metavar='', help='Name or ID of stack to update.') def do_stack_update(hc, args): '''Update the stack.''' tpl_files, template = template_utils.get_template_contents( args.template_file, args.template_url, args.template_object, hc.http_client.raw_request) env_files, env = template_utils.process_environment_and_files( env_path=args.environment_file) fields = { 'stack_id': args.id, 'parameters': utils.format_parameters(args.parameters), 'template': template, 'files': dict(list(tpl_files.items()) + list(env_files.items())), 'environment': env } hc.stacks.update(**fields) do_stack_list(hc) def do_list(hc, args=None): '''DEPRECATED! Use stack-list instead.''' do_stack_list(hc) @utils.arg('-f', '--filters', metavar='', help='Filter parameters to apply on returned stacks. ' 'This can be specified multiple times, or once with parameters ' 'separated by a semicolon.', action='append') @utils.arg('-l', '--limit', metavar='', help='Limit the number of stacks returned.') @utils.arg('-m', '--marker', metavar='', help='Only return stacks that appear after the given stack ID.') def do_stack_list(hc, args=None): '''List the user's stacks.''' kwargs = {} if args: kwargs = {'limit': args.limit, 'marker': args.marker, 'filters': utils.format_parameters(args.filters)} stacks = hc.stacks.list(**kwargs) fields = ['id', 'stack_name', 'stack_status', 'creation_time'] utils.print_list(stacks, fields, sortby=3) @utils.arg('id', metavar='', help='Name or ID of stack to query.') def do_output_list(hc, args): '''Show available outputs.''' try: stack = hc.stacks.get(stack_id=args.id) except exc.HTTPNotFound: raise exc.CommandError('Stack not found: %s' % args.id) else: outputs = stack.to_dict()['outputs'] fields = ['output_key', 'description'] formatters = { 'output_key': lambda x: x['output_key'], 'description': lambda x: x['description'], } utils.print_list(outputs, fields, formatters=formatters) @utils.arg('id', metavar='', help='Name or ID of stack to query.') @utils.arg('output', metavar='', help='Name of an output to display.') def do_output_show(hc, args): '''Show a specific stack output.''' try: stack = hc.stacks.get(stack_id=args.id) except exc.HTTPNotFound: raise exc.CommandError('Stack not found: %s' % args.id) else: for output in stack.to_dict().get('outputs', []): if output['output_key'] == args.output: value = output['output_value'] break else: return print (jsonutils.dumps(value, indent=2)) def do_resource_type_list(hc, args={}): '''List the available resource types.''' kwargs = {} types = hc.resource_types.list(**kwargs) utils.print_list(types, ['resource_type'], sortby=0) @utils.arg('resource_type', metavar='', help='Resource type to get the details for.') def do_resource_type_show(hc, args={}): '''Show the resource type.''' try: resource_type = hc.resource_types.get(args.resource_type) except exc.HTTPNotFound: raise exc.CommandError( 'Resource Type not found: %s' % args.resource_type) else: print(jsonutils.dumps(resource_type, indent=2)) @utils.arg('id', metavar='', help='Name or ID of stack to get the template for.') def do_gettemplate(hc, args): '''DEPRECATED! Use template-show instead.''' do_template_show(hc, args) @utils.arg('id', metavar='', help='Name or ID of stack to get the template for.') def do_template_show(hc, args): '''Get the template for the specified stack.''' fields = {'stack_id': args.id} try: template = hc.stacks.template(**fields) except exc.HTTPNotFound: raise exc.CommandError('Stack not found: %s' % args.id) else: if 'heat_template_version' in template: print(yaml.safe_dump(template, indent=2)) else: print(jsonutils.dumps(template, indent=2)) @utils.arg('-u', '--template-url', metavar='', help='URL of template.') @utils.arg('-f', '--template-file', metavar='', help='Path to the template.') @utils.arg('-e', '--environment-file', metavar='', help='Path to the environment.') @utils.arg('-o', '--template-object', metavar='', help='URL to retrieve template object (e.g. from swift).') @utils.arg('-P', '--parameters', metavar='', help='Parameter values to validate. ' 'This can be specified multiple times, or once with parameters ' 'separated by a semicolon.', action='append') def do_validate(hc, args): '''DEPRECATED! Use template-validate instead.''' do_template_validate(hc, args) @utils.arg('-u', '--template-url', metavar='', help='URL of template.') @utils.arg('-f', '--template-file', metavar='', help='Path to the template.') @utils.arg('-e', '--environment-file', metavar='', help='Path to the environment.') @utils.arg('-o', '--template-object', metavar='', help='URL to retrieve template object (e.g. from swift).') @utils.arg('-P', '--parameters', metavar='', help='Parameter values to validate. ' 'This can be specified multiple times, or once with parameters ' 'separated by a semicolon.', action='append') def do_template_validate(hc, args): '''Validate a template with parameters.''' tpl_files, template = template_utils.get_template_contents( args.template_file, args.template_url, args.template_object, hc.http_client.raw_request) env_files, env = template_utils.process_environment_and_files( env_path=args.environment_file) fields = { 'parameters': utils.format_parameters(args.parameters), 'template': template, 'files': dict(list(tpl_files.items()) + list(env_files.items())), 'environment': env } validation = hc.stacks.validate(**fields) print(jsonutils.dumps(validation, indent=2)) @utils.arg('id', metavar='', help='Name or ID of stack to show the resources for.') def do_resource_list(hc, args): '''Show list of resources belonging to a stack.''' fields = {'stack_id': args.id} try: resources = hc.resources.list(**fields) except exc.HTTPNotFound: raise exc.CommandError('Stack not found: %s' % args.id) else: fields = ['resource_type', 'resource_status', 'updated_time'] if len(resources) >= 1: if hasattr(resources[0], 'resource_name'): fields.insert(0, 'resource_name') else: fields.insert(0, 'logical_resource_id') utils.print_list(resources, fields, sortby=3) @utils.arg('id', metavar='', help='Name or ID of stack to show the resource for.') @utils.arg('resource', metavar='', help='Name of the resource to show the details for.') def do_resource(hc, args): '''DEPRECATED! Use resource-show instead.''' do_resource_show(hc, args) @utils.arg('id', metavar='', help='Name or ID of stack to show the resource for.') @utils.arg('resource', metavar='', help='Name of the resource to show the details for.') def do_resource_show(hc, args): '''Describe the resource.''' fields = {'stack_id': args.id, 'resource_name': args.resource} try: resource = hc.resources.get(**fields) except exc.HTTPNotFound: raise exc.CommandError('Stack or resource not found: %s %s' % (args.id, args.resource)) else: formatters = { 'links': utils.link_formatter, 'required_by': utils.newline_list_formatter } utils.print_dict(resource.to_dict(), formatters=formatters) @utils.arg('resource', metavar='', help='Name of the resource to generate a template for.') @utils.arg('-F', '--format', metavar='', help="The template output format, one of: %s." % ', '.join(utils.supported_formats.keys())) def do_resource_template(hc, args): '''Generate a template based on a resource.''' fields = {'resource_name': args.resource} try: template = hc.resources.generate_template(**fields) except exc.HTTPNotFound: raise exc.CommandError('Resource %s not found.' % args.resource) else: if args.format: print(utils.format_output(template, format=args.format)) else: print(utils.format_output(template)) @utils.arg('id', metavar='', help='Name or ID of stack to show the resource metadata for.') @utils.arg('resource', metavar='', help='Name of the resource to show the metadata for.') def do_resource_metadata(hc, args): '''List resource metadata.''' fields = {'stack_id': args.id, 'resource_name': args.resource} try: metadata = hc.resources.metadata(**fields) except exc.HTTPNotFound: raise exc.CommandError('Stack or resource not found: %s %s' % (args.id, args.resource)) else: print(jsonutils.dumps(metadata, indent=2)) @utils.arg('id', metavar='', help='Name or ID of stack the resource belongs to.') @utils.arg('resource', metavar='', help='Name of the resource to signal.') @utils.arg('-D', '--data', metavar='', help='JSON Data to send to the signal handler.') @utils.arg('-f', '--data-file', metavar='', help='File containing JSON data to send to the signal handler.') def do_resource_signal(hc, args): '''Send a signal to a resource.''' fields = {'stack_id': args.id, 'resource_name': args.resource} data = args.data data_file = args.data_file if data and data_file: raise exc.CommandError('Can only specify one of data and data-file') if data_file: data_url = template_utils.normalise_file_path_to_url(data_file) data = urlutils.urlopen(data_url).read() if data: try: data = jsonutils.loads(data) except ValueError as ex: raise exc.CommandError('Data should be in JSON format: %s' % ex) if not isinstance(data, dict): raise exc.CommandError('Data should be a JSON dict') fields['data'] = data try: hc.resources.signal(**fields) except exc.HTTPNotFound: raise exc.CommandError('Stack or resource not found: %s %s' % (args.id, args.resource)) @utils.arg('id', metavar='', help='Name or ID of stack to show the events for.') @utils.arg('-r', '--resource', metavar='', help='Name of the resource to filter events by.') def do_event_list(hc, args): '''List events for a stack.''' fields = {'stack_id': args.id, 'resource_name': args.resource} try: events = hc.events.list(**fields) except exc.HTTPNotFound as ex: # it could be the stack or resource that is not found # just use the message that the server sent us. raise exc.CommandError(str(ex)) else: fields = ['id', 'resource_status_reason', 'resource_status', 'event_time'] if len(events) >= 1: if hasattr(events[0], 'resource_name'): fields.insert(0, 'resource_name') else: fields.insert(0, 'logical_resource_id') utils.print_list(events, fields) @utils.arg('id', metavar='', help='Name or ID of stack to show the events for.') @utils.arg('resource', metavar='', help='Name of the resource the event belongs to.') @utils.arg('event', metavar='', help='ID of event to display details for.') def do_event(hc, args): '''DEPRECATED! Use event-show instead.''' do_event_show(hc, args) @utils.arg('id', metavar='', help='Name or ID of stack to show the events for.') @utils.arg('resource', metavar='', help='Name of the resource the event belongs to.') @utils.arg('event', metavar='', help='ID of event to display details for.') def do_event_show(hc, args): '''Describe the event.''' fields = {'stack_id': args.id, 'resource_name': args.resource, 'event_id': args.event} try: event = hc.events.get(**fields) except exc.HTTPNotFound as ex: # it could be the stack/resource/or event that is not found # just use the message that the server sent us. raise exc.CommandError(str(ex)) else: formatters = { 'links': utils.link_formatter, 'resource_properties': utils.json_formatter } utils.print_dict(event.to_dict(), formatters=formatters) def do_build_info(hc, args): '''Retrieve build information.''' result = hc.build_info.build_info() formatters = { 'api': utils.json_formatter, 'engine': utils.json_formatter, } utils.print_dict(result, formatters=formatters) python-heatclient-0.2.8/heatclient/v1/resources.py0000664000175400017540000000702112304731350023346 0ustar jenkinsjenkins00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # 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 heatclient.openstack.common.apiclient import base from heatclient.openstack.common.py3kcompat import urlutils from heatclient.openstack.common import strutils from heatclient.v1 import stacks DEFAULT_PAGE_SIZE = 20 class Resource(base.Resource): def __repr__(self): return "" % self._info def update(self, **fields): self.manager.update(self, **fields) def delete(self): return self.manager.delete(self) def data(self, **kwargs): return self.manager.data(self, **kwargs) class ResourceManager(stacks.StackChildManager): resource_class = Resource def list(self, stack_id): """Get a list of resources. :rtype: list of :class:`Resource` """ url = '/stacks/%s/resources' % stack_id return self._list(url, "resources") def get(self, stack_id, resource_name): """Get the details for a specific resource. :param stack_id: ID of stack containing the resource :param resource_name: ID of resource to get the details for """ stack_id = self._resolve_stack_id(stack_id) # Use urlutils for python2/python3 compatibility url_str = '/stacks/%s/resources/%s' % ( urlutils.quote(stack_id, ''), urlutils.quote(strutils.safe_encode(resource_name), '')) resp, body = self.client.json_request('GET', url_str) return Resource(self, body['resource']) def metadata(self, stack_id, resource_name): """Get the metadata for a specific resource. :param stack_id: ID of stack containing the resource :param resource_name: ID of resource to get metadata for """ stack_id = self._resolve_stack_id(stack_id) # Use urlutils for python2/python3 compatibility url_str = '/stacks/%s/resources/%s/metadata' % ( urlutils.quote(stack_id, ''), urlutils.quote(strutils.safe_encode(resource_name), '')) resp, body = self.client.json_request('GET', url_str) return body['metadata'] def signal(self, stack_id, resource_name, data=None): """Signal a specific resource. :param stack_id: ID of stack containing the resource :param resource_name: ID of resource to send signal to """ stack_id = self._resolve_stack_id(stack_id) url_str = '/stacks/%s/resources/%s/signal' % ( urlutils.quote(stack_id, ''), urlutils.quote(strutils.safe_encode(resource_name), '')) resp, body = self.client.json_request('POST', url_str, data=data) return body def generate_template(self, resource_name): # Use urlutils for python2/python3 compatibility url_str = '/resource_types/%s/template' % ( urlutils.quote(strutils.safe_encode(resource_name), '')) resp, body = self.client.json_request('GET', url_str) return body python-heatclient-0.2.8/heatclient/v1/actions.py0000664000175400017540000000316512304731347023007 0ustar jenkinsjenkins00000000000000# 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 heatclient.openstack.common.apiclient import base from heatclient.v1 import stacks DEFAULT_PAGE_SIZE = 20 class Action(base.Resource): def __repr__(self): return "" % self._info def update(self, **fields): self.manager.update(self, **fields) def delete(self): return self.manager.delete(self) def data(self, **kwargs): return self.manager.data(self, **kwargs) class ActionManager(stacks.StackChildManager): resource_class = Action def suspend(self, stack_id): """Suspend a stack.""" body = {'suspend': None} resp, body = self.client.json_request('POST', '/stacks/%s/actions' % stack_id, data=body) def resume(self, stack_id): """Resume a stack.""" body = {'resume': None} resp, body = self.client.json_request('POST', '/stacks/%s/actions' % stack_id, data=body) python-heatclient-0.2.8/heatclient/v1/resource_types.py0000664000175400017540000000315512304731347024421 0ustar jenkinsjenkins00000000000000# # 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 heatclient.openstack.common.apiclient import base from heatclient.openstack.common.py3kcompat import urlutils from heatclient.openstack.common import strutils class ResourceType(base.Resource): def __repr__(self): return "" % self._info def data(self, **kwargs): return self.manager.data(self, **kwargs) def _add_details(self, info): self.resource_type = info class ResourceTypeManager(base.BaseManager): resource_class = ResourceType def list(self): """Get a list of resource types. :rtype: list of :class:`ResourceType` """ return self._list('/resource_types', 'resource_types') def get(self, resource_type): """Get the details for a specific resource_type. :param resource_type: name of the resource type to get the details for """ url_str = '/resource_types/%s' % ( urlutils.quote(strutils.safe_encode(resource_type), '')) resp, body = self.client.json_request('GET', url_str) return body python-heatclient-0.2.8/heatclient/v1/software_configs.py0000664000175400017540000000321312304731350024675 0ustar jenkinsjenkins00000000000000# 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 heatclient.openstack.common.apiclient import base class SoftwareConfig(base.Resource): def __repr__(self): return "" % self._info def delete(self): return self.manager.delete(config_id=self.id) def data(self, **kwargs): return self.manager.data(self, **kwargs) class SoftwareConfigManager(base.BaseManager): resource_class = SoftwareConfig def get(self, config_id): """Get the details for a specific software config. :param config_id: ID of the software config """ resp, body = self.client.json_request( 'GET', '/software_configs/%s' % config_id) return SoftwareConfig(self, body['software_config']) def create(self, **kwargs): """Create a software config.""" resp, body = self.client.json_request('POST', '/software_configs', data=kwargs) return SoftwareConfig(self, body['software_config']) def delete(self, config_id): """Delete a software config.""" self._delete("/software_configs/%s" % config_id) python-heatclient-0.2.8/heatclient/v1/software_deployments.py0000664000175400017540000000525312304731350025616 0ustar jenkinsjenkins00000000000000# 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 heatclient.openstack.common.apiclient import base from heatclient.openstack.common.py3kcompat import urlutils class SoftwareDeployment(base.Resource): def __repr__(self): return "" % self._info def update(self, **fields): self.manager.update(deployment_id=self.id, **fields) def delete(self): return self.manager.delete(deployment_id=self.id) class SoftwareDeploymentManager(base.BaseManager): resource_class = SoftwareDeployment def list(self, **kwargs): """Get a list of software deployments. :rtype: list of :class:`SoftwareDeployment` """ url = '/software_deployments?%s' % urlutils.urlencode(kwargs) return self._list(url, "software_deployments") def metadata(self, server_id): """Get a grouped collection of software deployment metadata for a given server. :rtype: list of :class:`SoftwareDeployment` """ url = '/software_deployments/metadata/%s' % urlutils.quote( server_id, '') resp, body = self.client.json_request('GET', url) return body['metadata'] def get(self, deployment_id): """Get the details for a specific software deployment. :param deployment_id: ID of the software deployment """ resp, body = self.client.json_request( 'GET', '/software_deployments/%s' % deployment_id) return SoftwareDeployment(self, body['software_deployment']) def create(self, **kwargs): """Create a software deployment.""" resp, body = self.client.json_request( 'POST', '/software_deployments', data=kwargs) return SoftwareDeployment(self, body['software_deployment']) def update(self, deployment_id, **kwargs): """Update a software deployment.""" resp, body = self.client.json_request( 'PUT', '/software_deployments/%s' % deployment_id, data=kwargs) return SoftwareDeployment(self, body['software_deployment']) def delete(self, deployment_id): """Delete a software deployment.""" self._delete("/software_deployments/%s" % deployment_id) python-heatclient-0.2.8/heatclient/v1/stacks.py0000664000175400017540000001260012304731350022623 0ustar jenkinsjenkins00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # 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 six from heatclient.openstack.common.apiclient import base from heatclient.openstack.common.py3kcompat import urlutils class Stack(base.Resource): def __repr__(self): return "" % self._info def create(self, **fields): return self.manager.create(self.identifier, **fields) def update(self, **fields): self.manager.update(self.identifier, **fields) def delete(self): return self.manager.delete(self.identifier) def abandon(self): return self.manager.abandon(self.identifier) def get(self): # set_loaded() first ... so if we have to bail, we know we tried. self._loaded = True if not hasattr(self.manager, 'get'): return new = self.manager.get(self.identifier) if new: self._add_details(new._info) @property def action(self): s = self.stack_status # Return everything before the first underscore return s[:s.index('_')] @property def status(self): s = self.stack_status # Return everything after the first underscore return s[s.index('_') + 1:] @property def identifier(self): return '%s/%s' % (self.stack_name, self.id) class StackManager(base.BaseManager): resource_class = Stack def list(self, **kwargs): """Get a list of stacks. :param limit: maximum number of stacks to return :param marker: begin returning stacks that appear later in the stack list than that represented by this stack id :param filters: dict of direct comparison filters that mimics the structure of a stack object :rtype: list of :class:`Stack` """ def paginate(params): '''Paginate stacks, even if more than API limit.''' current_limit = int(params.get('limit') or 0) url = '/stacks?%s' % urlutils.urlencode(params, True) stacks = self._list(url, 'stacks') for stack in stacks: yield stack num_stacks = len(stacks) remaining_limit = current_limit - num_stacks if remaining_limit > 0 and num_stacks > 0: params['limit'] = remaining_limit params['marker'] = stack.id for stack in paginate(params): yield stack params = {} if 'filters' in kwargs: filters = kwargs.pop('filters') params.update(filters) for key, value in six.iteritems(kwargs): if value: params[key] = value return paginate(params) def create(self, **kwargs): """Create a stack.""" headers = self.client.credentials_headers() resp, body = self.client.json_request('POST', '/stacks', data=kwargs, headers=headers) return body def update(self, stack_id, **kwargs): """Update a stack.""" headers = self.client.credentials_headers() resp, body = self.client.json_request('PUT', '/stacks/%s' % stack_id, data=kwargs, headers=headers) def delete(self, stack_id): """Delete a stack.""" self._delete("/stacks/%s" % stack_id) def abandon(self, stack_id): """Abandon a stack.""" stack = self.get(stack_id) resp, body = self.client.json_request( 'DELETE', '/stacks/%s/abandon' % stack.identifier) return body def get(self, stack_id): """Get the metadata for a specific stack. :param stack_id: Stack ID to lookup """ resp, body = self.client.json_request('GET', '/stacks/%s' % stack_id) return Stack(self, body['stack']) def template(self, stack_id): """Get the template content for a specific stack as a parsed JSON object. :param stack_id: Stack ID to get the template for """ resp, body = self.client.json_request( 'GET', '/stacks/%s/template' % stack_id) return body def validate(self, **kwargs): """Validate a stack template.""" resp, body = self.client.json_request('POST', '/validate', data=kwargs) return body class StackChildManager(base.BaseManager): @property def api(self): return self.client def _resolve_stack_id(self, stack_id): # if the id already has a slash in it, # then it is already {stack_name}/{stack_id} if stack_id.find('/') > 0: return stack_id resp, body = self.client.json_request('GET', '/stacks/%s' % stack_id) stack = body['stack'] return '%s/%s' % (stack['stack_name'], stack['id']) python-heatclient-0.2.8/heatclient/v1/client.py0000664000175400017540000000414112304731347022620 0ustar jenkinsjenkins00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # 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 heatclient.common import http from heatclient.v1 import actions from heatclient.v1 import build_info from heatclient.v1 import events from heatclient.v1 import resource_types from heatclient.v1 import resources from heatclient.v1 import software_configs from heatclient.v1 import software_deployments from heatclient.v1 import stacks class Client(object): """Client for the Heat v1 API. :param string endpoint: A user-supplied endpoint URL for the heat service. :param string token: Token for authentication. :param integer timeout: Allows customization of the timeout for client http requests. (optional) """ def __init__(self, *args, **kwargs): """Initialize a new client for the Heat v1 API.""" self.http_client = http.HTTPClient(*args, **kwargs) self.stacks = stacks.StackManager(self.http_client) self.resources = resources.ResourceManager(self.http_client) self.resource_types = resource_types.ResourceTypeManager( self.http_client) self.events = events.EventManager(self.http_client) self.actions = actions.ActionManager(self.http_client) self.build_info = build_info.BuildInfoManager(self.http_client) self.software_deployments = \ software_deployments.SoftwareDeploymentManager( self.http_client) self.software_configs = software_configs.SoftwareConfigManager( self.http_client) python-heatclient-0.2.8/heatclient/v1/events.py0000664000175400017540000000520612304731350022643 0ustar jenkinsjenkins00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # 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 heatclient.openstack.common.apiclient import base from heatclient.openstack.common.py3kcompat import urlutils from heatclient.openstack.common import strutils from heatclient.v1 import stacks DEFAULT_PAGE_SIZE = 20 class Event(base.Resource): def __repr__(self): return "" % self._info def update(self, **fields): self.manager.update(self, **fields) def delete(self): return self.manager.delete(self) def data(self, **kwargs): return self.manager.data(self, **kwargs) class EventManager(stacks.StackChildManager): resource_class = Event def list(self, stack_id, resource_name=None): """Get a list of events. :param stack_id: ID of stack the events belong to :param resource_name: Optional name of resources to filter events by :rtype: list of :class:`Event` """ if resource_name is None: url = '/stacks/%s/events' % stack_id else: stack_id = self._resolve_stack_id(stack_id) # Use urlutils for python2/python3 compatibility url = '/stacks/%s/resources/%s/events' % ( urlutils.quote(stack_id, ''), urlutils.quote(strutils.safe_encode(resource_name), '')) return self._list(url, "events") def get(self, stack_id, resource_name, event_id): """Get the details for a specific event. :param stack_id: ID of stack containing the event :param resource_name: ID of resource the event belongs to :param event_id: ID of event to get the details for """ stack_id = self._resolve_stack_id(stack_id) # Use urlutils for python2/python3 compatibility url_str = '/stacks/%s/resources/%s/events/%s' % ( urlutils.quote(stack_id, ''), urlutils.quote(strutils.safe_encode(resource_name), ''), urlutils.quote(event_id, '')) resp, body = self.client.json_request('GET', url_str) return Event(self, body['event']) python-heatclient-0.2.8/heatclient/v1/__init__.py0000664000175400017540000000127212304731347023103 0ustar jenkinsjenkins00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # 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. __all__ = ['Client'] from heatclient.v1.client import Client python-heatclient-0.2.8/MANIFEST.in0000664000175400017540000000023312304731347020056 0ustar jenkinsjenkins00000000000000include AUTHORS include LICENSE include README.rst include ChangeLog include tox.ini include .testr.conf recursive-include doc * recursive-include tools * python-heatclient-0.2.8/run_tests.sh0000775000175400017540000000642712304731347020720 0ustar jenkinsjenkins00000000000000#!/bin/bash BASE_DIR=`dirname $0` function usage { echo "Usage: $0 [OPTION]..." echo "Run heatclient test suite(s)" echo "" echo " -V, --virtual-env Use virtualenv. Install automatically if not present." echo " (Default is to run tests in local environment)" echo " -F, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added." echo " -f, --func Functional tests have been removed." echo " -u, --unit Run unit tests (default when nothing specified)" echo " -p, --pep8 Run pep8 tests" echo " --all Run pep8 and unit tests" echo " -c, --coverage Generate coverage report" echo " -d, --debug Run tests with testtools instead of testr. This allows you to use the debugger." echo " -h, --help Print this usage message" exit } # must not assign -a as an option, needed for selecting custom attributes no_venv=1 function process_option { case "$1" in -V|--virtual-env) no_venv=0;; -F|--force) force=1;; -f|--func) test_func=1;; -u|--unit) test_unit=1;; -p|--pep8) test_pep8=1;; --all) test_unit=1; test_pep8=1;; -c|--coverage) coverage=1;; -d|--debug) debug=1;; -h|--help) usage;; *) args="$args $1"; test_unit=1;; esac } venv=.venv with_venv=tools/with_venv.sh wrapper="" debug=0 function run_tests { echo 'Running tests' if [ $debug -eq 1 ]; then echo "Debugging..." if [ "$args" = "" ]; then # Default to running all tests if specific test is not # provided. testrargs="discover ./heatclient/tests" fi ${wrapper} python -m testtools.run $args $testrargs # Short circuit because all of the testr and coverage stuff # below does not make sense when running testtools.run for # debugging purposes. return $? fi # Just run the test suites in current environment if [ -n "$args" ] ; then args="-t $args" fi ${wrapper} python setup.py testr --slowest $args } function run_pep8 { echo "Running flake8..." bash -c "${wrapper} flake8" } # run unit tests with pep8 when no arguments are specified # otherwise process CLI options if [[ $# == 0 ]]; then test_pep8=1 test_unit=1 else for arg in "$@"; do process_option $arg done fi if [ "$no_venv" == 0 ] then # Remove the virtual environment if --force used if [ "$force" == 1 ]; then echo "Cleaning virtualenv..." rm -rf ${venv} fi if [ -e ${venv} ]; then wrapper="${with_venv}" else # Automatically install the virtualenv python tools/install_venv.py wrapper="${with_venv}" fi fi result=0 # If functional or unit tests have been selected, run them if [ "$test_unit" == 1 ] || [ "$debug" == 1 ] ; then run_tests result=$? fi # Run pep8 if it was selected if [ "$test_pep8" == 1 ]; then run_pep8 fi # Generate coverage report if [ "$coverage" == 1 ]; then echo "Generating coverage report in ./cover" ${wrapper} python setup.py testr --coverage --slowest ${wrapper} python -m coverage report --show-missing fi exit $result python-heatclient-0.2.8/AUTHORS0000664000175400017540000000000112304731421017352 0ustar jenkinsjenkins00000000000000 python-heatclient-0.2.8/tox.ini0000664000175400017540000000127612304731347017643 0ustar jenkinsjenkins00000000000000[tox] envlist = py26,py27,pypy,pep8 minversion = 1.6 skipsdist = True [testenv] setenv = VIRTUAL_ENV={envdir} usedevelop = True install_command = pip install -U {opts} {packages} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = python setup.py testr --slowest --testr-args='{posargs}' [testenv:pep8] commands = flake8 [testenv:venv] commands = {posargs} [testenv:cover] commands = python setup.py testr --coverage --testr-args='{posargs}' [tox:jenkins] downloadcache = ~/cache/pip [flake8] show-source = True # H302: Do not import objects, only modules ignore = H302 builtins = _ exclude=.venv,.git,.tox,dist,*openstack/common*,*lib/python*,*egg,build