python-heatclient-1.14.0/0000775000175100017510000000000013234643356015271 5ustar zuulzuul00000000000000python-heatclient-1.14.0/releasenotes/0000775000175100017510000000000013234643356017762 5ustar zuulzuul00000000000000python-heatclient-1.14.0/releasenotes/source/0000775000175100017510000000000013234643356021262 5ustar zuulzuul00000000000000python-heatclient-1.14.0/releasenotes/source/conf.py0000666000175100017510000001320613234643054022560 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # # Python-heatclient releasenotes documentation build configuration file, # created by sphinx-quickstart on Fri Dec 8 02:08:36 2017. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'openstackdocstheme', 'reno.sphinxext', ] # openstackdocstheme options repository_name = 'openstack/python-heatclient' bug_project = 'heat' bug_tag = '' # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Python-heatclient releasenotes' copyright = u'2017, Heat team' author = u'Heat team' # 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 = '' # The full version, including alpha/beta/rc tags. release = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'openstackdocs' html_last_updated_fmt = '%Y-%m-%d %H:%M' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom 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'] # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. htmlhelp_basename = 'Python-heatclientreleasenotesdoc' # -- 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': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'Python-heatclientreleasenotes.tex', u'Python-heatclient releasenotes Documentation', u'Heat team', 'manual'), ] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'python-heatclientreleasenotes', u'Python-heatclient releasenotes Documentation', [author], 1) ] # -- 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 = [ (master_doc, 'Python-heatclientreleasenotes', u'Python-heatclient releasenotes Documentation', author, 'Python-heatclientreleasenotes', 'One line description of project.', 'Miscellaneous'), ] # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] python-heatclient-1.14.0/releasenotes/source/index.rst0000666000175100017510000000027413234643054023123 0ustar zuulzuul00000000000000 Welcome to Python-heatclient releasenotes's documentation! ========================================================== .. toctree:: :maxdepth: 2 :caption: Contents: unreleased python-heatclient-1.14.0/releasenotes/source/unreleased.rst0000666000175100017510000000015713234643054024143 0ustar zuulzuul00000000000000============================== Current Series Release Notes ============================== .. release-notes:: python-heatclient-1.14.0/releasenotes/notes/0000775000175100017510000000000013234643356021112 5ustar zuulzuul00000000000000python-heatclient-1.14.0/releasenotes/notes/add-converge-in-osc-stack-update-10f256589f628d13.yaml0000666000175100017510000000047113234643054032111 0ustar zuulzuul00000000000000--- features: - | Adds '--converge' option for stack update with openstackclient. If specified, existing resource attributes (which may have diverged) are compared with the resource properties in the template used for update. Usage: # openstack stack update --converge ... python-heatclient-1.14.0/releasenotes/notes/bug-1643492-2d7537b55f347722.yaml0000666000175100017510000000046613234643054025414 0ustar zuulzuul00000000000000--- other: - | Updated the Openstack orchestration Commands headers for ``openstack orchestration service list``, ``openstack orchestration template version list``, and ``openstack orchestration template function list`` commands [Bug `1643492 `_] python-heatclient-1.14.0/ChangeLog0000664000175100017510000010274613234643355017054 0ustar zuulzuul00000000000000CHANGES ======= 1.14.0 ------ * Updated from global requirements 1.13.1 ------ * Add reno for release notes management * Fix release note formatting * Avoid tox\_install.sh for constraints support 1.13.0 ------ * Migrate functional job to Zuul v3 * Don't preformat stack output show list/map values * Updated from global requirements * Updated from global requirements * Allow cancelling create\_in\_progress stacks with --no-rollback * Add --no-rollback option for stack cancel * Modify error message encountered during stack update 1.12.0 ------ * Add \`--converge\` argument for osc stack update * Use generic user for both zuul v2 and v3 * No longer use oslotest.mockpatch * Don't override sections in deep\_update * Updated from global requirements * Support --show-nested in openstack stack update --dry-run * Use Sphinx 1.5 warning-is-error * Updated from global requirements * Remove vestigate HUDSON\_PUBLISH\_DOCS reference * Updated from global requirements * Imported Translations from Zanata * Updated from global requirements 1.11.0 ------ * Fixing read before prompt bug * Decode content before checking * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Switch from oslosphinx to openstackdocstheme * Updated from global requirements 1.10.0 ------ * Add plug-in summary for osc doc * Updated from global requirements * Add optional arguments '-y' in CLI:snapshot-delete * Updated from global requirements * Don't encode path separators * Remove support for py34 * Add '--yes' for openstack stack snapshot delete * Explicitly set 'builders' option * Updated from global requirements 1.9.0 ----- * Updated from global requirements * Use upper-constraints for all jobs * Replace six.iteritems() with .items() * Build event stack\_name from links * Pass event filters to the server side * Fix error message when deployment not found * Remove log translations * Updated from global requirements * Updated from global requirements * Update test requirement * Use endpoint\_type with session client * Support openstack stack export * Updated from global requirements * Updated from global requirements * Handle log message interpolation by the logger * Don't always resolve outputs when showing a stack * Show 'project' info if heat server returns 1.8.0 ----- * Updated from global requirements * Updated from global requirements * Updated from global requirements * Support -y for --yes * Enable coverage report in console output * Distinguish between stack and resource events when polling * Make sure the --heat-url option is respected, part 2 * Remove LOG Warning from \_\_eq\_\_ * Replace mox3 with mock * Add convenience function for events over websocket 1.7.0 ----- * Updated from global requirements * Update docs for mark-unhealthy command * Add aliases to orchestration template version list * User server side env merging with osc plugin * Fix doc on keystoneauth session api usage * Updated from global requirements * Fix event list help * Updated from global requirements * Use default service\_type with session * Show team and repo badges on README * Don't resolve outputs when polling events * Updated from global requirements * Update headers for the Openstack Orchestration commands 1.6.1 ----- * Use endpoint as endpoint\_override * Trivial:remove unused code 1.6.0 ----- * Add functinoal tests for 'openstack stack snapshot delete' * Add heat CLI test for deleting stack * Add heat CLI test for creating stack from URL * Replace mox3 with mock * Replace mox3 with mock * Remove unused scripts in tools * Remove oslo-incubator modules * Move required modules from oslo-incubator * Updated from global requirements * Make method import\_versioned\_module work * Graduate to oslo.i18n and cleanup incubator usage * Change property option into append action * Updated from global requirements * Return condition functions based on the filter param * Updated from global requirements * Use region\_name and interface with session * Fix help string for osc resource show * handle empty sections in environment files * Updated from global requirements * Fix "heat output-show -F json" output format * Use event\_time as default event sort key in OSC * Updated from global requirements * Updated from global requirements * Improved the help message of the stack-list tags * delete python bytecode including pyo before every test run * Don't use endpoint\_override with session * Show resource name path in event log formatter * Add url in setup.cfg * Clean imports in code * Modify use of assertTrue(A in B) * Import mox in \_\_init\_\_.py * Replace functions 'Dict.get' and 'del' with 'Dict.pop' * Trivial:Standardize the output format of 'help' in shell.py * Add parameter\_merge\_startegies section 1.4.0 ----- * Updated from global requirements * Add a nested\_depth argument to poll\_for\_events * Update the home-page in setup.cfg * Standardize import format * Remove unused function * Updated from global requirements * Change usage of \_auth\_url private attribute * Updated from global requirements * Remove discover from test-requirements * Add support for stack file list * Updated from global requirements * Trivial: Remove out-of-date and useless example * Add Python 3.5 classifier and venv * Updated from global requirements 1.3.0 ----- * Fix doc build if git is absent * Fix deprecation message for stack-preview * Updated from global requirements * Remove unused LOG * Remove white space between print and () * base.Resource not define \_\_ne\_\_() built-in function * Updated from global requirements * Updated from global requirements * Fix typo * Pass correct arguments in osc plugin * Use osc\_lib instead of cliff * Use osc-lib instead of openstackclient * Imported Translations from Zanata * Enhance error message * Replace dict.iteritems with six.iteritems to make PY3 compatible * Show 'deletion\_time' of stacks * Updated from global requirements * Updated from global requirements * Implement client side of event list --nested-depth * Refactor test\_shell * Serialize files when using SessionClient * Updated from global requirements * Updated from global requirements * Updated from global requirements * Show 'parameter\_defaults' for environment-show * A resource list formatter for graphviz dot diagrams * Updated from global requirements * Implement "openstack stack failures list" * Updated from global requirements * Clean up osc fakes and mocks * Fix typo * osc resource-type-show description option * osc resource-type-list description option * Remove redundant auth discovery * switch to keystoneauth * Updated from global requirements * Allow redirects to use location from response * Updated from global requirements * Updated from global requirements * Updated from global requirements * Fix obscure error message when no template given to OSC * Replace tempest-lib with tempest.lib * Moved required parameter for visibility * use thread safe fnmatch * OpenStack client plugin for environment show * Add client library support for retrieving a stack's environment * Support "--pre-delete" argument for hook-clear * Fixed fragile assertion in test\_template\_utils 1.2.0 ----- * Updated from global requirements * Updated from global requirements * Fix logging issue in heatclient tests * Add basic read-only function test for template validate * Support OS\_KEY/OS\_CERT environment variables * Replace assertRegexpMatches with assertRegex * Moved -t parameter for visibility * Move OpenStack client to test-requirements * OSC plugin for orchestration template validate * Use stack.get for resolving outputs for old API * Fix regression handling relative paths in environment * stack-delete command should not call do\_stack\_list * Added deprecation warnings for heat commands * Don't use print() from event\_utils.poll\_for\_events * OSC plugin should be region/interface aware * Replace ConfigParser with six.moves.configparser * Fixed issues in invalid JSON file * Fixed exceptions handling in stacks deleting * Add an example on keystoneauth Sessions to the doc * Updated from global requirements 1.0.0 ----- * Use poll\_for\_events for "openstack stack delete" * Add OSC plugin for openstack orchestation service list * Add OSC plugin for openstack orchestation build info * Register a log formatter for stack event list * Basic set of stack functional tests for openstack client * Strip any whitespace before checking for json * Add OSC plugin for stack resource mark unhealthy * Implement stack event list --follow * Replace assertEqual(None, \*) with assertIsNone * Prompt user before deleting stack if tty * Add more readonly openstack client funcitonal tests * Use poll\_for\_events for "openstack stack " * Use poll\_for\_events for "openstack stack adopt" * Use poll\_for\_events for "openstack stack update" * Use poll\_for\_events for "openstack stack create" * poll\_for\_events fall back to stack get * Fix collision with update and update cancel * Update OSC metavars * OpenstackClient plugin for software deployment output show * Move poll\_for\_events to event\_utils * Adds --filter to search for resources * Updated from global requirements * Stack resource search * OpenstackClient plugin for software deployment metadata show * Add stack hook poll and clear to openstack client * Updated from global requirements * OpenstackClient plugin for deployment create * Remove the only-value flag of output-show * Changes to support server-side environment resolution * OSC plugin for stack snapshot delete * OSC plugin for stack snapshot create * Use yaml.safe\_load() instead of yaml.load() * Use oslo.utils.reflection to extract class name * Add OpenstackClient plugin for software deployment show * Add openstack client stack snapshot restore * Add openstack client stack resource signal * OSC plugin for stack resource show and list * OpenstackClient plugin for event list * OpenstackClient plugin for event show * OpenstackClient plugin for stack delete * Fix resource\_type osc entrypoint * OSC plugin for software config show * Updated from global requirements * Add openstack cli stack actions * Add openstack client resource type list and show * OSC plugin for stack snapshot show * Add openstack client software config create * OpenstackClient plugin for template show * OpenStackClient plugin for stack output list * OpenstackClient plugin for stack output show * Add openstack client software config list * Add openstack client stack resource metadata show * Adds ignore\_errors for template-validate command * Add heat client support for Resource set health * Sort osc command entry points * OpenstackClient plugin for template function list * OpenstackClient plugin for template version list * Add OpenstackClient plugin for software deployment list * Add openstack client software config delete * OpenstackClient plugin for stack abandon * OpenstackClient plugin for stack adopt * OpenstackClient plugin for software deployment delete * Add openstack client stack basic funtion tests * Remove incorrectly used "# flake8: noqa" * Update translation setup * Edit backslashes for lines continuations * Raise CommandError when Any of stack/deployment/config delete failed * Updated from global requirements * Ignore restricted\_actions key * Add formatters for 'tags' in method \_do\_stack\_show * Enable osc extension in setup.cfg 0.9.0 ----- * Disable osc extension for 0.9.0 release * Remove argparse from requirements * Add openstack client stack snapshot list * Add --dry-run option to openstack stack create * Include keystone message when authentication failed * Add show\_nested support to update --dry-run * Update template\_utils to support object env's * Improve "openstack stack create --wait" help message * OpenstackClient plugin for stack update * OpenstackClient plugin for stack create * Validate for empty file for stack-adopt * Misspelling in message * Updated from global requirements * Add resolve\_outputs parameter to stack get method * Allow event specification in the environment * Add debug testenv in tox * Replace deprecated library function os.popen() with subprocess * Update --template-object to support nested stacks * Added -P support to template-validate * Fix error message on authentication failure * Change LOG.warn to LOG.warning * Updated from global requirements * use keystoneclient exceptions instead of oslo-incubator code * Resolve outputs from stack object * Use stack lookup for getting stack name/id * Replace assertEqual(None, \*) with assertIsNone in tests * Removes MANIFEST.in as it is not needed explicitely by PBR * Fix Resource.\_\_eq\_\_ mismatch semantics of object equal * Updated from global requirements * remove py26 specific code from heatclient * Deprecated tox -downloadcache option removed * Updated from global requirements * remove py26 support tag in setup.cfg * Add JsonFormat display option * Updated from global requirements * Remove py26 support * Delete python bytecode before every test run * Updated from global requirements * Enable pep8 E123 test * Enable pep8 E126 test * Enable pep8 E128 test * Enable pep8 E265 test * Enable pep8 E241 test * Enable pep8 E713 test * Use six.iteritems() instead of iter(XXX.items()) * Fix error when listing stacks * OpenStack typo * Enable pep8 H202 test * Enable pep8 H238 test * Enable pep8 H405 tests * Add output API calls implementations * Refine event\_log\_formatter output * Last sync from oslo-incubator * Delete the corresponding config when deleting a deployment * Fill status code for every HTTPException * OpenStackClient plugin for stack list * Fix environment files parsing bug for stack-adopt * Support to list software configs * Updated from global requirements * Ignore .eggs * Updated from global requirements * OpenStackClient plugin for stack show * Updated from global requirements * Fix error in stack-list metadata * improve readme content * Updated from global requirements * Add option for detailed template error * Updated from global requirements * Add standard code coverage configuration file * Updated from global requirements * Add PATCH support for stack-update --dry-run * Fix the bug when showing event in log format * Updated from global requirements 0.8.0 ----- * stack-update -x tolerate no template * Ability to specify show\_nested for template validation * Fix deployment create error when using existing config * Fix stack sorting support * Enable filters for heat resource-type-list * Modify test templates and the path to subunit2html * Add CLI option for update dry-run * Add py34 env to default tox run 0.7.0 ----- * Fix a nit in resource-list help * Replace deprecated function * Updated from global requirements * Improve the format hint of -Pf option * Support tags parameter for stack-preview * Do not show snapshot data when doing snapshot-list * Updated from global requirements * Poll functionality for stack create action * Updated from global requirements * Use Heat resource to validate resource\_type\_show * Update python3 classifiers * Updated from global requirements * Updated from global requirements * add with\_detail parameter in heat resource-list request * Fix docstrings and add missing * Add missing \`deployment-list\` to cli * Updated from global requirements * Support 'with\_attr' parameter for resource-show * Updated from global requirements * New mock release(1.1.0) broke unit tests * Fix error when creating deployment from cli * Add support for template-function-list command * Fix TypeError when call keystoneclient session.request() * Add support for template-version-list command * Add tests for SessionClient * Updated from global requirements * Move SessionClient from HTTPClient to adapter * resource-list --nested-depth stack\_name column * Fix argument order for assertEqual * Updated from global requirements * Fixed VerifyAll() when test case does ReplayAll() * Updated from global requirements * Move test\_event\_utils.py module to unit directory * Add first pass at post\_test\_hook for functional * cleanup openstack-common.conf and sync updated files * Move usage methods \*\_request to get/post/etc * Updated from global requirements * Add functional tests from Tempest * Update hacking version to fix pep8 gate job * Adds --format=log option to event-list * Add --tags option to stack-create and stack-update * Add stack tag filtering options to stack-list * Add OS\_TEST\_PATH to testr * Move unittests to subdir unit 0.6.0 ----- * Updated from global requirements * Make hook type optional for hook-clear * Refactor common event code into event\_utils module * Add hook-poll function to check if a stack has pending hooks * Work around pypy testing issue * Implement --limit option for event-list with --nested-depth * event-list marker, only slice when needed * Add option to show hidden stacks in stack-list * Add an option to generate template based on resource type * Updated from global requirements * Implement --marker for event-list --nested-depth * Fix sort column for event-list with nested-depth * Unit tests for stack actions 0.5.0 ----- * correction in function names under test\_resource * Uncap library requirements for liberty * Add --nested-depth option to event-list * Make README.rst comply with expected format * Remove the deprecated shell commands 0.4.0 ----- * Parse nested files if they are template * Add wildcard support to hook-clear * Add options for setting and clearing of hooks * Fix test class names * Add option for heatclient to accept parameter value from file * Sync with oslo\_incubator * Migrate to new oslo\_xxx namespace * Updated from global requirements * Implement deployment-create * Implement deployment-output-show * Make ; parsing optional in format\_parameters * Updated from global requirements * Fix SessionClient error when endpoint=None * Fix non-working endpoint type argument * Updated from global requirements * Updates heat.rst with 'service-list * Sort event-list by oldest first 0.3.0 ----- * Adds CLI heat service-list * Add adopt/abandon description to heat manual * Fix different behavior in python3.4 json module * Updated from global requirements * Add CLI commands to interact with software-config * output-show format by json or raw * CLI: Add --all option to output-show command * Removed http proxy environment variable so that httpretty can work * Updated from global requirements * Replace httpretty with requests-mock * Fix passing an object url to the heat CLI * Cleanup shell tests * Format attributes as JSON in resource-show output * Updated from global requirements * Add transtlation markers for log messages * Add transtlation markers for error messages * Add transtlation markers for help messages * Initial setup for i18n support * Show the creation\_time for stack snapshot list * Workflow documentation is now in infra-manual * Remove remaining deprecated oslo-incubator modules * Convert importutils to oslo.utils * Updated from global requirements * Fix H302 errors * Curl statements to include globoff for IPv6 URLs * Add support for parameter\_defaults in environment * Updated from global requirements * Convert strutils to oslo.utils.encodeutils * Move to oslo.serialization * Implement restore operation * Implement snapshot operations * Tests work fine with random PYTHONHASHSEED * Updated from global requirements * Remove \_ from builtins * Fix template\_utils tests for Python 3.4 * Fix event tests for Python 3.4 * Fix shell tests for Python 3.4 * Allow mock-based patching in shell tests * Make sure the --heat-url option is respected * Put a cap on our cyclomatic complexity * Pass auth\_url if os\_no\_client\_auth specified * Updated from global requirements * Remove extraneous vim editor configuration comments * Updated from global requirements * Fixed null str returned for incorrect parameter 0.2.12 ------ * Add a required 'args' to do\_list() * Client supports check action * Fix stack update for Python API * Updated from global requirements 0.2.11 ------ * Add 'cancel\_update' action and command * Updated from global requirements * Add stack-update argument for reset parameters * Add stack-update argument for patching parameters * Display stack owner info on stack-list * Add support for OSprofiler * Reuse existing disable\_rollback in stack-update * Stop using intersphinx * Add --show-nested option to stack-list * warn against sorting requirements * handles keyboard interrupt * Re-add keystone v3 auth with fixes * Template is not required on stack-adopt * output-file option in stack-abandon * Handle upper cased endpoints * Updated from global requirements * Remove unused/mutable default args * Update stack-preview usage text * Removed undefined method in install\_env.py file * Add support for multiple environment files * Add nested-depth option to resource-list * Added timeout and rollback params in stack-preview * Revert "Add keystone v3 auth support" * Don't expose X-Auth-Token in heat CLI * Update theme for docs * Add a tox job for generating docs * Add rel field to links display * Implement events pagination, sorting and filtering * Add keystone v3 auth support * Deprecate misleading cli command 'resource-template' * Show physical\_resource\_id in resource-list * Fix the section name in CONTRIBUTING.rst 0.2.10 ------ * Improve --debug logging output * Do not set up logging handler in http * Updated from global requirements * Fix unicode display in parameters * Append extra space after command in bash\_completion * Add shell arguments for stack update * Removed now unnecesary workaround for PyPy * Don't allow "propagate" of heatclient logger * Add option to show soft-deleted stacks in stack-list * Client should support stack-list for global scope * Mark heatclient as being a universal wheel * Remove no use option parameters for template-validate * Let server order event-list * fixed typos found by RETF rules * Heat client does not support OS\_CACERT option * Fix empty resource list index out of range error * Add code coverage in resource list test * Remove unused arguments for stack-preview command * Use correct order of arguments to assertEqual 0.2.9 ----- * Updated from global requirements * Add a description of how stack-abandon works * Process provider templates for included files * Deprecate preview --create-timeout in favor of --timeout * Support Python 3 * Sync oslo incubator * Python 3: fix test\_template\_utils.py * Skip timeout\_mins when not specify * get\_file: encode non utf-8 encoding files via base64 * get\_file: do not read same url once again * Resolve files in resource types * Do not use the '+' operation with dict\_items() * Python3: fix a bytes/str issue * Updated from global requirements * Using common methods from oslo cliutils * Python 3: decode bytes before feeding them to jsonutils.loads() * Output warnings for deprecated commands * Add stack-preview support * Workaround failing pypy gate job * Remove dependent module py3kcompat * Python 3: Fix YamlEnvironmentTest tests * Oslo: sync strutils * Add new tests to cover most of the requests port * Modify stack ID lookup to only use stacks:lookup * Add timeout option to stack-update * Deprecate create/adopt --create-timeout in favor of --timeout * Remove unused versions define in template\_format.py * Rename --timeout global option and make it work * Python3: fix test\_stack\_list\_with\_args() * Pass bytes to NamedTemporaryFile.write() * Enable --help for each command 0.2.8 ----- * Sort requirement files in alphabetical order * 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-1.14.0/tox.ini0000666000175100017510000000275013234643054016605 0ustar zuulzuul00000000000000[tox] envlist = pypy,py35,py27,pep8 minversion = 1.6 skipsdist = True [testenv] setenv = VIRTUAL_ENV={envdir} CLIENT_NAME=python-heatclient usedevelop = True install_command = pip install {opts} {packages} deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = find . -type f -name "*.py[c|o]" -delete python setup.py testr --slowest --testr-args='{posargs}' whitelist_externals = find [testenv:debug] commands = oslo_debug_helper -t heatclient/tests {posargs} [testenv:debug-py27] basepython = python2.7 commands = oslo_debug_helper -t heatclient/tests {posargs} [testenv:debug-py34] basepython = python3.4 commands = oslo_debug_helper -t heatclient/tests {posargs} [testenv:pep8] commands = flake8 [testenv:venv] commands = {posargs} [testenv:functional] setenv = OS_TEST_PATH = ./heatclient/tests/functional CLIENT_NAME=python-heatclient passenv = OS_* [testenv:cover] commands = python setup.py testr --coverage --testr-args='{posargs}' coverage report [testenv:docs] commands= python setup.py build_sphinx [flake8] show-source = True exclude=.venv,.git,.tox,dist,*lib/python*,*egg,build max-complexity=20 [hacking] import_exceptions = heatclient._i18n [testenv:releasenotes] commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html python-heatclient-1.14.0/setup.py0000666000175100017510000000200613234643054016776 0ustar zuulzuul00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools # In python < 2.7.4, a lazy loading of package `pbr` will break # setuptools if some other modules registered functions in `atexit`. # solution from: http://bugs.python.org/issue15881#msg170215 try: import multiprocessing # noqa except ImportError: pass setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) python-heatclient-1.14.0/PKG-INFO0000664000175100017510000000552313234643356016373 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: python-heatclient Version: 1.14.0 Summary: OpenStack Orchestration API Client Library Home-page: http://docs.openstack.org/developer/python-heatclient/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description-Content-Type: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: http://governance.openstack.org/badges/python-heatclient.svg :target: http://governance.openstack.org/reference/tags/index.html .. Change things from this point on ================= python-heatclient ================= .. image:: https://img.shields.io/pypi/v/python-heatclient.svg :target: https://pypi.python.org/pypi/python-heatclient/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/python-heatclient.svg :target: https://pypi.python.org/pypi/python-heatclient/ :alt: Downloads OpenStack Orchestration API Client Library 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``). * Free software: Apache license * `PyPi`_ - package installation * `Online Documentation`_ * `Launchpad project`_ - release management * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ * `Specs`_ * `Template`_ * `How to Contribute`_ .. _PyPi: https://pypi.python.org/pypi/python-heatclient .. _Online Documentation: http://docs.openstack.org/developer/python-heatclient .. _Launchpad project: https://launchpad.net/python-heatclient .. _Blueprints: https://blueprints.launchpad.net/python-heatclient .. _Bugs: https://bugs.launchpad.net/python-heatclient .. _Source: https://git.openstack.org/cgit/openstack/python-heatclient .. _How to Contribute: http://docs.openstack.org/infra/manual/developers.html .. _Specs: http://specs.openstack.org/openstack/heat-specs/ .. _Template: https://git.openstack.org/cgit/openstack/heat-templates/ Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 python-heatclient-1.14.0/requirements.txt0000666000175100017510000000117413234643054020555 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. Babel!=2.4.0,>=2.3.4 # BSD pbr!=2.1.0,>=2.0.0 # Apache-2.0 cliff!=2.9.0,>=2.8.0 # Apache-2.0 iso8601>=0.1.11 # MIT osc-lib>=1.8.0 # Apache-2.0 PrettyTable<0.8,>=0.7.1 # BSD oslo.i18n>=3.15.3 # Apache-2.0 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 keystoneauth1>=3.3.0 # Apache-2.0 python-swiftclient>=3.2.0 # Apache-2.0 PyYAML>=3.10 # MIT requests>=2.14.2 # Apache-2.0 six>=1.10.0 # MIT python-heatclient-1.14.0/AUTHORS0000664000175100017510000001414413234643355016344 0ustar zuulzuul00000000000000Adrien Vergé Ahmed Elkhouly Aleksei Guzev Alex Gaynor Amey Bhide Anderson Mesquita Andreas Jaeger Andreas Jaeger Andrew Plunk Andrey Kurilin Angus Salkeld Angus Salkeld Bartlomiej Biernacki Bin Zhou Bo Wang Bryan D. Payne Bryan Jones Cao Xuan Hoang ChenZheng Chmouel Boudjnah Christian Berendt Chuck Short Clayton O'Neill Clenimar Filemon Clint Byrum Crag Wolfe Cyril Roelandt Dan Prince Dan Radez Daniel Gonzalez Davanum Srinivas David Hu Dirk Mueller Dmitriy Uvarenkov Doug Hellmann Ethan Lynn Flavio Percoco Georgy Dyuldin Gábor Antal Herman Ge Ian Main Itxaka Ivan Kolodyazhny JUN JIE NAN JUNJIE NAN Jamie Lennox Jason Dunsmore Jay Dobies Jay Lau Jeremy Stanley Jianing YANG Joe Borg JordanP KATO Tomoyuki Kanagaraj Manickam Kanagaraj Manickam Kevin_Zheng Kui Shi Lars Kellogg-Stedman Lin Hua Cheng LiuNanke Lucky samadhiya M V P Nitesh Mark Vanderwiel Masahito Muroi Monty Taylor Nachi Ueno Nam Nguyen Hoai Naohiro Tamura Oleg Khavroshin Oleksii Chuprykov Ondřej Nový Pablo Andres Fuente Pablo Iranzo Gómez PanFengyun PanFengyun Paul Breaux Paul Van Eck Pavlo Shchelokovskyy Peter Razumovsky PriyaDuggirala Rabi Mishra Rahul Nair Rakesh H S Randall Burt Richard Lee Rico Lin Rob Cresswell Rob Crittenden Roberto Polli Ronald Bradford Rui Chen Russell Bryant Ryan S. Brown Sergey Kraynev Sharat Sharma Shuquan Huang Simon Pasquier Simon Pasquier Sirushti Murugesan Song Li Stephen Finucane Steve Baker Steve Martinelli Steven Dake Steven Hardy Sulochan Acharya Sushil Kumar Swann Croiset Swapnil Kulkarni (coolsvap) Takashi NATSUME Tang Chen Tetiana Lashchova Tetyana Lashchova Thomas Bechtold Thomas Herve Thomas Herve Tim Schnell Tomas Sedovic Tomas Sedovic Unmesh Gurjar Van Hung Pham Victor Morales Vijayaguru Guruchave Vijendar Komalla Vijendra Soni Winnie Tsang Zane Bitter Zhang Yang ZhiQiang Fan ZhiQiang Fan Zuul anusha-rayani-7 bhagyashris cedric.brandily chenhaiq chenxiao dixiaoli gong yong sheng huangtianhua ji-xuepeng kairat_kushaev liu-sheng liuqing liyi llg8212 lvdongbing neetu rabi rico.lin ricolin ricolin root shizhihui shu-mutou sonu.kumar sridhargaddam tamilhce tanlin tengqm ting.wang xiaolihope yuyafei zengyingzhe zhang-jinnan zhangguoqing zzxwill python-heatclient-1.14.0/.coveragerc0000666000175100017510000000015713234643054017412 0ustar zuulzuul00000000000000[run] branch = True source = heatclient omit = heatclient/tests/* [report] ignore_errors = True precision = 2 python-heatclient-1.14.0/heatclient/0000775000175100017510000000000013234643356017411 5ustar zuulzuul00000000000000python-heatclient-1.14.0/heatclient/client.py0000666000175100017510000000154113234643054021237 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_utils import importutils def Client(version, *args, **kwargs): module = importutils.import_versioned_module('heatclient', version, 'client') client_class = getattr(module, 'Client') return client_class(*args, **kwargs) python-heatclient-1.14.0/heatclient/v1/0000775000175100017510000000000013234643356017737 5ustar zuulzuul00000000000000python-heatclient-1.14.0/heatclient/v1/software_configs.py0000666000175100017510000000462613234643054023660 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from six.moves.urllib import parse from heatclient.common import base from heatclient.common import utils 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 list(self, **kwargs): """Get a list of software configs. :rtype: list of :class:`SoftwareConfig` """ qparams = {} for opt, val in kwargs.items(): if val: qparams[opt] = val # Transform the dict to a sequence of two-element tuples in fixed # order, then the encoded string will be consistent in Python 2&3. if qparams: new_qparams = sorted(qparams.items(), key=lambda x: x[0]) query_string = "?%s" % parse.urlencode(new_qparams) else: query_string = "" url = '/software_configs%s' % query_string return self._list(url, "software_configs") def get(self, config_id): """Get the details for a specific software config. :param config_id: ID of the software config """ resp = self.client.get('/software_configs/%s' % config_id) body = utils.get_response_body(resp) return SoftwareConfig(self, body.get('software_config')) def create(self, **kwargs): """Create a software config.""" resp = self.client.post('/software_configs', data=kwargs) body = utils.get_response_body(resp) return SoftwareConfig(self, body.get('software_config')) def delete(self, config_id): """Delete a software config.""" self._delete("/software_configs/%s" % config_id) python-heatclient-1.14.0/heatclient/v1/stacks.py0000666000175100017510000002651613234643054021610 0ustar zuulzuul00000000000000# 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 six.moves.urllib import parse from heatclient._i18n import _ from heatclient.common import base from heatclient.common import utils from heatclient import exc class Stack(base.Resource): def __repr__(self): return "" % self._info def preview(self, **fields): return self.manager.preview(**fields) 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 snapshot(self, name=None): return self.manager.snapshot(self.identifier, name) def snapshot_show(self, snapshot_id): return self.manager.snapshot_show(self.identifier, snapshot_id) def snapshot_delete(self, snapshot_id): return self.manager.snapshot_delete(self.identifier, snapshot_id) def restore(self, snapshot_id): return self.manager.restore(self.identifier, snapshot_id) def snapshot_list(self): return self.manager.snapshot_list(self.identifier) def output_list(self): return self.manager.output_list(self.identifier) def output_show(self, output_key): return self.manager.output_show(self.identifier, output_key) def environment(self): return self.manager.environment(self.identifier) def files(self): return self.manager.files(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 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 # We want to capture the redirect, not actually get the stack, # since all we want is the stacks:lookup response to get the # fully qualified ID, and not all users are allowed to do the # redirected stacks:show, so pass redirect=False resp = self.client.get('/stacks/%s' % stack_id, redirect=False) location = resp.headers.get('location') if not location: message = _("Location not returned with redirect") raise exc.InvalidEndpoint(message=message) return location.split('/stacks/', 1)[1] class StackManager(StackChildManager): 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' % parse.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 kwargs.items(): if value: params[key] = value return paginate(params) def preview(self, **kwargs): """Preview a stack.""" headers = self.client.credentials_headers() resp = self.client.post('/stacks/preview', data=kwargs, headers=headers) body = utils.get_response_body(resp) return Stack(self, body.get('stack')) def create(self, **kwargs): """Create a stack.""" headers = self.client.credentials_headers() resp = self.client.post('/stacks', data=kwargs, headers=headers) body = utils.get_response_body(resp) return body def update(self, stack_id, **kwargs): """Update a stack.""" headers = self.client.credentials_headers() if kwargs.pop('existing', None): self.client.patch('/stacks/%s' % stack_id, data=kwargs, headers=headers) else: self.client.put('/stacks/%s' % stack_id, data=kwargs, headers=headers) def preview_update(self, stack_id, **kwargs): """Preview a stack update.""" stack_identifier = self._resolve_stack_id(stack_id) headers = self.client.credentials_headers() path = '/stacks/%s/preview' % stack_identifier if kwargs.pop('show_nested', False): path += '?show_nested=True' if kwargs.pop('existing', None): resp = self.client.patch(path, data=kwargs, headers=headers) else: resp = self.client.put(path, data=kwargs, headers=headers) body = utils.get_response_body(resp) return body def delete(self, stack_id): """Delete a stack.""" self._delete("/stacks/%s" % stack_id) def abandon(self, stack_id): """Abandon a stack.""" stack_identifier = self._resolve_stack_id(stack_id) resp = self.client.delete('/stacks/%s/abandon' % stack_identifier) body = utils.get_response_body(resp) return body def export(self, stack_id): """Export data of a stack.""" stack_identifier = self._resolve_stack_id(stack_id) resp = self.client.get('/stacks/%s/export' % stack_identifier) body = utils.get_response_body(resp) return body def snapshot(self, stack_id, name=None): """Snapshot a stack.""" stack_identifier = self._resolve_stack_id(stack_id) data = {} if name: data['name'] = name resp = self.client.post('/stacks/%s/snapshots' % stack_identifier, data=data) body = utils.get_response_body(resp) return body def snapshot_show(self, stack_id, snapshot_id): stack_identifier = self._resolve_stack_id(stack_id) resp = self.client.get('/stacks/%s/snapshots/%s' % (stack_identifier, snapshot_id)) body = utils.get_response_body(resp) return body def snapshot_delete(self, stack_id, snapshot_id): stack_identifier = self._resolve_stack_id(stack_id) resp = self.client.delete('/stacks/%s/snapshots/%s' % (stack_identifier, snapshot_id)) body = utils.get_response_body(resp) return body def restore(self, stack_id, snapshot_id): stack_identifier = self._resolve_stack_id(stack_id) resp = self.client.post('/stacks/%s/snapshots/%s/restore' % (stack_identifier, snapshot_id)) body = utils.get_response_body(resp) return body def snapshot_list(self, stack_id): stack_identifier = self._resolve_stack_id(stack_id) resp = self.client.get('/stacks/%s/snapshots' % stack_identifier) body = utils.get_response_body(resp) return body def output_list(self, stack_id): stack_identifier = self._resolve_stack_id(stack_id) resp = self.client.get('/stacks/%s/outputs' % stack_identifier) body = utils.get_response_body(resp) return body def output_show(self, stack_id, output_key): stack_identifier = self._resolve_stack_id(stack_id) resp = self.client.get('/stacks/%(id)s/outputs/%(key)s' % { 'id': stack_identifier, 'key': output_key }) body = utils.get_response_body(resp) return body def get(self, stack_id, resolve_outputs=True): """Get the metadata for a specific stack. :param stack_id: Stack ID to lookup :param resolve_outputs: If True, then outputs for this stack will be resolved """ kwargs = {} if not resolve_outputs: kwargs['params'] = {"resolve_outputs": False} resp = self.client.get('/stacks/%s' % stack_id, **kwargs) body = utils.get_response_body(resp) return Stack(self, body.get('stack')) def template(self, stack_id): """Get template content for a specific stack as a parsed JSON object. :param stack_id: Stack ID to get the template for """ resp = self.client.get('/stacks/%s/template' % stack_id) body = utils.get_response_body(resp) return body def environment(self, stack_id): """Returns the environment for an existing stack. :param stack_id: identifies the stack :return: """ resp = self.client.get('/stacks/%s/environment' % stack_id) body = utils.get_response_body(resp) return body def files(self, stack_id): """Returns the files for an existing stack. :param stack_id: identifies the stack :return: """ resp = self.client.get('/stacks/%s/files' % stack_id) body = utils.get_response_body(resp) return body def validate(self, **kwargs): """Validate a stack template.""" url = '/validate' params = {} if kwargs.pop('show_nested', False): params['show_nested'] = True ignore_errors = kwargs.pop('ignore_errors', None) if ignore_errors: params['ignore_errors'] = ignore_errors args = {} if kwargs: args['data'] = kwargs if params: args['params'] = params resp = self.client.post(url, **args) body = utils.get_response_body(resp) return body python-heatclient-1.14.0/heatclient/v1/template_versions.py0000666000175100017510000000345113234643054024054 0ustar zuulzuul00000000000000# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_utils import encodeutils from six.moves.urllib import parse from heatclient.common import base class TemplateVersion(base.Resource): def __repr__(self): return "" % self._info def data(self, **kwargs): return self.manager.data(self, **kwargs) class TemplateVersionManager(base.BaseManager): resource_class = TemplateVersion def list(self): """Get a list of template versions. :rtype: list of :class:`TemplateVersion` """ return self._list('/template_versions', 'template_versions') def get(self, template_version, **kwargs): """Get a list of functions for a specific resource_type. :param template_version: template version to get the functions for """ url_str = '/template_versions/%s/functions' % ( parse.quote(encodeutils.safe_encode(template_version))) params = {} if 'with_condition_func' in kwargs: with_condition_func = kwargs.pop('with_condition_func') params.update({'with_condition_func': with_condition_func}) if params: url_str += '?%s' % parse.urlencode(params, True) return self._list(url_str, 'template_functions') python-heatclient-1.14.0/heatclient/v1/client.py0000666000175100017510000000455013234643054021570 0ustar zuulzuul00000000000000# 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 services from heatclient.v1 import software_configs from heatclient.v1 import software_deployments from heatclient.v1 import stacks from heatclient.v1 import template_versions 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._construct_http_client(*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) self.services = services.ServiceManager(self.http_client) self.template_versions = template_versions.TemplateVersionManager( self.http_client) python-heatclient-1.14.0/heatclient/v1/actions.py0000666000175100017510000000365413234643054021756 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from heatclient.common 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} self.client.post('/stacks/%s/actions' % stack_id, data=body) def resume(self, stack_id): """Resume a stack.""" body = {'resume': None} self.client.post('/stacks/%s/actions' % stack_id, data=body) def cancel_update(self, stack_id): """Cancel running update of a stack.""" body = {'cancel_update': None} self.client.post('/stacks/%s/actions' % stack_id, data=body) def cancel_without_rollback(self, stack_id): """Cancel running update of a stack.""" body = {'cancel_without_rollback': None} self.client.post('/stacks/%s/actions' % stack_id, data=body) def check(self, stack_id): """Check a stack.""" body = {'check': None} self.client.post('/stacks/%s/actions' % stack_id, data=body) python-heatclient-1.14.0/heatclient/v1/events.py0000666000175100017510000000572613234643054021624 0ustar zuulzuul00000000000000# 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 collections from oslo_utils import encodeutils from six.moves.urllib import parse from heatclient.common import base from heatclient.common import utils 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, **kwargs): """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` """ params = {} if 'filters' in kwargs: filters = kwargs.pop('filters') params.update(filters) for key, value in kwargs.items(): if value: params[key] = value if resource_name is None: url = '/stacks/%s/events' % stack_id else: stack_id = self._resolve_stack_id(stack_id) url = '/stacks/%s/resources/%s/events' % ( parse.quote(stack_id), parse.quote(encodeutils.safe_encode(resource_name))) if params: # convert to a sorted dict for python3 predictible order params = collections.OrderedDict(sorted(params.items())) url += '?%s' % parse.urlencode(params, True) 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) url_str = '/stacks/%s/resources/%s/events/%s' % ( parse.quote(stack_id), parse.quote(encodeutils.safe_encode(resource_name)), parse.quote(event_id, '')) resp = self.client.get(url_str) body = utils.get_response_body(resp) return Event(self, body.get('event')) python-heatclient-1.14.0/heatclient/v1/software_deployments.py0000666000175100017510000000535513234643054024573 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from six.moves.urllib import parse from heatclient.common import base from heatclient.common import utils 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' % parse.urlencode(kwargs) return self._list(url, "software_deployments") def metadata(self, server_id): """Get a collection of software deployment metadata for given server. :rtype: list of :class:`SoftwareDeployment` """ url = '/software_deployments/metadata/%s' % parse.quote( server_id) resp = self.client.get(url) body = utils.get_response_body(resp) return body.get('metadata') def get(self, deployment_id): """Get the details for a specific software deployment. :param deployment_id: ID of the software deployment """ resp = self.client.get('/software_deployments/%s' % deployment_id) body = utils.get_response_body(resp) return SoftwareDeployment(self, body.get('software_deployment')) def create(self, **kwargs): """Create a software deployment.""" resp = self.client.post('/software_deployments', data=kwargs) body = utils.get_response_body(resp) return SoftwareDeployment(self, body.get('software_deployment')) def update(self, deployment_id, **kwargs): """Update a software deployment.""" resp = self.client.put('/software_deployments/%s' % deployment_id, data=kwargs) body = utils.get_response_body(resp) return SoftwareDeployment(self, body.get('software_deployment')) def delete(self, deployment_id): """Delete a software deployment.""" self._delete("/software_deployments/%s" % deployment_id) python-heatclient-1.14.0/heatclient/v1/services.py0000666000175100017510000000174313234643054022136 0ustar zuulzuul00000000000000# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from heatclient.common import base class Service(base.Resource): def __repr__(self): return "" % self._info class ServiceManager(base.BaseManager): resource_class = Service def list(self): """Get a list of services. :rtype: list of :class:`Service` """ url = '/services' return self._list(url, "services") python-heatclient-1.14.0/heatclient/v1/__init__.py0000666000175100017510000000130213234643054022041 0ustar zuulzuul00000000000000# 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 # noqa python-heatclient-1.14.0/heatclient/v1/resources.py0000666000175100017510000001205413234643054022322 0ustar zuulzuul00000000000000# 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 oslo_utils import encodeutils from six.moves.urllib import parse from heatclient.common import base from heatclient.common import utils 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) @property def stack_name(self): if not hasattr(self, 'links'): return for l in self.links: if l['rel'] == 'stack': return l['href'].split('/')[-2] class ResourceManager(stacks.StackChildManager): resource_class = Resource def list(self, stack_id, **kwargs): """Get a list of resources. :rtype: list of :class:`Resource` """ params = {} if 'filters' in kwargs: filters = kwargs.pop('filters') params.update(filters) for key, value in kwargs.items(): if value: params[key] = value url = '/stacks/%s/resources' % stack_id if params: url += '?%s' % parse.urlencode(params, True) return self._list(url, "resources") def get(self, stack_id, resource_name, with_attr=None): """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 :param with_attr: Attributes to show """ stack_id = self._resolve_stack_id(stack_id) url_str = '/stacks/%s/resources/%s' % ( parse.quote(stack_id), parse.quote(encodeutils.safe_encode(resource_name))) if with_attr: params = {'with_attr': with_attr} url_str += '?%s' % parse.urlencode(params, True) resp = self.client.get(url_str) body = utils.get_response_body(resp) return Resource(self, body.get('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) url_str = '/stacks/%s/resources/%s/metadata' % ( parse.quote(stack_id), parse.quote(encodeutils.safe_encode(resource_name))) resp = self.client.get(url_str) body = utils.get_response_body(resp) return body.get('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' % ( parse.quote(stack_id), parse.quote(encodeutils.safe_encode(resource_name))) resp = self.client.post(url_str, data=data) body = utils.get_response_body(resp) return body def mark_unhealthy(self, stack_id, resource_name, mark_unhealthy, resource_status_reason): """Mark a resource as healthy or unhealthy. :param stack_id: ID of stack containing the resource :param resource_name: ID of resource :param mark_unhealthy: Mark resource unhealthy if set to True :param resource_status_reason: Reason for resource status change. """ stack_id = self._resolve_stack_id(stack_id) url_str = '/stacks/%s/resources/%s' % ( parse.quote(stack_id), parse.quote(encodeutils.safe_encode(resource_name))) resp = self.client.patch( url_str, data={"mark_unhealthy": mark_unhealthy, "resource_status_reason": resource_status_reason}) body = utils.get_response_body(resp) return body def generate_template(self, resource_name): """Deprecated in favor of generate_template in ResourceTypeManager.""" url_str = '/resource_types/%s/template' % ( parse.quote(encodeutils.safe_encode(resource_name))) resp = self.client.get(url_str) body = utils.get_response_body(resp) return body python-heatclient-1.14.0/heatclient/v1/resource_types.py0000666000175100017510000000576113234643054023372 0ustar zuulzuul00000000000000# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_utils import encodeutils import six from six.moves.urllib import parse from heatclient.common import base from heatclient.common import utils class ResourceType(base.Resource): def __repr__(self): if isinstance(self._info, six.string_types): return "" % self._info else: return "" % self._info.get('resource_type') def data(self, **kwargs): return self.manager.data(self, **kwargs) def _add_details(self, info): if isinstance(info, six.string_types): self.resource_type = info elif isinstance(info, dict): self.resource_type = info.get('resource_type') self.description = info.get('description') class ResourceTypeManager(base.BaseManager): resource_class = ResourceType KEY = 'resource_types' def list(self, **kwargs): """Get a list of resource types. :rtype: list of :class:`ResourceType` """ url = '/%s' % self.KEY params = {} if 'filters' in kwargs: filters = kwargs.pop('filters') params.update(filters) if 'with_description' in kwargs: with_description = kwargs.pop('with_description') params.update({'with_description': with_description}) if params: url += '?%s' % parse.urlencode(params, True) return self._list(url, self.KEY) def get(self, resource_type, with_description=False): """Get the details for a specific resource_type. :param resource_type: name of the resource type to get the details for :param with_description: return result with description or not """ url_str = '/%s/%s' % ( self.KEY, parse.quote(encodeutils.safe_encode(resource_type))) resp = self.client.get(url_str, params={'with_description': with_description}) body = utils.get_response_body(resp) return body def generate_template(self, resource_type, template_type='cfn'): url_str = '/%s/%s/template' % ( self.KEY, parse.quote(encodeutils.safe_encode(resource_type))) if template_type: url_str += '?%s' % parse.urlencode( {'template_type': template_type}, True) resp = self.client.get(url_str) body = utils.get_response_body(resp) return body python-heatclient-1.14.0/heatclient/v1/shell.py0000666000175100017510000021374113234643054021425 0ustar zuulzuul00000000000000# 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 logging import sys from oslo_serialization import jsonutils from oslo_utils import strutils import six from six.moves.urllib import request import yaml from heatclient._i18n import _ from heatclient.common import deployment_utils from heatclient.common import event_utils from heatclient.common import hook_utils from heatclient.common import http from heatclient.common import template_format from heatclient.common import template_utils from heatclient.common import utils import heatclient.exc as exc logger = logging.getLogger(__name__) def show_deprecated(deprecated, recommended): logger.warning('"%(old)s" is deprecated, ' 'please use "%(new)s" instead', {'old': deprecated, 'new': recommended} ) @utils.arg('-f', '--template-file', metavar='', help=_('Path to the template.')) @utils.arg('-e', '--environment-file', metavar='', help=_('Path to the environment, it can be specified ' 'multiple times.'), action='append') @utils.arg('--pre-create', metavar='', default=None, action='append', help=_('Name of a resource to set a pre-create hook to. Resources ' 'in nested stacks can be set using slash as a separator: ' 'nested_stack/another/my_resource. You can use wildcards ' 'to match multiple stacks or resources: ' 'nested_stack/an*/*_resource. This can be specified ' 'multiple times')) @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='', type=int, help=_('Stack creation timeout in minutes. ' 'DEPRECATED use %(arg)s instead.') % {'arg': '--timeout'}) @utils.arg('-t', '--timeout', metavar='', type=int, help=_('Stack creation timeout in minutes.')) @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('-Pf', '--parameter-file', metavar='', help=_('Parameter values from file used to create the stack. ' 'This can be specified multiple times. Parameter value ' 'would be the content of the file'), action='append') @utils.arg('--poll', metavar='SECONDS', type=int, nargs='?', const=5, help=_('Poll and report events until stack completes. ' 'Optional poll interval in seconds can be provided as ' 'argument, default 5.')) @utils.arg('name', metavar='', help=_('Name of the stack to create.')) @utils.arg('--tags', metavar='', help=_('A list of tags to associate with the stack.')) def do_stack_create(hc, args): '''Create the stack.''' show_deprecated('heat stack-create', 'openstack stack create') tpl_files, template = template_utils.get_template_contents( args.template_file, args.template_url, args.template_object, http.authenticated_fetcher(hc)) env_files_list = [] env_files, env = template_utils.process_multiple_environments_and_files( env_paths=args.environment_file, env_list_tracker=env_files_list) if args.create_timeout: logger.warning('%(arg1)s is deprecated, ' 'please use %(arg2)s instead', { 'arg1': '-c/--create-timeout', 'arg2': '-t/--timeout'}) if args.pre_create: template_utils.hooks_to_env(env, args.pre_create, 'pre-create') fields = { 'stack_name': args.name, 'disable_rollback': not(args.enable_rollback), 'parameters': utils.format_all_parameters(args.parameters, args.parameter_file, args.template_file, args.template_url), 'template': template, 'files': dict(list(tpl_files.items()) + list(env_files.items())), 'environment': env } # If one or more environments is found, pass the listing to the server if env_files_list: fields['environment_files'] = env_files_list if args.tags: fields['tags'] = args.tags timeout = args.timeout or args.create_timeout if timeout: fields['timeout_mins'] = timeout hc.stacks.create(**fields) do_stack_list(hc) if not args.poll: return show_fields = {'stack_id': args.name} _do_stack_show(hc, show_fields) stack_status, msg = event_utils.poll_for_events( hc, args.name, action='CREATE', poll_period=args.poll) _do_stack_show(hc, show_fields) if stack_status == 'CREATE_FAILED': raise exc.StackFailure(msg) print(msg) @utils.arg('-e', '--environment-file', metavar='', help=_('Path to the environment, it can be specified ' 'multiple times.'), action='append') @utils.arg('-c', '--create-timeout', metavar='', type=int, help=_('Stack creation timeout in minutes. ' 'DEPRECATED use %(arg)s instead.') % {'arg': '--timeout'}) @utils.arg('-t', '--timeout', metavar='', type=int, help=_('Stack creation timeout in minutes.')) @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.''' show_deprecated('heat stack-adopt', 'openstack stack adopt') env_files, env = template_utils.process_multiple_environments_and_files( env_paths=args.environment_file) if not args.adopt_file: raise exc.CommandError(_('Need to specify %(arg)s') % {'arg': '--adopt-file'}) adopt_url = utils.normalise_file_path_to_url(args.adopt_file) adopt_data = request.urlopen(adopt_url).read() if not len(adopt_data): raise exc.CommandError('Invalid adopt-file, no data!') if args.create_timeout: logger.warning('%(arg1)s is deprecated, ' 'please use %(arg2)s instead', { 'arg1': '-c/--create-timeout', 'arg2': '-t/--timeout'}) fields = { 'stack_name': args.name, 'disable_rollback': not(args.enable_rollback), 'adopt_stack_data': adopt_data, 'parameters': utils.format_parameters(args.parameters), 'files': dict(list(env_files.items())), 'environment': env } timeout = args.timeout or args.create_timeout if timeout: fields['timeout_mins'] = timeout 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, it can be specified ' 'multiple times.'), action='append') @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('-t', '--timeout', metavar='', type=int, help=_('Stack creation timeout in minutes. This is only used ' 'during validation in preview.')) @utils.arg('-r', '--enable-rollback', default=False, action="store_true", help=_('Enable rollback on failure. This option is not used during ' 'preview and exists only for symmetry with %(cmd)s.') % {'cmd': 'stack-create'}) @utils.arg('-P', '--parameters', metavar='', help=_('Parameter values used to preview the stack. ' 'This can be specified multiple times, or once with ' 'parameters separated by semicolon.'), action='append') @utils.arg('-Pf', '--parameter-file', metavar='', help=_('Parameter values from file used to create the stack. ' 'This can be specified multiple times. Parameter value ' 'would be the content of the file'), action='append') @utils.arg('name', metavar='', help=_('Name of the stack to preview.')) @utils.arg('--tags', metavar='', help=_('A list of tags to associate with the stack.')) def do_stack_preview(hc, args): '''Preview the stack.''' show_deprecated('heat stack-preview', 'openstack stack create --dry-run') tpl_files, template = template_utils.get_template_contents( args.template_file, args.template_url, args.template_object, http.authenticated_fetcher(hc)) env_files_list = [] env_files, env = template_utils.process_multiple_environments_and_files( env_paths=args.environment_file, env_list_tracker=env_files_list) fields = { 'stack_name': args.name, 'disable_rollback': not(args.enable_rollback), 'timeout_mins': args.timeout, 'parameters': utils.format_all_parameters(args.parameters, args.parameter_file, args.template_file, args.template_url), 'template': template, 'files': dict(list(tpl_files.items()) + list(env_files.items())), 'environment': env } # If one or more environments is found, pass the listing to the server if env_files_list: fields['environment_files'] = env_files_list if args.tags: fields['tags'] = args.tags stack = hc.stacks.preview(**fields) 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, 'resources': utils.json_formatter, 'links': utils.link_formatter, } utils.print_dict(stack.to_dict(), formatters=formatters) @utils.arg('id', metavar='', nargs='+', help=_('Name or ID of stack(s) to delete.')) @utils.arg('-y', '--yes', default=False, action="store_true", help=_('Skip yes/no prompt (assume yes).')) def do_stack_delete(hc, args): '''Delete the stack(s).''' show_deprecated('heat stack-delete', 'openstack stack delete') failure_count = 0 try: if not args.yes and sys.stdin.isatty(): prompt_response = six.moves.input( _("Are you sure you want to delete this stack(s) [y/N]? ") ).lower() if not prompt_response.startswith('y'): logger.info( 'User did not confirm stack delete so taking no action.') return except KeyboardInterrupt: # ctrl-c logger.info( 'User did not confirm stack delete (ctrl-c) so taking no action.') return except EOFError: # ctrl-d logger.info( 'User did not confirm stack delete (ctrl-d) so taking no action.') return for sid in args.id: fields = {'stack_id': sid} try: hc.stacks.delete(**fields) success_msg = _("Request to delete stack %s has been accepted.") print(success_msg % sid) except (exc.HTTPNotFound, exc.Forbidden) as e: failure_count += 1 print(e) if failure_count: raise exc.CommandError(_("Unable to delete %(count)d of the %(total)d " "stacks.") % {'count': failure_count, 'total': len(args.id)}) @utils.arg('-O', '--output-file', metavar='', help=_('file to output abandon result. ' 'If the option is specified, the result will be ' 'output into .')) @utils.arg('id', metavar='', help=_('Name or ID of stack to abandon.')) def do_stack_abandon(hc, args): '''Abandon the stack. This will delete the record of the stack from Heat, but will not delete any of the underlying resources. Prints an adoptable JSON representation of the stack to stdout or a file on success. ''' show_deprecated('heat stack-abandon', 'openstack stack abandon') fields = {'stack_id': args.id} try: stack = hc.stacks.abandon(**fields) except exc.HTTPNotFound: raise exc.CommandError(_('Stack not found: %s') % args.id) else: result = jsonutils.dumps(stack, indent=2) if args.output_file is not None: try: with open(args.output_file, "w") as f: f.write(result) except IOError as err: print(result) raise exc.CommandError(str(err)) else: print(result) @utils.arg('id', metavar='', help=_('Name or ID of stack to suspend.')) def do_action_suspend(hc, args): '''Suspend the stack.''' show_deprecated('heat action-suspend', 'openstack stack suspend') 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.''' show_deprecated('heat action-resume', 'openstack stack resume') 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 check.')) def do_action_check(hc, args): '''Check that stack resources are in expected states.''' show_deprecated('heat action-check', 'openstack stack check') fields = {'stack_id': args.id} try: hc.actions.check(**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.')) @utils.arg('--no-resolve-outputs', action="store_true", help=_('Do not resolve outputs of the stack.')) def do_stack_show(hc, args): '''Describe the stack.''' show_deprecated('heat stack-show', 'openstack stack show') fields = {'stack_id': args.id, 'resolve_outputs': not args.no_resolve_outputs} _do_stack_show(hc, fields) @utils.arg('-f', '--template-file', metavar='', help=_('Path to the template.')) @utils.arg('-e', '--environment-file', metavar='', help=_('Path to the environment, it can be specified ' 'multiple times.'), action='append') @utils.arg('--pre-update', metavar='', default=None, action='append', help=_('Name of a resource to set a pre-update hook to. Resources ' 'in nested stacks can be set using slash as a separator: ' 'nested_stack/another/my_resource. You can use wildcards ' 'to match multiple stacks or resources: ' 'nested_stack/an*/*_resource. This can be specified ' 'multiple times')) @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('-t', '--timeout', metavar='', type=int, help=_('Stack update timeout in minutes.')) @utils.arg('-r', '--enable-rollback', default=False, action="store_true", help=_('DEPRECATED! Use %(arg)s argument instead. ' 'Enable rollback on stack update failure. ' 'NOTE: default behavior is now to use the rollback value ' 'of existing stack.') % {'arg': '--rollback'}) @utils.arg('--rollback', default=None, metavar='', help=_('Set rollback on update failure. ' 'Values %(true)s set rollback to enabled. ' 'Values %(false)s set rollback to disabled. ' 'Default is to use the value of existing stack to be ' 'updated.') % {'true': strutils.TRUE_STRINGS, 'false': strutils.FALSE_STRINGS}) @utils.arg('-y', '--dry-run', default=False, action="store_true", help=_('Do not actually perform the stack update, but show what ' 'would be changed')) @utils.arg('-n', '--show-nested', default=False, action="store_true", help=_('Show nested stacks when performing --dry-run')) @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('-Pf', '--parameter-file', metavar='', help=_('Parameter values from file used to create the stack. ' 'This can be specified multiple times. Parameter value ' 'would be the content of the file'), action='append') @utils.arg('-x', '--existing', default=False, action="store_true", help=_('Re-use the template, parameters and environment of the ' 'current stack. If the template argument is omitted then ' 'the existing template is used. If no %(env_arg)s is ' 'specified then the existing environment is used. ' 'Parameters specified in %(arg)s will patch over the ' 'existing values in the current stack. Parameters omitted ' 'will keep the existing values.') % {'arg': '--parameters', 'env_arg': '--environment-file'}) @utils.arg('-c', '--clear-parameter', metavar='', help=_('Remove the parameters from the set of parameters of ' 'current stack for the %(cmd)s. The default value in the ' 'template will be used. This can be specified multiple ' 'times.') % {'cmd': 'stack-update'}, action='append') @utils.arg('id', metavar='', help=_('Name or ID of stack to update.')) @utils.arg('--tags', metavar='', help=_('An updated list of tags to associate with the stack.')) def do_stack_update(hc, args): '''Update the stack.''' show_deprecated('heat stack-update', 'openstack stack update') tpl_files, template = template_utils.get_template_contents( args.template_file, args.template_url, args.template_object, http.authenticated_fetcher(hc), existing=args.existing) env_files_list = [] env_files, env = template_utils.process_multiple_environments_and_files( env_paths=args.environment_file, env_list_tracker=env_files_list) if args.pre_update: template_utils.hooks_to_env(env, args.pre_update, 'pre-update') fields = { 'stack_id': args.id, 'parameters': utils.format_all_parameters(args.parameters, args.parameter_file, args.template_file, args.template_url), 'existing': args.existing, 'template': template, 'files': dict(list(tpl_files.items()) + list(env_files.items())), 'environment': env } # If one or more environments is found, pass the listing to the server if env_files_list: fields['environment_files'] = env_files_list if args.tags: fields['tags'] = args.tags if args.timeout: fields['timeout_mins'] = args.timeout if args.clear_parameter: fields['clear_parameters'] = list(args.clear_parameter) if args.rollback is not None: try: rollback = strutils.bool_from_string(args.rollback, strict=True) except ValueError as ex: raise exc.CommandError(six.text_type(ex)) else: fields['disable_rollback'] = not(rollback) # TODO(pshchelo): remove the following 'else' clause after deprecation # period of --enable-rollback switch and assign -r shortcut to --rollback else: if args.enable_rollback: fields['disable_rollback'] = False if args.dry_run is True: if args.show_nested: fields['show_nested'] = args.show_nested resource_changes = hc.stacks.preview_update(**fields) formatters = { 'resource_identity': utils.json_formatter } fields = ['state', 'resource_name', 'resource_type', 'resource_identity'] for k in resource_changes.get("resource_changes", {}).keys(): for i in range(len(resource_changes["resource_changes"][k])): resource_changes["resource_changes"][k][i]['state'] = k utils.print_update_list( sum(resource_changes["resource_changes"].values(), []), fields, formatters=formatters ) return hc.stacks.update(**fields) do_stack_list(hc) @utils.arg('id', metavar='', help=_('Name or ID of stack to cancel update for.')) def do_stack_cancel_update(hc, args): '''Cancel currently running update of the stack.''' show_deprecated('heat stack-cancel-update', 'openstack stack cancel') fields = {'stack_id': args.id} try: hc.actions.cancel_update(**fields) except exc.HTTPNotFound: raise exc.CommandError(_('Stack not found: %s') % args.id) else: do_stack_list(hc) @utils.arg('-s', '--show-deleted', default=False, action="store_true", help=_('Include soft-deleted stacks in the stack listing.')) @utils.arg('-n', '--show-nested', default=False, action="store_true", help=_('Include nested stacks in the stack listing.')) @utils.arg('-a', '--show-hidden', default=False, action="store_true", help=_('Include hidden stacks in the stack listing.')) @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('-t', '--tags', metavar='', help=_('Show stacks containing these tags. If multiple tags ' 'are passed, they will be combined using the AND ' 'boolean expression. ')) @utils.arg('--tags-any', metavar='', help=_('Show stacks containing these tags, If multiple tags ' 'are passed, they will be combined using the OR ' 'boolean expression. ')) @utils.arg('--not-tags', metavar='', help=_('Show stacks not containing these tags, If multiple tags ' 'are passed, they will be combined using the AND ' 'boolean expression. ')) @utils.arg('--not-tags-any', metavar='', help=_('Show stacks not containing these tags, If multiple tags ' 'are passed, they will be combined using the OR ' 'boolean expression. ')) @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.')) @utils.arg('-k', '--sort-keys', metavar='', help=_('List of keys for sorting the returned stacks. ' 'This can be specified multiple times or once with keys ' 'separated by semicolons. Valid sorting keys include ' '"stack_name", "stack_status", "creation_time" and ' '"updated_time".'), action='append') @utils.arg('-d', '--sort-dir', metavar='[asc|desc]', help=_('Sorting direction (either "asc" or "desc") for the sorting ' 'keys.')) @utils.arg('-g', '--global-tenant', action='store_true', default=False, help=_('Display stacks from all tenants. Operation only authorized ' 'for users who match the policy in heat\'s policy.json.')) @utils.arg('-o', '--show-owner', action='store_true', default=False, help=_('Display stack owner information. This is automatically ' 'enabled when using %(arg)s.') % {'arg': '--global-tenant'}) def do_stack_list(hc, args=None): '''List the user's stacks.''' show_deprecated('heat stack-list', 'openstack stack list') kwargs = {} fields = ['id', 'stack_name', 'stack_status', 'creation_time', 'updated_time'] sort_keys = ['stack_name', 'stack_status', 'creation_time', 'updated_time'] sortby_index = 3 if args: kwargs = {'limit': args.limit, 'marker': args.marker, 'filters': utils.format_parameters(args.filters), 'tags': args.tags, 'tags_any': args.tags_any, 'not_tags': args.not_tags, 'not_tags_any': args.not_tags_any, 'global_tenant': args.global_tenant, 'show_deleted': args.show_deleted, 'show_hidden': args.show_hidden} if args.show_nested: fields.append('parent') kwargs['show_nested'] = True if args.sort_keys: # flatten key list first keys = [] for k in args.sort_keys: if ';' in k: keys.extend(k.split(';')) else: keys.append(k) # validate key list for key in keys: if key not in sort_keys: err = _("Sorting key '%(key)s' not one of the supported " "keys: %(keys)s") % {'key': key, "keys": sort_keys} raise exc.CommandError(err) kwargs['sort_keys'] = keys sortby_index = None if args.sort_dir: if args.sort_dir not in ('asc', 'desc'): raise exc.CommandError(_("Sorting direction must be one of " "'asc' and 'desc'")) kwargs['sort_dir'] = args.sort_dir if args.global_tenant or args.show_owner: fields.append('stack_owner') if args.show_deleted: fields.append('deletion_time') stacks = hc.stacks.list(**kwargs) stacks = list(stacks) for stk in stacks: if hasattr(stk, 'project'): fields.append('project') break utils.print_list(stacks, fields, sortby_index=sortby_index) @utils.arg('id', metavar='', help=_('Name or ID of stack to query.')) def do_output_list(hc, args): """Show available outputs.""" show_deprecated('heat output-list', 'openstack stack output list') try: outputs = hc.stacks.output_list(args.id) except exc.HTTPNotFound: try: outputs = hc.stacks.get(args.id).to_dict() except exc.HTTPNotFound: raise exc.CommandError(_('Stack not found: %s') % args.id) fields = ['output_key', 'description'] formatters = { 'output_key': lambda x: x['output_key'], 'description': lambda x: x['description'], } utils.print_list(outputs['outputs'], fields, formatters=formatters) @utils.arg('id', metavar='', help=_('Name or ID of stack to query.')) @utils.arg('output', metavar='', nargs='?', default=None, help=_('Name of an output to display.')) @utils.arg('-F', '--format', metavar='', help=_('The output value format, one of: json, raw.'), default='raw') @utils.arg('-a', '--all', default=False, action='store_true', help=_('Display all stack outputs.')) @utils.arg('--with-detail', default=False, action="store_true", help=_('Enable detail information presented, like ' 'key and description.')) def do_output_show(hc, args): """Show a specific stack output.""" show_deprecated('heat output-show', 'openstack stack output show') def resolve_output(output_key): try: output = hc.stacks.output_show(args.id, output_key) except exc.HTTPNotFound: try: output = None stack = hc.stacks.get(args.id).to_dict() for o in stack.get('outputs', []): if o['output_key'] == output_key: output = {'output': o} break if output is None: raise exc.CommandError(_('Output %(key)s not found.') % { 'key': args.output}) except exc.HTTPNotFound: raise exc.CommandError( _('Stack %(id)s or output %(key)s not found.') % { 'id': args.id, 'key': args.output}) return output def show_output(output): if 'output_error' in output['output']: msg = _("Output error: %s") % output['output']['output_error'] raise exc.CommandError(msg) if args.with_detail: formatters = { 'output_value': (lambda x: utils.json_formatter(x) if args.format == 'json' else x) } utils.print_dict(output['output'], formatters=formatters) else: if args.format == 'json': print(utils.json_formatter(output['output'])) elif (isinstance(output['output']['output_value'], dict) or isinstance(output['output']['output_value'], list)): print(utils.json_formatter(output['output']['output_value'])) else: print(output['output']['output_value']) if args.all: if args.output: raise exc.CommandError( _("Can't specify an output name and the --all flag")) try: outputs = hc.stacks.output_list(args.id) resolved = False except exc.HTTPNotFound: try: outputs = hc.stacks.get(args.id).to_dict() resolved = True except exc.HTTPNotFound: raise exc.CommandError(_('Stack not found: %s') % args.id) for output in outputs['outputs']: if resolved: show_output({'output': output}) else: show_output(resolve_output(output['output_key'])) else: show_output(resolve_output(args.output)) @utils.arg('-f', '--filters', metavar='', help=_('Filter parameters to apply on returned resource types. ' 'This can be specified multiple times, or once with ' 'parameters separated by a semicolon. It can be any of ' 'name, version and support_status'), action='append') def do_resource_type_list(hc, args): '''List the available resource types.''' show_deprecated('heat resource-type-list', 'openstack orchestration resource type list') types = hc.resource_types.list( filters=utils.format_parameters(args.filters)) utils.print_list(types, ['resource_type'], sortby_index=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.''' show_deprecated('heat resource-type-show', 'openstack orchestration resource type show') 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('resource_type', metavar='', help=_('Resource type to generate a template for.')) @utils.arg('-t', '--template-type', metavar='', default='cfn', help=_('Template type to generate, hot or cfn.')) @utils.arg('-F', '--format', metavar='', help=_("The template output format, one of: %s.") % ', '.join(utils.supported_formats.keys())) def do_resource_type_template(hc, args): '''Generate a template based on a resource type.''' show_deprecated('heat resource-type-template', 'openstack orchestration resource ' 'type show --template-type hot') fields = {'resource_type': args.resource_type, 'template_type': args.template_type} try: template = hc.resource_types.generate_template(**fields) except exc.HTTPNotFound: raise exc.CommandError( _('Resource Type %s not found.') % args.resource_type) 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 get the template for.')) def do_template_show(hc, args): '''Get the template for the specified stack.''' show_deprecated('heat template-show', 'openstack stack template show') 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, ensure_ascii=False)) @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, it can be specified ' 'multiple times.'), action='append') @utils.arg('-o', '--template-object', metavar='', help=_('URL to retrieve template object (e.g. from swift).')) @utils.arg('-n', '--show-nested', default=False, action="store_true", help=_('Resolve parameters from nested templates as well.')) @utils.arg('-P', '--parameters', metavar='', help=_('Parameter values for the template. ' 'This can be specified multiple times, or once with ' 'parameters separated by a semicolon.'), action='append') @utils.arg('-I', '--ignore-errors', metavar='', help=_('List of heat errors to ignore.')) def do_template_validate(hc, args): """Validate a template with parameters.""" show_deprecated('heat template-validate', 'openstack orchestration template validate') tpl_files, template = template_utils.get_template_contents( args.template_file, args.template_url, args.template_object, http.authenticated_fetcher(hc)) env_files_list = [] env_files, env = template_utils.process_multiple_environments_and_files( env_paths=args.environment_file, env_list_tracker=env_files_list) fields = { 'template': template, 'parameters': utils.format_parameters(args.parameters), 'files': dict(list(tpl_files.items()) + list(env_files.items())), 'environment': env, } if args.ignore_errors: fields['ignore_errors'] = args.ignore_errors # If one or more environments is found, pass the listing to the server if env_files_list: fields['environment_files'] = env_files_list if args.show_nested: fields['show_nested'] = args.show_nested validation = hc.stacks.validate(**fields) print(jsonutils.dumps(validation, indent=2, ensure_ascii=False)) @utils.arg('id', metavar='', help=_('Name or ID of stack to show the resources for.')) @utils.arg('-n', '--nested-depth', metavar='', help=_('Depth of nested stacks from which to display resources.')) @utils.arg('--with-detail', default=False, action="store_true", help=_('Enable detail information presented for each resource ' 'in resources list.')) @utils.arg('-f', '--filter', metavar='', help=_('Filter parameters to apply on returned resources based on' ' their name, status, type, action, id and' ' physical_resource_id. This can be specified multiple' ' times.'), action='append') def do_resource_list(hc, args): '''Show list of resources belonging to a stack.''' show_deprecated('heat resource-list', 'openstack stack resource list') fields = { 'stack_id': args.id, 'nested_depth': args.nested_depth, 'with_detail': args.with_detail, 'filters': utils.format_parameters(args.filter) } try: resources = hc.resources.list(**fields) except exc.HTTPNotFound: raise exc.CommandError(_('Stack not found: %s') % args.id) else: fields = ['physical_resource_id', 'resource_type', 'resource_status', 'updated_time'] if len(resources) >= 1 and not hasattr(resources[0], 'resource_name'): fields.insert(0, 'logical_resource_id') else: fields.insert(0, 'resource_name') if args.nested_depth or args.with_detail: fields.append('stack_name') utils.print_list(resources, fields, sortby_index=4) @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.')) @utils.arg('-a', '--with-attr', metavar='', help=_('Attribute to show, it can be specified ' 'multiple times.'), action='append') def do_resource_show(hc, args): '''Describe the resource.''' show_deprecated('heat resource-show', 'openstack stack resource show') fields = {'stack_id': args.id, 'resource_name': args.resource} if args.with_attr: fields['with_attr'] = list(args.with_attr) try: resource = hc.resources.get(**fields) except exc.HTTPNotFound: raise exc.CommandError(_('Stack or resource not found: ' '%(id)s %(resource)s') % {'id': args.id, 'resource': args.resource}) else: formatters = { 'attributes': utils.json_formatter, 'links': utils.link_formatter, 'required_by': utils.newline_list_formatter } utils.print_dict(resource.to_dict(), formatters=formatters) @utils.arg('resource_type', metavar='', help=_('Resource type to generate a template for.')) @utils.arg('-t', '--template-type', metavar='', default='cfn', help=_('Template type to generate, hot or cfn.')) @utils.arg('-F', '--format', metavar='', help=_("The template output format, one of: %s.") % ', '.join(utils.supported_formats.keys())) def do_resource_template(hc, args): '''DEPRECATED!''' do_resource_type_template(hc, args) @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.''' show_deprecated('heat resource-metadata', 'openstack stack 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: ' '%(id)s %(resource)s') % {'id': args.id, 'resource': 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.''' show_deprecated('heat resource-signal', 'openstack stack resource signal') 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 = utils.normalise_file_path_to_url(data_file) data = request.urlopen(data_url).read() if data: if isinstance(data, six.binary_type): data = data.decode('utf-8') 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: ' '%(id)s %(resource)s') % {'id': args.id, 'resource': args.resource}) @utils.arg('id', metavar='', help=_('Name or ID of stack the resource belongs to.')) @utils.arg('resource', metavar='', help=_('Name or physical ID of the resource.')) @utils.arg('reason', default="", nargs='?', help=_('Reason for state change.')) @utils.arg('--reset', default=False, action="store_true", help=_('Set the resource as healthy.')) def do_resource_mark_unhealthy(hc, args): '''Set resource's health.''' show_deprecated('heat resource-mark-unhealthy', 'openstack stack resource mark unhealthy') fields = {'stack_id': args.id, 'resource_name': args.resource, 'mark_unhealthy': not args.reset, 'resource_status_reason': args.reason} try: hc.resources.mark_unhealthy(**fields) except exc.HTTPNotFound: raise exc.CommandError(_('Stack or resource not found: ' '%(id)s %(resource)s') % {'id': args.id, 'resource': args.resource}) @utils.arg('id', metavar='', help=_('Name or ID of the stack these resources belong to.')) @utils.arg('--pre-create', action='store_true', default=False, help=_('Clear the pre-create hooks (optional)')) @utils.arg('--pre-update', action='store_true', default=False, help=_('Clear the pre-update hooks (optional)')) @utils.arg('--pre-delete', action='store_true', default=False, help=_('Clear the pre-delete hooks (optional)')) @utils.arg('hook', metavar='', nargs='+', help=_('Resource names with hooks to clear. Resources ' 'in nested stacks can be set using slash as a separator: ' 'nested_stack/another/my_resource. You can use wildcards ' 'to match multiple stacks or resources: ' 'nested_stack/an*/*_resource')) def do_hook_clear(hc, args): '''Clear hooks on a given stack.''' show_deprecated('heat hook-clear', 'openstack stack hook clear') if args.pre_create: hook_type = 'pre-create' elif args.pre_update: hook_type = 'pre-update' elif args.pre_delete: hook_type = 'pre-delete' else: hook_type = hook_utils.get_hook_type_via_status(hc, args.id) for hook_string in args.hook: hook = [b for b in hook_string.split('/') if b] resource_pattern = hook[-1] stack_id = args.id hook_utils.clear_wildcard_hooks(hc, stack_id, hook[:-1], hook_type, resource_pattern) @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.')) @utils.arg('-f', '--filters', metavar='', help=_('Filter parameters to apply on returned events. ' '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 events returned.')) @utils.arg('-m', '--marker', metavar='', help=_('Only return events that appear after the given event ID.')) @utils.arg('-n', '--nested-depth', metavar='', help=_('Depth of nested stacks from which to display events. ' 'Note this cannot be specified with --resource.')) @utils.arg('-F', '--format', metavar='', help=_('The output value format, one of: log, table'), default='table') def do_event_list(hc, args): '''List events for a stack.''' show_deprecated('heat event-list', 'openstack stack event list') display_fields = ['id', 'resource_status_reason', 'resource_status', 'event_time'] event_args = {'resource_name': args.resource, 'limit': args.limit, 'marker': args.marker, 'filters': utils.format_parameters(args.filters), 'sort_dir': 'asc'} # Specifying a resource in recursive mode makes no sense.. if args.nested_depth and args.resource: msg = _("--nested-depth cannot be specified with --resource") raise exc.CommandError(msg) if args.nested_depth: try: nested_depth = int(args.nested_depth) except ValueError: msg = _("--nested-depth invalid value %s") % args.nested_depth raise exc.CommandError(msg) # Until the API supports recursive event listing we'll have to do the # marker/limit filtering client-side del (event_args['marker']) del (event_args['limit']) # Nested list adds the stack name to the output display_fields.append('stack_name') else: nested_depth = 0 events = event_utils.get_events( hc, stack_id=args.id, event_args=event_args, nested_depth=nested_depth, marker=args.marker, limit=args.limit) if len(events) >= 1: if hasattr(events[0], 'resource_name'): display_fields.insert(0, 'resource_name') else: display_fields.insert(0, 'logical_resource_id') if args.format == 'log': print(utils.event_log_formatter(events)) else: utils.print_list(events, display_fields, sortby_index=None) @utils.arg('id', metavar='', help=_('Name or ID of stack to show the pending hooks for.')) @utils.arg('-n', '--nested-depth', metavar='', help=_('Depth of nested stacks from which to display hooks.')) def do_hook_poll(hc, args): '''List resources with pending hook for a stack.''' show_deprecated('heat hook-poll', 'openstack stack hook poll') # There are a few steps to determining if a stack has pending hooks # 1. The stack is IN_PROGRESS status (otherwise, by definition no hooks # can be pending # 2. There is an event for a resource associated with hitting a hook # 3. There is not an event associated with clearing the hook in step(2) # # So, essentially, this ends up being a specially filtered type of event # listing, because all hook status is exposed via events. In future # we might consider exposing some more efficient interface via the API # to reduce the expense of this brute-force polling approach display_fields = ['id', 'resource_status_reason', 'resource_status', 'event_time'] if args.nested_depth: try: nested_depth = int(args.nested_depth) except ValueError: msg = _("--nested-depth invalid value %s") % args.nested_depth raise exc.CommandError(msg) display_fields.append('stack_name') else: nested_depth = 0 hook_type = hook_utils.get_hook_type_via_status(hc, args.id) event_args = {'sort_dir': 'asc'} hook_events = event_utils.get_hook_events( hc, stack_id=args.id, event_args=event_args, nested_depth=nested_depth, hook_type=hook_type) if len(hook_events) >= 1: if hasattr(hook_events[0], 'resource_name'): display_fields.insert(0, 'resource_name') else: display_fields.insert(0, 'logical_resource_id') utils.print_list(hook_events, display_fields, sortby_index=None) @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!''' 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.''' show_deprecated('heat event-show', 'openstack stack event show') 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) @utils.arg('-f', '--definition-file', metavar='', help=_('Path to JSON/YAML containing map defining ' ', , and .')) @utils.arg('-c', '--config-file', metavar='', help=_('Path to configuration script/data.')) @utils.arg('-g', '--group', metavar='', default='Heat::Ungrouped', help=_('Group name of configuration tool expected by the config.')) @utils.arg('name', metavar='', help=_('Name of the configuration to create.')) def do_config_create(hc, args): '''Create a software configuration.''' show_deprecated('heat config-create', 'openstack software config create') config = { 'group': args.group, 'config': '' } defn = {} if args.definition_file: defn_url = utils.normalise_file_path_to_url( args.definition_file) defn_raw = request.urlopen(defn_url).read() or '{}' defn = yaml.load(defn_raw, Loader=template_format.yaml_loader) config['inputs'] = defn.get('inputs', []) config['outputs'] = defn.get('outputs', []) config['options'] = defn.get('options', {}) if args.config_file: config_url = utils.normalise_file_path_to_url( args.config_file) config['config'] = request.urlopen(config_url).read() # build a mini-template with a config resource and validate it validate_template = { 'heat_template_version': '2013-05-23', 'resources': { args.name: { 'type': 'OS::Heat::SoftwareConfig', 'properties': config } } } hc.stacks.validate(template=validate_template) config['name'] = args.name sc = hc.software_configs.create(**config) print(jsonutils.dumps(sc.to_dict(), indent=2)) @utils.arg('-l', '--limit', metavar='', help=_('Limit the number of configs returned.')) @utils.arg('-m', '--marker', metavar='', help=_('Return configs that appear after the given config ID.')) def do_config_list(hc, args): '''List software configs.''' show_deprecated('heat config-list', 'openstack software config list') kwargs = {} if args.limit: kwargs['limit'] = args.limit if args.marker: kwargs['marker'] = args.marker scs = hc.software_configs.list(**kwargs) fields = ['id', 'name', 'group', 'creation_time'] utils.print_list(scs, fields, sortby_index=None) @utils.arg('id', metavar='', help=_('ID of the config.')) @utils.arg('-c', '--config-only', default=False, action="store_true", help=_('Only display the value of the property.')) def do_config_show(hc, args): '''View details of a software configuration.''' show_deprecated('heat config-show', 'openstack software config show') try: sc = hc.software_configs.get(config_id=args.id) except exc.HTTPNotFound: raise exc.CommandError('Configuration not found: %s' % args.id) else: if args.config_only: print(sc.config) else: print(jsonutils.dumps(sc.to_dict(), indent=2)) @utils.arg('id', metavar='', nargs='+', help=_('ID of the configuration(s) to delete.')) def do_config_delete(hc, args): '''Delete the software configuration(s).''' show_deprecated('heat config-delete', 'openstack software config delete') failure_count = 0 for config_id in args.id: try: hc.software_configs.delete(config_id=config_id) except exc.HTTPNotFound: failure_count += 1 print(_('Software config with ID %s not found') % config_id) if failure_count: raise exc.CommandError(_("Unable to delete %(count)d of the %(total)d " "configs.") % {'count': failure_count, 'total': len(args.id)}) @utils.arg('-i', '--input-value', metavar='', help=_('Input value to set on the deployment. ' 'This can be specified multiple times.'), action='append') @utils.arg('-a', '--action', metavar='', default='UPDATE', help=_('Name of action for this deployment. ' 'Can be a custom action, or one of: ' 'CREATE, UPDATE, DELETE, SUSPEND, RESUME')) @utils.arg('-c', '--config', metavar='', help=_('ID of the configuration to deploy.')) @utils.arg('-s', '--server', metavar='', required=True, help=_('ID of the server being deployed to.')) @utils.arg('-t', '--signal-transport', default='TEMP_URL_SIGNAL', metavar='', help=_('How the server should signal to heat with the deployment ' 'output values. TEMP_URL_SIGNAL will create a ' 'Swift TempURL to be signaled via HTTP PUT. NO_SIGNAL will ' 'result in the resource going to the COMPLETE state ' 'without waiting for any signal.')) @utils.arg('--container', metavar='', help=_('Optional name of container to store TEMP_URL_SIGNAL ' 'objects in. If not specified a container will be created ' 'with a name derived from the DEPLOY_NAME')) @utils.arg('--timeout', metavar='', type=int, default=60, help=_('Deployment timeout in minutes.')) @utils.arg('name', metavar='', help=_('Name of the derived config associated with this ' 'deployment. This is used to apply a sort order to the ' 'list of configurations currently deployed to the server.')) def do_deployment_create(hc, args): '''Create a software deployment.''' show_deprecated('heat deployment-create', 'openstack software deployment create') config = {} if args.config: try: config = hc.software_configs.get(config_id=args.config) except exc.HTTPNotFound: raise exc.CommandError( _('Configuration not found: %s') % args.config) derrived_params = deployment_utils.build_derived_config_params( action=args.action, source=config, name=args.name, input_values=utils.format_parameters(args.input_value, False), server_id=args.server, signal_transport=args.signal_transport, signal_id=deployment_utils.build_signal_id(hc, args) ) derived_config = hc.software_configs.create(**derrived_params) sd = hc.software_deployments.create( tenant_id='asdf', config_id=derived_config.id, server_id=args.server, action=args.action, status='IN_PROGRESS' ) print(jsonutils.dumps(sd.to_dict(), indent=2)) @utils.arg('-s', '--server', metavar='', help=_('ID of the server to fetch deployments for.')) def do_deployment_list(hc, args): '''List software deployments.''' show_deprecated('heat deployment-list', 'openstack software deployment list') kwargs = {'server_id': args.server} if args.server else {} deployments = hc.software_deployments.list(**kwargs) fields = ['id', 'config_id', 'server_id', 'action', 'status', 'creation_time', 'status_reason'] utils.print_list(deployments, fields, sortby_index=5) @utils.arg('id', metavar='', help=_('ID of the deployment.')) def do_deployment_show(hc, args): '''Show the details of a software deployment.''' show_deprecated('heat deployment-show', 'openstack software deployment show') try: sd = hc.software_deployments.get(deployment_id=args.id) except exc.HTTPNotFound: raise exc.CommandError(_('Deployment not found: %s') % args.id) else: print(jsonutils.dumps(sd.to_dict(), indent=2)) @utils.arg('id', metavar='', help=_('ID of the server to fetch deployments for.')) def do_deployment_metadata_show(hc, args): '''Get deployment configuration metadata for the specified server.''' show_deprecated('heat deployment-metadata-show', 'openstack software deployment metadata show') md = hc.software_deployments.metadata(server_id=args.id) print(jsonutils.dumps(md, indent=2)) @utils.arg('id', metavar='', nargs='+', help=_('ID of the deployment(s) to delete.')) def do_deployment_delete(hc, args): '''Delete the software deployment(s).''' show_deprecated('heat deployment-delete', 'openstack software deployment delete') failure_count = 0 for deploy_id in args.id: try: sd = hc.software_deployments.get(deployment_id=deploy_id) hc.software_deployments.delete(deployment_id=deploy_id) except Exception as e: if isinstance(e, exc.HTTPNotFound): print(_('Deployment with ID %s not found') % deploy_id) failure_count += 1 continue # just try best to delete the corresponding config try: config_id = getattr(sd, 'config_id') hc.software_configs.delete(config_id=config_id) except Exception: print(_('Failed to delete the correlative config ' '%(config_id)s of deployment %(deploy_id)s') % {'config_id': config_id, 'deploy_id': deploy_id}) if failure_count: raise exc.CommandError(_("Unable to delete %(count)d of the %(total)d " "deployments.") % {'count': failure_count, 'total': len(args.id)}) @utils.arg('id', metavar='', help=_('ID deployment to show the output for.')) @utils.arg('output', metavar='', nargs='?', default=None, help=_('Name of an output to display.')) @utils.arg('-a', '--all', default=False, action='store_true', help=_('Display all deployment outputs.')) @utils.arg('-F', '--format', metavar='', help=_('The output value format, one of: raw, json'), default='raw') def do_deployment_output_show(hc, args): '''Show a specific deployment output.''' show_deprecated('heat deployment-output-show', 'openstack software deployment output show') if (not args.all and args.output is None or args.all and args.output is not None): raise exc.CommandError( _('Error: either %(output)s or %(all)s argument is needed.') % {'output': '', 'all': '--all'}) try: sd = hc.software_deployments.get(deployment_id=args.id) except exc.HTTPNotFound: raise exc.CommandError(_('Deployment not found: %s') % args.id) outputs = sd.to_dict().get('output_values', {}) if args.all: print(utils.json_formatter(outputs)) else: for output_key, value in outputs.items(): if output_key == args.output: break else: return if (args.format == 'json' or isinstance(value, dict) or isinstance(value, list)): print(utils.json_formatter(value)) else: print(value) def do_build_info(hc, args): '''Retrieve build information.''' show_deprecated('heat build-info', 'openstack orchestration build info') result = hc.build_info.build_info() formatters = { 'api': utils.json_formatter, 'engine': utils.json_formatter, } utils.print_dict(result, formatters=formatters) @utils.arg('id', metavar='', help=_('Name or ID of stack to snapshot.')) @utils.arg('-n', '--name', metavar='', help=_('If specified, the name given to the snapshot.')) def do_stack_snapshot(hc, args): '''Make a snapshot of a stack.''' show_deprecated('heat stack-snapshot', 'openstack stack snapshot create') fields = {'stack_id': args.id} if args.name: fields['name'] = args.name try: snapshot = hc.stacks.snapshot(**fields) except exc.HTTPNotFound: raise exc.CommandError(_('Stack not found: %s') % args.id) else: print(jsonutils.dumps(snapshot, indent=2, ensure_ascii=False)) @utils.arg('id', metavar='', help=_('Name or ID of the stack containing the snapshot.')) @utils.arg('snapshot', metavar='', help=_('The ID of the snapshot to show.')) def do_snapshot_show(hc, args): '''Show a snapshot of a stack.''' show_deprecated('heat snapshot-show', 'openstack stack snapshot show') fields = {'stack_id': args.id, 'snapshot_id': args.snapshot} try: snapshot = hc.stacks.snapshot_show(**fields) except exc.HTTPNotFound: raise exc.CommandError(_('Stack or snapshot not found')) else: print(jsonutils.dumps(snapshot, indent=2, ensure_ascii=False)) @utils.arg('id', metavar='', help=_('Name or ID of the stack containing the snapshot.')) @utils.arg('snapshot', metavar='', help=_('The ID of the snapshot to delete.')) @utils.arg('-y', '--yes', default=False, action="store_true", help=_('Skip yes/no prompt (assume yes).')) def do_snapshot_delete(hc, args): '''Delete a snapshot of a stack.''' show_deprecated('heat snapshot-delete', 'openstack stack snapshot delete') msg = "User did not confirm snapshot delete %sso taking no action." try: if not args.yes and sys.stdin.isatty(): sys.stdout.write( _('Are you sure you want to delete the snapshot of this ' 'stack [Y/N]?')) prompt_response = sys.stdin.readline().lower() if not prompt_response.startswith('y'): logger.info(msg, '') return except KeyboardInterrupt: # ctrl-c logger.info(msg, '(ctrl-c) ') return except EOFError: # ctrl-d logger.info(msg, '(ctrl-d) ') return fields = {'stack_id': args.id, 'snapshot_id': args.snapshot} try: hc.stacks.snapshot_delete(**fields) success_msg = _("Request to delete the snapshot %(snapshot_id)s of " "the stack %(stack_id)s has been accepted.") print(success_msg % {'stack_id': args.id, 'snapshot_id': args.snapshot}) except exc.HTTPNotFound: raise exc.CommandError(_('Stack or snapshot not found')) @utils.arg('id', metavar='', help=_('Name or ID of the stack containing the snapshot.')) @utils.arg('snapshot', metavar='', help=_('The ID of the snapshot to restore.')) def do_stack_restore(hc, args): '''Restore a snapshot of a stack.''' show_deprecated('heat stack-restore', 'openstack stack snapshot restore') fields = {'stack_id': args.id, 'snapshot_id': args.snapshot} try: hc.stacks.restore(**fields) except exc.HTTPNotFound: raise exc.CommandError(_('Stack or snapshot not found')) @utils.arg('id', metavar='', help=_('Name or ID of the stack containing the snapshots.')) def do_snapshot_list(hc, args): '''List the snapshots of a stack.''' show_deprecated('heat snapshot-list', 'openstack stack snapshot list') fields = {'stack_id': args.id} try: snapshots = hc.stacks.snapshot_list(**fields) except exc.HTTPNotFound: raise exc.CommandError(_('Stack not found: %s') % args.id) else: fields = ['id', 'name', 'status', 'status_reason', 'creation_time'] formatters = { 'id': lambda x: x['id'], 'name': lambda x: x['name'], 'status': lambda x: x['status'], 'status_reason': lambda x: x['status_reason'], 'creation_time': lambda x: x['creation_time'], } utils.print_list(snapshots["snapshots"], fields, formatters=formatters) def do_service_list(hc, args=None): '''List the Heat engines.''' show_deprecated('heat service-list', 'openstack orchestration service list') fields = ['hostname', 'binary', 'engine_id', 'host', 'topic', 'updated_at', 'status'] services = hc.services.list() utils.print_list(services, fields, sortby_index=1) def do_template_version_list(hc, args): '''List the available template versions.''' show_deprecated('heat template-version-list', 'openstack orchestration template version list') versions = hc.template_versions.list() fields = ['version', 'type'] utils.print_list(versions, fields, sortby_index=1) @utils.arg('template_version', metavar='', help=_('Template version to get the functions for.')) def do_template_function_list(hc, args): '''List the available functions.''' show_deprecated('heat template-function-list', 'openstack orchestration template function list') try: functions = hc.template_versions.get(args.template_version) except exc.HTTPNotFound: raise exc.CommandError( _('Template version not found: %s') % args.template_version) else: utils.print_list(functions, ['functions', 'description']) def _do_stack_show(hc, fields): try: stack = hc.stacks.get(**fields) except exc.HTTPNotFound: raise exc.CommandError(_('Stack not found: %s') % fields.get('stack_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, 'tags': utils.json_formatter } utils.print_dict(stack.to_dict(), formatters=formatters) python-heatclient-1.14.0/heatclient/v1/build_info.py0000666000175100017510000000210313234643054022414 0ustar zuulzuul00000000000000# 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 base from heatclient.common import utils 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 = self.client.get('/build_info') body = utils.get_response_body(resp) return body python-heatclient-1.14.0/heatclient/common/0000775000175100017510000000000013234643356020701 5ustar zuulzuul00000000000000python-heatclient-1.14.0/heatclient/common/hook_utils.py0000666000175100017510000000573013234643054023435 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging from oslo_utils import fnmatch from heatclient._i18n import _ from heatclient import exc logger = logging.getLogger(__name__) def clear_hook(hc, stack_id, resource_name, hook_type): try: hc.resources.signal( stack_id=stack_id, resource_name=resource_name, data={'unset_hook': hook_type}) except exc.HTTPNotFound: logger.error( "Stack %(stack)s or resource %(resource)s " "not found for hook %(hook_type)", {'resource': resource_name, 'stack': stack_id, 'hook_type': hook_type}) def clear_wildcard_hooks(hc, stack_id, stack_patterns, hook_type, resource_pattern): if stack_patterns: for resource in hc.resources.list(stack_id): res_name = resource.resource_name if fnmatch.fnmatchcase(res_name, stack_patterns[0]): nested_stack = hc.resources.get( stack_id=stack_id, resource_name=res_name) clear_wildcard_hooks( hc, nested_stack.physical_resource_id, stack_patterns[1:], hook_type, resource_pattern) else: for resource in hc.resources.list(stack_id): res_name = resource.resource_name if fnmatch.fnmatchcase(res_name, resource_pattern): clear_hook(hc, stack_id, res_name, hook_type) def get_hook_type_via_status(hc, stack_id): # Figure out if the hook should be pre-create, pre-update or # pre-delete based on the stack status, also sanity assertions # that we're in-progress. try: stack = hc.stacks.get(stack_id=stack_id) except exc.HTTPNotFound: raise exc.CommandError(_('Stack not found: %s') % stack_id) else: if 'IN_PROGRESS' not in stack.stack_status: raise exc.CommandError(_('Stack status %s not IN_PROGRESS') % stack.stack_status) if 'CREATE' in stack.stack_status: hook_type = 'pre-create' elif 'UPDATE' in stack.stack_status: hook_type = 'pre-update' elif 'DELETE' in stack.stack_status: hook_type = 'pre-delete' else: raise exc.CommandError(_('Unexpected stack status %s, ' 'only create, update and delete supported') % stack.stack_status) return hook_type python-heatclient-1.14.0/heatclient/common/http.py0000666000175100017510000003271013234643054022232 0ustar zuulzuul00000000000000# 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 hashlib import logging import os import socket from keystoneauth1 import adapter from oslo_serialization import jsonutils from oslo_utils import encodeutils from oslo_utils import importutils import requests import six from six.moves.urllib import parse from heatclient._i18n import _ from heatclient.common import utils from heatclient import exc LOG = logging.getLogger(__name__) USER_AGENT = 'python-heatclient' CHUNKSIZE = 1024 * 64 # 64kB SENSITIVE_HEADERS = ('X-Auth-Token',) osprofiler_web = importutils.try_import("osprofiler.web") def authenticated_fetcher(hc): """A wrapper around the heat client object to fetch a template.""" def _do(*args, **kwargs): if isinstance(hc.http_client, SessionClient): method, url = args return hc.http_client.request(url, method, **kwargs).content else: return hc.http_client.raw_request(*args, **kwargs).content return _do 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.warning("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.timeout = kwargs.get('timeout') 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 parse.urlparse(endpoint).scheme == "https": if kwargs.get('insecure'): self.verify_cert = False else: self.verify_cert = kwargs.get('ca_file', get_system_ca_file()) # FIXME(shardy): We need this for compatibility with the oslo apiclient # we should move to inheriting this class from the oslo HTTPClient self.last_request_id = None def safe_header(self, name, value): if name in SENSITIVE_HEADERS: # because in python3 byte string handling is ... ug v = value.encode('utf-8') h = hashlib.sha1(v) d = h.hexdigest() return encodeutils.safe_decode(name), "{SHA1}%s" % d else: return (encodeutils.safe_decode(name), encodeutils.safe_decode(value)) def log_curl_request(self, method, url, kwargs): curl = ['curl -g -i -X %s' % method] for (key, value) in kwargs['headers'].items(): header = '-H \'%s: %s\'' % self.safe_header(key, 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 'X-Auth-Key' not in kwargs['headers']: kwargs['headers'].update(self.credentials_headers()) if osprofiler_web: kwargs['headers'].update(osprofiler_web.get_trace_id_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 if self.timeout is not None: kwargs['timeout'] = float(self.timeout) # Allow caller to specify not to follow redirects, in which case we # just return the redirect response. Useful for using stacks:lookup. redirect = kwargs.pop('redirect', True) # 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 # Use fully qualified URL from response header for redirects if not parse.urlparse(url).netloc: url = self.endpoint_url + url try: resp = requests.request( method, 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) txt_content = encodeutils.safe_decode(resp.content, 'utf-8') if not ('X-Auth-Key' in kwargs['headers']) and ( resp.status_code == 401 or (resp.status_code == 500 and "(HTTP 401)" in txt_content)): raise exc.HTTPUnauthorized(_("Authentication failed: %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, # unless caller specified redirect=False if redirect: location = resp.headers.get('location') if not location: message = _("Location not returned with redirect") raise exc.InvalidEndpoint(message=message) resp = self._http_request(location, method, **kwargs) elif resp.status_code == 300: raise exc.from_response(resp) return resp def credentials_headers(self): creds = {} # NOTE(dhu): (shardy) When deferred_auth_method=password, Heat # encrypts and stores username/password. For Keystone v3, the # intent is to use trusts since SHARDY is working towards # deferred_auth_method=trusts as the default. # TODO(dhu): Make Keystone v3 work in Heat standalone mode. Maye # require X-Auth-User-Domain. 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 = utils.get_response_body(resp) 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) class SessionClient(adapter.LegacyJsonAdapter): """HTTP client based on Keystone client session.""" def request(self, url, method, **kwargs): redirect = kwargs.get('redirect') kwargs.setdefault('user_agent', USER_AGENT) if 'data' in kwargs: kwargs['data'] = jsonutils.dumps(kwargs['data']) resp, body = super(SessionClient, self).request( url, method, raise_exc=False, **kwargs) if 400 <= resp.status_code < 600: raise exc.from_response(resp) elif resp.status_code in (301, 302, 305): if redirect: location = resp.headers.get('location') path = self.strip_endpoint(location) resp = self.request(path, method, **kwargs) elif resp.status_code == 300: raise exc.from_response(resp) return resp def credentials_headers(self): return {} def strip_endpoint(self, location): if location is None: message = _("Location not returned with 302") raise exc.InvalidEndpoint(message=message) if (self.endpoint_override is not None and location.lower().startswith(self.endpoint_override.lower())): return location[len(self.endpoint_override):] else: return location def _construct_http_client(endpoint=None, username=None, password=None, include_pass=None, endpoint_type=None, auth_url=None, **kwargs): session = kwargs.pop('session', None) auth = kwargs.pop('auth', None) if session: if 'endpoint_override' not in kwargs and endpoint: kwargs['endpoint_override'] = endpoint if 'service_type' not in kwargs: kwargs['service_type'] = 'orchestration' if 'interface' not in kwargs and endpoint_type: kwargs['interface'] = endpoint_type return SessionClient(session, auth=auth, **kwargs) else: return HTTPClient(endpoint=endpoint, username=username, password=password, include_pass=include_pass, endpoint_type=endpoint_type, auth_url=auth_url, **kwargs) python-heatclient-1.14.0/heatclient/common/event_utils.py0000666000175100017510000002435513234643054023622 0ustar zuulzuul00000000000000# Copyright 2015 Red Hat Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import sys import time from heatclient._i18n import _ from heatclient.common import utils import heatclient.exc as exc from heatclient.v1 import events as events_mod def get_hook_events(hc, stack_id, event_args, nested_depth=0, hook_type='pre-create'): if hook_type == 'pre-create': stack_action_reason = 'Stack CREATE started' hook_event_reason = 'CREATE paused until Hook pre-create is cleared' hook_clear_event_reason = 'Hook pre-create is cleared' elif hook_type == 'pre-update': stack_action_reason = 'Stack UPDATE started' hook_event_reason = 'UPDATE paused until Hook pre-update is cleared' hook_clear_event_reason = 'Hook pre-update is cleared' elif hook_type == 'pre-delete': stack_action_reason = 'Stack DELETE started' hook_event_reason = 'DELETE paused until Hook pre-delete is cleared' hook_clear_event_reason = 'Hook pre-delete is cleared' else: raise exc.CommandError(_('Unexpected hook type %s') % hook_type) events = get_events(hc, stack_id=stack_id, event_args=event_args, nested_depth=nested_depth) # Get the most recent event associated with this action, which gives us the # event when we moved into IN_PROGRESS for the hooks we're interested in. stack_name = stack_id.split("/")[0] action_start_event = [e for e in enumerate(events) if e[1].resource_status_reason == stack_action_reason and e[1].stack_name == stack_name][-1] # Slice the events with the index from the enumerate action_start_index = action_start_event[0] events = events[action_start_index:] # Get hook events still pending by some list filtering/comparison # We build a map hook events per-resource, and remove any event # for which there is a corresponding hook-clear event. resource_event_map = {} for e in events: stack_resource = (e.stack_name, e.resource_name) if e.resource_status_reason == hook_event_reason: resource_event_map[(e.stack_name, e.resource_name)] = e elif e.resource_status_reason == hook_clear_event_reason: if resource_event_map.get(stack_resource): del(resource_event_map[(e.stack_name, e.resource_name)]) return list(resource_event_map.values()) def get_events(hc, stack_id, event_args, nested_depth=0, marker=None, limit=None): event_args = dict(event_args) if marker: event_args['marker'] = marker if limit: event_args['limit'] = limit if not nested_depth: # simple call with no nested_depth return _get_stack_events(hc, stack_id, event_args) # assume an API which supports nested_depth event_args['nested_depth'] = nested_depth events = _get_stack_events(hc, stack_id, event_args) if not events: return events first_links = getattr(events[0], 'links', []) root_stack_link = [l for l in first_links if l.get('rel') == 'root_stack'] if root_stack_link: # response has a root_stack link, indicating this is an API which # supports nested_depth return events # API doesn't support nested_depth, do client-side paging and recursive # event fetch marker = event_args.pop('marker', None) limit = event_args.pop('limit', None) event_args.pop('nested_depth', None) events = _get_stack_events(hc, stack_id, event_args) events.extend(_get_nested_events(hc, nested_depth, stack_id, event_args)) # Because there have been multiple stacks events mangled into # one list, we need to sort before passing to print_list # Note we can't use the prettytable sortby_index here, because # the "start" option doesn't allow post-sort slicing, which # will be needed to make "--marker" work for nested_depth lists events.sort(key=lambda x: x.event_time) # Slice the list if marker is specified if marker: try: marker_index = [e.id for e in events].index(marker) events = events[marker_index:] except ValueError: pass # Slice the list if limit is specified if limit: limit_index = min(int(limit), len(events)) events = events[:limit_index] return events def _get_nested_ids(hc, stack_id): nested_ids = [] try: resources = hc.resources.list(stack_id=stack_id) except exc.HTTPNotFound: raise exc.CommandError(_('Stack not found: %s') % stack_id) for r in resources: nested_id = utils.resource_nested_identifier(r) if nested_id: nested_ids.append(nested_id) return nested_ids def _get_nested_events(hc, nested_depth, stack_id, event_args): # FIXME(shardy): this is very inefficient, we should add nested_depth to # the event_list API in a future heat version, but this will be required # until kilo heat is EOL. nested_ids = _get_nested_ids(hc, stack_id) nested_events = [] for n_id in nested_ids: stack_events = _get_stack_events(hc, n_id, event_args) if stack_events: nested_events.extend(stack_events) if nested_depth > 1: next_depth = nested_depth - 1 nested_events.extend(_get_nested_events( hc, next_depth, n_id, event_args)) return nested_events def _get_stack_name_from_links(event): links = dict((l.get('rel'), l.get('href')) for l in getattr(event, 'links', [])) href = links.get('stack') if not href: return return href.split('/stacks/', 1)[-1].split('/')[0] def _get_stack_events(hc, stack_id, event_args): event_args['stack_id'] = stack_id try: events = hc.events.list(**event_args) 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: stack_name = stack_id.split("/")[0] # Show which stack the event comes from (for nested events) for e in events: e.stack_name = _get_stack_name_from_links(e) or stack_name return events def poll_for_events(hc, stack_name, action=None, poll_period=5, marker=None, out=None, nested_depth=0): """Continuously poll events and logs for performed action on stack.""" if action: stop_status = ('%s_FAILED' % action, '%s_COMPLETE' % action) stop_check = lambda a: a in stop_status else: stop_check = lambda a: a.endswith('_COMPLETE') or a.endswith('_FAILED') no_event_polls = 0 msg_template = _("\n Stack %(name)s %(status)s \n") if not out: out = sys.stdout event_log_context = utils.EventLogContext() def is_stack_event(event): if getattr(event, 'resource_name', '') != stack_name: return False phys_id = getattr(event, 'physical_resource_id', '') links = dict((l.get('rel'), l.get('href')) for l in getattr(event, 'links', [])) stack_id = links.get('stack', phys_id).rsplit('/', 1)[-1] return stack_id == phys_id while True: events = get_events(hc, stack_id=stack_name, nested_depth=nested_depth, event_args={'sort_dir': 'asc', 'marker': marker}) if len(events) == 0: no_event_polls += 1 else: no_event_polls = 0 # set marker to last event that was received. marker = getattr(events[-1], 'id', None) events_log = utils.event_log_formatter(events, event_log_context) out.write(events_log) out.write('\n') for event in events: # check if stack event was also received if is_stack_event(event): stack_status = getattr(event, 'resource_status', '') msg = msg_template % dict( name=stack_name, status=stack_status) if stop_check(stack_status): return stack_status, msg if no_event_polls >= 2: # after 2 polls with no events, fall back to a stack get stack = hc.stacks.get(stack_name, resolve_outputs=False) stack_status = stack.stack_status msg = msg_template % dict( name=stack_name, status=stack_status) if stop_check(stack_status): return stack_status, msg # go back to event polling again no_event_polls = 0 time.sleep(poll_period) def wait_for_events(ws, stack_name, out=None): """Receive events over the passed websocket and wait for final status.""" msg_template = _("\n Stack %(name)s %(status)s \n") if not out: out = sys.stdout event_log_context = utils.EventLogContext() while True: data = ws.recv()['body'] event = events_mod.Event(None, data['payload'], True) # Keep compatibility with the HTTP API event.event_time = data['timestamp'] event.resource_status = '%s_%s' % (event.resource_action, event.resource_status) events_log = utils.event_log_formatter([event], event_log_context) out.write(events_log) out.write('\n') if data['payload']['resource_name'] == stack_name: stack_status = data['payload']['resource_status'] if stack_status in ('COMPLETE', 'FAILED'): msg = msg_template % dict( name=stack_name, status=event.resource_status) return '%s_%s' % (event.resource_action, stack_status), msg python-heatclient-1.14.0/heatclient/common/deployment_utils.py0000666000175100017510000001215213234643054024651 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy import uuid import six from six.moves.urllib import parse as urlparse from swiftclient import client as sc from swiftclient import utils as swiftclient_utils from heatclient._i18n import _ from heatclient import exc from heatclient.v1 import software_configs def build_derived_config_params(action, source, name, input_values, server_id, signal_transport, signal_id=None): if isinstance(source, software_configs.SoftwareConfig): source = source.to_dict() input_values = input_values or {} inputs = copy.deepcopy(source.get('inputs')) or [] for inp in inputs: input_key = inp['name'] inp['value'] = input_values.pop(input_key, inp.get('default')) # for any input values that do not have a declared input, add # a derived declared input so that they can be used as config # inputs for inpk, inpv in input_values.items(): inputs.append({ 'name': inpk, 'type': 'String', 'value': inpv }) inputs.extend([{ 'name': 'deploy_server_id', 'description': _('ID of the server being deployed to'), 'type': 'String', 'value': server_id }, { 'name': 'deploy_action', 'description': _('Name of the current action being deployed'), 'type': 'String', 'value': action }, { 'name': 'deploy_signal_transport', 'description': _('How the server should signal to heat with ' 'the deployment output values.'), 'type': 'String', 'value': signal_transport }]) if signal_transport == 'TEMP_URL_SIGNAL': inputs.append({ 'name': 'deploy_signal_id', 'description': _('ID of signal to use for signaling ' 'output values'), 'type': 'String', 'value': signal_id }) inputs.append({ 'name': 'deploy_signal_verb', 'description': _('HTTP verb to use for signaling ' 'output values'), 'type': 'String', 'value': 'PUT' }) elif signal_transport != 'NO_SIGNAL': raise exc.CommandError( _('Unsupported signal transport %s') % signal_transport) return { 'group': source.get('group') or 'Heat::Ungrouped', 'config': source.get('config') or '', 'options': source.get('options') or {}, 'inputs': inputs, 'outputs': source.get('outputs') or [], 'name': name } def create_temp_url(swift_client, name, timeout, container=None): container = container or '%(name)s-%(uuid)s' % { 'name': name, 'uuid': uuid.uuid4()} object_name = str(uuid.uuid4()) swift_client.put_container(container) key_header = 'x-account-meta-temp-url-key' if key_header not in swift_client.head_account(): swift_client.post_account({ key_header: six.text_type(uuid.uuid4())[:32]}) key = swift_client.head_account()[key_header] project_path = swift_client.url.split('/')[-1] path = '/v1/%s/%s/%s' % (project_path, container, object_name) timeout_secs = timeout * 60 tempurl = swiftclient_utils.generate_temp_url(path, timeout_secs, key, 'PUT') sw_url = urlparse.urlparse(swift_client.url) put_url = '%s://%s%s' % (sw_url.scheme, sw_url.netloc, tempurl) swift_client.put_object(container, object_name, '') return put_url def build_signal_id(hc, args): if args.signal_transport != 'TEMP_URL_SIGNAL': return if args.os_no_client_auth: raise exc.CommandError(_( 'Cannot use --os-no-client-auth, auth required to create ' 'a Swift TempURL.')) swift_client = create_swift_client( hc.http_client.auth, hc.http_client.session, args) return create_temp_url(swift_client, args.name, args.timeout) def create_swift_client(auth, session, args): auth_token = auth.get_token(session) endpoint = auth.get_endpoint(session, service_type='object-store', region_name=args.os_region_name) project_name = args.os_project_name or args.os_tenant_name swift_args = { 'auth_version': '2.0', 'tenant_name': project_name, 'user': args.os_username, 'key': None, 'authurl': None, 'preauthtoken': auth_token, 'preauthurl': endpoint, 'cacert': args.os_cacert, 'insecure': args.insecure } return sc.Connection(**swift_args) python-heatclient-1.14.0/heatclient/common/utils.py0000666000175100017510000003344513234643054022421 0ustar zuulzuul00000000000000# 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 base64 import logging import os import textwrap import uuid from oslo_serialization import jsonutils from oslo_utils import encodeutils import prettytable import six from six.moves.urllib import error from six.moves.urllib import parse from six.moves.urllib import request import yaml from heatclient._i18n import _ from heatclient import exc LOG = logging.getLogger(__name__) supported_formats = { "json": lambda x: jsonutils.dumps(x, indent=2), "yaml": yaml.safe_dump } def arg(*args, **kwargs): """Decorator for CLI args. Example: >>> @arg("name", help="Name of the new entity") ... def entity_create(args): ... pass """ def _decorator(func): add_arg(func, *args, **kwargs) return func return _decorator def env(*args, **kwargs): """Returns the first environment variable set. If all are empty, defaults to '' or keyword arg `default`. """ for arg in args: value = os.environ.get(arg) if value: return value return kwargs.get('default', '') def add_arg(func, *args, **kwargs): """Bind CLI arguments to a shell.py `do_foo` function.""" if not hasattr(func, 'arguments'): func.arguments = [] # NOTE(sirp): avoid dups that can occur when the module is shared across # tests. if (args, kwargs) not in func.arguments: # Because of the semantics of decorator composition if we just append # to the options list positional options will appear to be backwards. func.arguments.insert(0, (args, kwargs)) def print_list(objs, fields, formatters=None, sortby_index=0, mixed_case_fields=None, field_labels=None): """Print a list of objects as a table, one row per object. :param objs: iterable of :class:`Resource` :param fields: attributes that correspond to columns, in order :param formatters: `dict` of callables for field formatting :param sortby_index: index of the field for sorting table rows :param mixed_case_fields: fields corresponding to object attributes that have mixed case names (e.g., 'serverId') :param field_labels: Labels to use in the heading of the table, default to fields. """ formatters = formatters or {} mixed_case_fields = mixed_case_fields or [] field_labels = field_labels or fields if len(field_labels) != len(fields): raise ValueError(_("Field labels list %(labels)s has different number " "of elements than fields list %(fields)s"), {'labels': field_labels, 'fields': fields}) if sortby_index is None: kwargs = {} else: kwargs = {'sortby': field_labels[sortby_index]} pt = prettytable.PrettyTable(field_labels) pt.align = 'l' for o in objs: row = [] for field in fields: if field in formatters: row.append(formatters[field](o)) else: if field in mixed_case_fields: field_name = field.replace(' ', '_') else: field_name = field.lower().replace(' ', '_') data = getattr(o, field_name, '') row.append(data) pt.add_row(row) if six.PY3: print(encodeutils.safe_encode(pt.get_string(**kwargs)).decode()) else: print(encodeutils.safe_encode(pt.get_string(**kwargs))) def link_formatter(links): def format_link(l): if 'rel' in l: return "%s (%s)" % (l.get('href', ''), l.get('rel', '')) else: return "%s" % (l.get('href', '')) return '\n'.join(format_link(l) for l in links or []) def resource_nested_identifier(rsrc): nested_link = [l for l in rsrc.links or [] if l.get('rel') == 'nested'] if nested_link: nested_href = nested_link[0].get('href') nested_identifier = nested_href.split("/")[-2:] return "/".join(nested_identifier) def json_formatter(js): return jsonutils.dumps(js, indent=2, ensure_ascii=False, separators=(', ', ': ')) def yaml_formatter(js): return yaml.safe_dump(js, default_flow_style=False) 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_dict(d, formatters=None): formatters = formatters or {} 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')) class EventLogContext(object): def __init__(self): # key is a stack id or the name of the nested stack, value is a tuple # of the parent stack id, and the name of the resource in the parent # stack self.id_to_res_info = {} def prepend_paths(self, resource_path, stack_id): if stack_id not in self.id_to_res_info: return stack_id, res_name = self.id_to_res_info.get(stack_id) if res_name in self.id_to_res_info: # do a double lookup to skip the ugly stack name that doesn't # correspond to an actual resource name n_stack_id, res_name = self.id_to_res_info.get(res_name) resource_path.insert(0, res_name) self.prepend_paths(resource_path, n_stack_id) elif res_name: resource_path.insert(0, res_name) def build_resource_name(self, event): res_name = getattr(event, 'resource_name') # Contribute this event to self.id_to_res_info to assist with # future calls to build_resource_name def get_stack_id(): if getattr(event, 'stack_id', None) is not None: return event.stack_id for l in getattr(event, 'links', []): if l.get('rel') == 'stack': if 'href' not in l: return None stack_link = l['href'] return stack_link.split('/')[-1] stack_id = get_stack_id() if not stack_id: return res_name phys_id = getattr(event, 'physical_resource_id', None) status = getattr(event, 'resource_status', None) is_stack_event = stack_id == phys_id if is_stack_event: # this is an event for a stack self.id_to_res_info[stack_id] = (stack_id, res_name) elif phys_id and status == 'CREATE_IN_PROGRESS': # this might be an event for a resource which creates a stack self.id_to_res_info[phys_id] = (stack_id, res_name) # Now build this resource path based on previous calls to # build_resource_name resource_path = [] if res_name and not is_stack_event: resource_path.append(res_name) self.prepend_paths(resource_path, stack_id) return '.'.join(resource_path) def event_log_formatter(events, event_log_context=None): """Return the events in log format.""" event_log = [] log_format = ("%(event_time)s " "[%(rsrc_name)s]: %(rsrc_status)s %(rsrc_status_reason)s") # It is preferable for a context to be passed in, but there might be enough # events in this call to build a better resource name, so create a context # anyway if event_log_context is None: event_log_context = EventLogContext() for event in events: rsrc_name = event_log_context.build_resource_name(event) event_time = getattr(event, 'event_time', '') log = log_format % { 'event_time': event_time.replace('T', ' '), 'rsrc_name': rsrc_name, 'rsrc_status': getattr(event, 'resource_status', ''), 'rsrc_status_reason': getattr(event, 'resource_status_reason', '') } event_log.append(log) return "\n".join(event_log) def print_update_list(lst, fields, formatters=None): """Print the stack-update --dry-run output as a table. This function is necessary to print the stack-update --dry-run output, which contains additional information about the update. """ formatters = formatters or {} pt = prettytable.PrettyTable(fields, caching=False, print_empty=False) pt.align = 'l' for change in lst: row = [] for field in fields: if field in formatters: row.append(formatters[field](change.get(field, None))) else: row.append(change.get(field, None)) pt.add_row(row) if six.PY3: print(encodeutils.safe_encode(pt.get_string()).decode()) else: print(encodeutils.safe_encode(pt.get_string())) 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 %(name)s with a name or ID of " "'%(name_or_id)s' exists.") % { 'name': manager.resource_class.__name__.lower(), 'name_or_id': name_or_id }) raise exc.CommandError(msg) def format_parameters(params, parse_semicolon=True): '''Reformat parameters into dict of format expected by the API.''' if not params: return {} if parse_semicolon: # 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 = _('Malformed parameter(%s). Use the key=value format.') % p 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_all_parameters(params, param_files, template_file=None, template_url=None): parameters = {} parameters.update(format_parameters(params)) parameters.update(format_parameter_file( param_files, template_file, template_url)) return parameters def format_parameter_file(param_files, template_file=None, template_url=None): '''Reformat file parameters into dict of format expected by the API.''' if not param_files: return {} params = format_parameters(param_files, False) template_base_url = None if template_file or template_url: template_base_url = base_url_for_url(get_template_url( template_file, template_url)) param_file = {} for key, value in params.items(): param_file[key] = resolve_param_get_file(value, template_base_url) return param_file def resolve_param_get_file(file, base_url): if base_url and not base_url.endswith('/'): base_url = base_url + '/' str_url = parse.urljoin(base_url, file) return read_url_content(str_url) 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) def parse_query_url(url): base_url, query_params = url.split('?') return base_url, parse.parse_qs(query_params) def get_template_url(template_file=None, template_url=None): if template_file: template_url = normalise_file_path_to_url(template_file) return template_url def read_url_content(url): try: content = request.urlopen(url).read() except error.URLError: raise exc.CommandError(_('Could not fetch contents for %s') % url) if content: try: content.decode('utf-8') except ValueError: content = base64.encodestring(content) return content def base_url_for_url(url): parsed = parse.urlparse(url) parsed_dir = os.path.dirname(parsed.path) return parse.urljoin(url, parsed_dir) def normalise_file_path_to_url(path): if parse.urlparse(path).scheme: return path path = os.path.abspath(path) return parse.urljoin('file:', request.pathname2url(path)) def get_response_body(resp): 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 body python-heatclient-1.14.0/heatclient/common/template_format.py0000666000175100017510000000513313234643054024435 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import yaml from heatclient._i18n import _ 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. """ # strip any whitespace before the check tmpl_str = tmpl_str.strip() if tmpl_str.startswith('{'): tpl = json.loads(tmpl_str) else: try: tpl = yaml.load(tmpl_str, Loader=yaml_loader) except yaml.YAMLError: # NOTE(prazumovsky): we need to return more informative error for # user, so use SafeLoader, which return error message with template # snippet where error has been occurred. try: tpl = yaml.load(tmpl_str, Loader=yaml.SafeLoader) 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-1.14.0/heatclient/common/__init__.py0000666000175100017510000000000013234643054022775 0ustar zuulzuul00000000000000python-heatclient-1.14.0/heatclient/common/template_utils.py0000666000175100017510000003420713234643054024311 0ustar zuulzuul00000000000000# 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 collections from oslo_serialization import jsonutils import six from six.moves.urllib import error from six.moves.urllib import parse from six.moves.urllib import request from heatclient._i18n import _ from heatclient.common import environment_format from heatclient.common import template_format from heatclient.common import utils from heatclient import exc def process_template_path(template_path, object_request=None, existing=False): """Read template from template path. Attempt to read template first as a file or url. If that is unsuccessful, try again assuming path is to a template object. :param template_path: local or uri path to template :param object_request: custom object request function used to get template if local or uri path fails :param existing: if the current stack's template should be used :returns: get_file dict and template contents :raises: error.URLError """ try: return get_template_contents(template_file=template_path, existing=existing) except error.URLError as template_file_exc: try: return get_template_contents(template_object=template_path, object_request=object_request, existing=existing) except exc.HTTPNotFound: # The initial exception gives the user better failure context. raise template_file_exc def get_template_contents(template_file=None, template_url=None, template_object=None, object_request=None, files=None, existing=False): is_object = False # Transform a bare file path to a file:// URL. if template_file: template_url = utils.normalise_file_path_to_url(template_file) if template_url: tpl = request.urlopen(template_url).read() elif template_object: is_object = True template_url = template_object tpl = object_request and object_request('GET', template_object) elif existing: return {}, None else: raise exc.CommandError(_('Need to specify exactly one of ' '[%(arg1)s, %(arg2)s or %(arg3)s]' ' or %(arg4)s') % { 'arg1': '--template-file', 'arg2': '--template-url', 'arg3': '--template-object', 'arg4': '--existing'}) 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 %(url)s %(error)s') % {'url': template_url, 'error': e}) tmpl_base_url = utils.base_url_for_url(template_url) if files is None: files = {} resolve_template_get_files(template, files, tmpl_base_url, is_object, object_request) return files, template def resolve_template_get_files(template, files, template_base_url, is_object=False, object_request=None): def ignore_if(key, value): if key != 'get_file' and key != 'type': return True if not isinstance(value, six.string_types): return True if (key == 'type' and not value.endswith(('.yaml', '.template'))): return True return False def recurse_if(value): return isinstance(value, (dict, list)) get_file_contents(template, files, template_base_url, ignore_if, recurse_if, is_object, object_request) def is_template(file_content): try: if isinstance(file_content, six.binary_type): file_content = file_content.decode('utf-8') template_format.parse(file_content) except (ValueError, TypeError): return False return True def get_file_contents(from_data, files, base_url=None, ignore_if=None, recurse_if=None, is_object=False, object_request=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, is_object, object_request) if isinstance(from_data, dict): for key, value in 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 = parse.urljoin(base_url, value) if str_url not in files: if is_object and object_request: file_content = object_request('GET', str_url) else: file_content = utils.read_url_content(str_url) if is_template(file_content): if is_object: template = get_template_contents( template_object=str_url, files=files, object_request=object_request)[1] else: template = get_template_contents( template_url=str_url, files=files)[1] file_content = jsonutils.dumps(template) files[str_url] = file_content # replace the data value with the normalised absolute URL from_data[key] = str_url def read_url_content(url): '''DEPRECATED! Use 'utils.read_url_content' instead.''' return utils.read_url_content(url) def base_url_for_url(url): '''DEPRECATED! Use 'utils.base_url_for_url' instead.''' return utils.base_url_for_url(url) def normalise_file_path_to_url(path): '''DEPRECATED! Use 'utils.normalise_file_path_to_url' instead.''' return utils.normalise_file_path_to_url(path) def deep_update(old, new): '''Merge nested dictionaries.''' # Prevents an error if in a previous iteration # old[k] = None but v[k] = {...}, if old is None: old = {} for k, v in new.items(): if isinstance(v, collections.Mapping): r = deep_update(old.get(k, {}), v) old[k] = r elif v is None and isinstance(old.get(k), collections.Mapping): # Don't override empty data, to work around yaml syntax issue pass else: old[k] = new[k] return old def process_multiple_environments_and_files(env_paths=None, template=None, template_url=None, env_path_is_object=None, object_request=None, env_list_tracker=None): """Reads one or more environment files. Reads in each specified environment file and returns a dictionary of the filenames->contents (suitable for the files dict) and the consolidated environment (after having applied the correct overrides based on order). If a list is provided in the env_list_tracker parameter, the behavior is altered to take advantage of server-side environment resolution. Specifically, this means: * Populating env_list_tracker with an ordered list of environment file URLs to be passed to the server * Including the contents of each environment file in the returned files dict, keyed by one of the URLs in env_list_tracker :param env_paths: list of paths to the environment files to load; if None, empty results will be returned :type env_paths: list or None :param template: unused; only included for API compatibility :param template_url: unused; only included for API compatibility :param env_list_tracker: if specified, environment filenames will be stored within :type env_list_tracker: list or None :return: tuple of files dict and a dict of the consolidated environment :rtype: tuple """ merged_files = {} merged_env = {} # If we're keeping a list of environment files separately, include the # contents of the files in the files dict include_env_in_files = env_list_tracker is not None if env_paths: for env_path in env_paths: files, env = process_environment_and_files( env_path=env_path, template=template, template_url=template_url, env_path_is_object=env_path_is_object, object_request=object_request, include_env_in_files=include_env_in_files) # 'files' looks like {"filename1": contents, "filename2": contents} # so a simple update is enough for merging merged_files.update(files) # 'env' can be a deeply nested dictionary, so a simple update is # not enough merged_env = deep_update(merged_env, env) if env_list_tracker is not None: env_url = utils.normalise_file_path_to_url(env_path) env_list_tracker.append(env_url) return merged_files, merged_env def process_environment_and_files(env_path=None, template=None, template_url=None, env_path_is_object=None, object_request=None, include_env_in_files=False): """Loads a single environment file. Returns an entry suitable for the files dict which maps the environment filename to its contents. :param env_path: full path to the file to load :type env_path: str or None :param include_env_in_files: if specified, the raw environment file itself will be included in the returned files dict :type include_env_in_files: bool :return: tuple of files dict and the loaded environment as a dict :rtype: (dict, dict) """ files = {} env = {} is_object = env_path_is_object and env_path_is_object(env_path) if is_object: raw_env = object_request and object_request('GET', env_path) env = environment_format.parse(raw_env) env_base_url = utils.base_url_for_url(env_path) resolve_environment_urls( env.get('resource_registry'), files, env_base_url, is_object=True, object_request=object_request) elif env_path: env_url = utils.normalise_file_path_to_url(env_path) env_base_url = utils.base_url_for_url(env_url) raw_env = request.urlopen(env_url).read() env = environment_format.parse(raw_env) resolve_environment_urls( env.get('resource_registry'), files, env_base_url) if include_env_in_files: files[env_url] = jsonutils.dumps(env) return files, env def resolve_environment_urls(resource_registry, files, env_base_url, is_object=False, object_request=None): """Handles any resource URLs specified in an environment. :param resource_registry: mapping of type name to template filename :type resource_registry: dict :param files: dict to store loaded file contents into :type files: dict :param env_base_url: base URL to look in when loading files :type env_base_url: str or None """ 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 if key in ['hooks', 'restricted_actions']: return True get_file_contents(rr, files, base_url, ignore_if, is_object=is_object, object_request=object_request) for res_name, res_dict in 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, is_object=is_object, object_request=object_request) def hooks_to_env(env, arg_hooks, hook): """Add hooks from args to environment's resource_registry section. Hooks are either "resource_name" (if it's a top-level resource) or "nested_stack/resource_name" (if the resource is in a nested stack). The environment expects each hook to be associated with the resource within `resource_registry/resources` using the `hooks: pre-create` format. """ if 'resource_registry' not in env: env['resource_registry'] = {} if 'resources' not in env['resource_registry']: env['resource_registry']['resources'] = {} for hook_declaration in arg_hooks: hook_path = [r for r in hook_declaration.split('/') if r] resources = env['resource_registry']['resources'] for nested_stack in hook_path: if nested_stack not in resources: resources[nested_stack] = {} resources = resources[nested_stack] else: resources['hooks'] = hook python-heatclient-1.14.0/heatclient/common/resource_formatter.py0000666000175100017510000001263713234643054025173 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import collections import hashlib from cliff.formatters import base class ResourceDotInfo(object): def __init__(self, res): self.resource = res links = {l['rel']: l['href'] for l in res.links} self.nested_dot_id = self.dot_id(links.get('nested'), 'stack') self.stack_dot_id = self.dot_id(links.get('stack'), 'stack') self.res_dot_id = self.dot_id(links.get('self')) @staticmethod def dot_id(url, prefix=None): """Build an id with a prefix and a truncated hash of the URL""" if not url: return None if not prefix: prefix = 'r' hash_object = hashlib.sha256(url.encode('utf-8')) return '%s_%s' % (prefix, hash_object.hexdigest()[:20]) class ResourceDotFormatter(base.ListFormatter): def add_argument_group(self, parser): pass def emit_list(self, column_names, data, stdout, parsed_args): writer = ResourceDotWriter(data, stdout) writer.write() class ResourceDotWriter(object): def __init__(self, data, stdout): self.resources_by_stack = collections.defaultdict( collections.OrderedDict) self.resources_by_dot_id = collections.OrderedDict() self.nested_stack_ids = [] self.stdout = stdout for r in data: rinfo = ResourceDotInfo(r) if rinfo.stack_dot_id: self.resources_by_stack[ rinfo.stack_dot_id][r.resource_name] = rinfo if rinfo.res_dot_id: self.resources_by_dot_id[rinfo.res_dot_id] = rinfo if rinfo.nested_dot_id: self.nested_stack_ids.append(rinfo.nested_dot_id) def write(self): stdout = self.stdout stdout.write('digraph G {\n') stdout.write(' graph [\n' ' fontsize=10 fontname="Verdana" ' 'compound=true rankdir=LR\n' ' ]\n') self.write_root_nodes() self.write_subgraphs() self.write_nested_stack_edges() self.write_required_by_edges() stdout.write('}\n') def write_root_nodes(self): for stack_dot_id in set(self.resources_by_stack.keys()).difference( self.nested_stack_ids): resources = self.resources_by_stack[stack_dot_id] self.write_nodes(resources, 2) def write_subgraphs(self): for dot_id, rinfo in self.resources_by_dot_id.items(): if rinfo.nested_dot_id: resources = self.resources_by_stack[rinfo.nested_dot_id] if resources: self.write_subgraph(resources, rinfo) def write_nodes(self, resources, indent): stdout = self.stdout spaces = ' ' * indent for rinfo in resources.values(): r = rinfo.resource dot_id = rinfo.res_dot_id if r.resource_status.endswith('FAILED'): style = 'style=filled color=red' else: style = '' stdout.write('%s%s [label="%s\n%s" %s];\n' % (spaces, dot_id, r.resource_name, r.resource_type, style)) stdout.write('\n') def write_subgraph(self, resources, nested_resource): stdout = self.stdout stack_dot_id = nested_resource.nested_dot_id nested_name = nested_resource.resource.resource_name stdout.write(' subgraph cluster_%s {\n' % stack_dot_id) stdout.write(' label="%s";\n' % nested_name) self.write_nodes(resources, 4) stdout.write(' }\n\n') def write_required_by_edges(self): stdout = self.stdout for dot_id, rinfo in self.resources_by_dot_id.items(): r = rinfo.resource required_by = r.required_by stack_dot_id = rinfo.stack_dot_id if not required_by or not stack_dot_id: continue stack_resources = self.resources_by_stack.get(stack_dot_id, {}) for req in required_by: other_rinfo = stack_resources.get(req) if other_rinfo: stdout.write(' %s -> %s;\n' % (rinfo.res_dot_id, other_rinfo.res_dot_id)) stdout.write('\n') def write_nested_stack_edges(self): stdout = self.stdout for dot_id, rinfo in self.resources_by_dot_id.items(): if rinfo.nested_dot_id: nested_resources = self.resources_by_stack[rinfo.nested_dot_id] if nested_resources: first_resource = list(nested_resources.values())[0] stdout.write( ' %s -> %s [\n color=dimgray lhead=cluster_%s ' 'arrowhead=none\n ];\n' % (dot_id, first_resource.res_dot_id, rinfo.nested_dot_id)) stdout.write('\n') python-heatclient-1.14.0/heatclient/common/environment_format.py0000666000175100017510000000436113234643054025170 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import yaml from heatclient._i18n import _ from heatclient.common import template_format SECTIONS = ( PARAMETER_DEFAULTS, PARAMETERS, RESOURCE_REGISTRY, ENCRYPTED_PARAM_NAMES, EVENT_SINKS, PARAMETER_MERGE_STRATEGIES ) = ( 'parameter_defaults', 'parameters', 'resource_registry', 'encrypted_param_names', 'event_sinks', 'parameter_merge_strategies' ) 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=template_format.yaml_loader) except yaml.YAMLError: # NOTE(prazumovsky): we need to return more informative error for # user, so use SafeLoader, which return error message with template # snippet where error has been occurred. try: env = yaml.load(env_str, Loader=yaml.SafeLoader) 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 and param != PARAMETER_MERGE_STRATEGIES: if param in (ENCRYPTED_PARAM_NAMES, EVENT_SINKS): env[param] = [] else: env[param] = {} python-heatclient-1.14.0/heatclient/common/format_utils.py0000666000175100017510000000522613234643054023765 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # # Copyright 2015 IBM Corp. import sys from osc_lib.command import command import six class RawFormat(command.ShowOne): def produce_output(self, parsed_args, column_names, data): if data is None: return self.formatter.emit_one(column_names, data, self.app.stdout, parsed_args) class JsonFormat(RawFormat): @property def formatter_default(self): return 'json' class YamlFormat(RawFormat): @property def formatter_default(self): return 'yaml' class ShellFormat(RawFormat): @property def formatter_default(self): return 'shell' class ValueFormat(RawFormat): @property def formatter_default(self): return 'value' def indent_and_truncate(txt, spaces=0, truncate=False, truncate_limit=10, truncate_prefix=None, truncate_postfix=None): """Indents supplied multiline text by the specified number of spaces """ if txt is None: return lines = six.text_type(txt).splitlines() if truncate and len(lines) > truncate_limit: lines = lines[-truncate_limit:] if truncate_prefix is not None: lines.insert(0, truncate_prefix) if truncate_postfix is not None: lines.append(truncate_postfix) if spaces > 0: lines = [" " * spaces + line for line in lines] return '\n'.join(lines) def print_software_deployment_output(data, name, out=sys.stdout, long=False): """Prints details of the software deployment for user consumption The format attempts to be valid yaml, but is primarily aimed at showing useful information to the user in a helpful layout. """ if data is None: data = {} if name in ('deploy_stdout', 'deploy_stderr'): output = indent_and_truncate( data.get(name), spaces=4, truncate=not long, truncate_prefix='...', truncate_postfix='(truncated, view all with --long)') out.write(' %s: |\n%s\n' % (name, output)) else: out.write(' %s: %s\n' % (name, data.get(name))) python-heatclient-1.14.0/heatclient/common/base.py0000666000175100017510000004152513234643054022171 0ustar zuulzuul00000000000000# 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. """ import abc import copy from oslo_utils import reflection from oslo_utils import strutils import six from six.moves.urllib import parse from heatclient._i18n import _ from heatclient import exc as exceptions 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=None, 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'. If response_key is None - all response body will be used. :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] if response_key is not None else body # 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=None): """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'. If response_key is None - all response body will be used. """ body = self.client.get(url).json() data = body[response_key] if response_key is not None else body return self.resource_class(self, data, 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=None, 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., 'server'. If response_key is None - all response body will be used. :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() data = body[response_key] if response_key is not None else body if return_raw: return data return self.resource_class(self, data) 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'. If response_key is None - all response body will be used. """ 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'. If response_key is None - all response body will be used. """ 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 %(name)s matching %(args)s.") % { 'name': self.resource_class.__name__, 'args': 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 kwargs.copy().items(): 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' % parse.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' % parse.urlencode(kwargs) if kwargs else '', }, self.collection_key) num = len(rl) if num == 0: msg = _("No %(name)s matching %(args)s.") % { 'name': self.resource_class.__name__, 'args': kwargs } raise exceptions.NotFound(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) class_name = reflection.get_class_name(self, fully_qualified=False) return "<%s %s>" % (class_name, info) @property def human_id(self): """Human-readable ID which can be used for bash completion. """ if self.HUMAN_ID: name = getattr(self, self.NAME_ATTR, None) if name is not None: return strutils.to_slug(name) return None def _add_details(self, info): for (k, v) in info.items(): 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): """Support for lazy loading details. Some clients, such as novaclient have the option to lazy load the details, details which can be loaded with this function. """ # set_loaded() first ... so if we have to bail, we know we tried. self.set_loaded(True) if not hasattr(self.manager, 'get'): return new = self.manager.get(self.id) if new: self._add_details(new._info) self._add_details( {'x_request_id': self.manager.client.last_request_id}) 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 return self._info == other._info def __ne__(self, other): return not self.__eq__(other) def is_same_obj(self, other): """Identify the two objects are same one with same id.""" if isinstance(other, self.__class__): if hasattr(self, 'id') and hasattr(other, 'id'): return self.id == other.id return False def is_loaded(self): return self._loaded def set_loaded(self, val): self._loaded = val def to_dict(self): return copy.deepcopy(self._info) python-heatclient-1.14.0/heatclient/exc.py0000666000175100017510000001113513234643054020540 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import sys from oslo_serialization import jsonutils from oslo_utils import reflection from heatclient._i18n import _ 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, code=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__}} if self.code == "N/A" and code is not None: self.code = code def __str__(self): message = self.error['error'].get('message', 'Internal Error') if verbose: traceback = self.error['error'].get('traceback', '') return (_('ERROR: %(message)s\n%(traceback)s') % {'message': message, 'traceback': 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 (_("%(name)s (HTTP %(code)s) %(details)s") % { 'name': reflection.get_class_name(self, fully_qualified=False), 'code': self.code, 'details': 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 NoUniqueMatch(HTTPException): 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, response.status_code) class NoTokenLookupException(Exception): """DEPRECATED.""" pass class EndpointNotFound(Exception): """DEPRECATED.""" pass class StackFailure(Exception): pass python-heatclient-1.14.0/heatclient/__init__.py0000666000175100017510000000122413234643054021516 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import pbr.version __version__ = pbr.version.VersionInfo('python-heatclient').version_string() python-heatclient-1.14.0/heatclient/_i18n.py0000666000175100017510000000237113234643054020701 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """oslo.i18n integration module. See http://docs.openstack.org/developer/oslo.i18n/usage.html """ import oslo_i18n # NOTE(dhellmann): This reference to o-s-l-o will be replaced by the # application name when this module is synced into the separate # repository. It is OK to have more than one translation function # using the same domain, since there will still only be one message # catalog. _translators = oslo_i18n.TranslatorFactory(domain='heatclient') # The primary translation function using the well-known name "_" _ = _translators.primary def get_available_languages(): return oslo_i18n.get_available_languages('heatclient') def enable_lazy(): return oslo_i18n.enable_lazy() python-heatclient-1.14.0/heatclient/osc/0000775000175100017510000000000013234643356020175 5ustar zuulzuul00000000000000python-heatclient-1.14.0/heatclient/osc/v1/0000775000175100017510000000000013234643356020523 5ustar zuulzuul00000000000000python-heatclient-1.14.0/heatclient/osc/v1/stack_failures.py0000666000175100017510000001233413234643054024074 0ustar zuulzuul00000000000000# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import collections from osc_lib.command import command from heatclient._i18n import _ from heatclient.common import format_utils from heatclient import exc class ListStackFailures(command.Command): """Show information about failed stack resources.""" def take_action(self, parsed_args): self.heat_client = self.app.client_manager.orchestration failures = self._build_failed_resources(parsed_args.stack) deployment_failures = self._build_software_deployments(failures) self._print_failures(failures, deployment_failures, long=parsed_args.long) def get_parser(self, prog_name): parser = super(ListStackFailures, self).get_parser(prog_name) parser.add_argument( 'stack', metavar='', help=_('Stack to display (name or ID)'), ) parser.add_argument( '--long', action='store_true', default=False, help=_('Show full deployment logs in output'), ) return parser def _build_failed_resources(self, stack): """List information about FAILED stack resources. Failed resources are added by recursing from the top level stack into failed nested stack resources. A failed nested stack resource is only added to the failed list if it contains no failed resources. """ s = self.heat_client.stacks.get(stack) if s.status != 'FAILED': return [] resources = self.heat_client.resources.list(s.id) failures = collections.OrderedDict() self._append_failed_resources(failures, resources, [s.stack_name]) return failures def _append_failed_resources(self, failures, resources, resource_path): """Recursively build list of failed resources.""" appended = False for r in resources: if not r.resource_status.endswith('FAILED'): continue # determine if this resources is a nested stack links_rel = list([l['rel'] for l in r.links]) is_nested = 'nested' in links_rel nested_appended = False next_resource_path = list(resource_path) next_resource_path.append(r.resource_name) if is_nested: try: nested_resources = self.heat_client.resources.list( r.physical_resource_id) nested_appended = self._append_failed_resources( failures, nested_resources, next_resource_path) except exc.HTTPNotFound: # there is a failed resource but no stack pass if not nested_appended: failures['.'.join(next_resource_path)] = r appended = True return appended def _build_software_deployments(self, resources): """Build a dict of software deployments from the supplied resources. The key is the deployment ID. """ df = {} if not resources: return df for r in resources.values(): if r.resource_type not in ('OS::Heat::StructuredDeployment', 'OS::Heat::SoftwareDeployment'): continue try: sd = self.heat_client.software_deployments.get( deployment_id=r.physical_resource_id) df[r.physical_resource_id] = sd except exc.HTTPNotFound: pass return df def _print_failures(self, failures, deployment_failures, long=False): """Print failed resources. If the resource is a deployment resource, look up the deployment and print deploy_stdout and deploy_stderr. """ out = self.app.stdout if not failures: return for k, f in failures.items(): out.write('%s:\n' % k) out.write(' resource_type: %s\n' % f.resource_type) out.write(' physical_resource_id: %s\n' % f.physical_resource_id) out.write(' status: %s\n' % f.resource_status) reason = format_utils.indent_and_truncate( f.resource_status_reason, spaces=4, truncate=not long, truncate_prefix='...\n') out.write(' status_reason: |\n%s\n' % reason) df = deployment_failures.get(f.physical_resource_id) if df: for output in ('deploy_stdout', 'deploy_stderr'): format_utils.print_software_deployment_output( data=df.output_values, name=output, long=long, out=out) python-heatclient-1.14.0/heatclient/osc/v1/snapshot.py0000666000175100017510000001756113234643054022743 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # """Orchestration v1 Stack Snapshot implementations.""" import logging import sys from osc_lib.command import command from osc_lib import exceptions as exc from osc_lib.i18n import _ from osc_lib import utils import six from heatclient.common import format_utils from heatclient import exc as heat_exc class ListSnapshot(command.Lister): """List stack snapshots.""" log = logging.getLogger(__name__ + ".ListSnapshot") def get_parser(self, prog_name): parser = super(ListSnapshot, self).get_parser(prog_name) parser.add_argument( 'stack', metavar='', help=_('Name or ID of stack containing the snapshots') ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) heat_client = self.app.client_manager.orchestration return self._list_snapshot(heat_client, parsed_args) def _list_snapshot(self, heat_client, parsed_args): fields = {'stack_id': parsed_args.stack} try: snapshots = heat_client.stacks.snapshot_list(**fields) except heat_exc.HTTPNotFound: raise exc.CommandError(_('Stack not found: %s') % parsed_args.stack) columns = ['id', 'name', 'status', 'status_reason', 'creation_time'] return ( columns, (utils.get_dict_properties(s, columns) for s in snapshots['snapshots']) ) class ShowSnapshot(format_utils.YamlFormat): """Show stack snapshot.""" log = logging.getLogger(__name__ + ".ShowSnapshot") def get_parser(self, prog_name): parser = super(ShowSnapshot, self).get_parser(prog_name) parser.add_argument( 'stack', metavar='', help=_('Name or ID of stack containing the snapshot') ) parser.add_argument( 'snapshot', metavar='', help=_('ID of the snapshot to show') ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) heat_client = self.app.client_manager.orchestration return self._show_snapshot(heat_client, parsed_args.stack, parsed_args.snapshot) def _show_snapshot(self, heat_client, stack_id, snapshot_id): try: data = heat_client.stacks.snapshot_show(stack_id, snapshot_id) except heat_exc.HTTPNotFound: raise exc.CommandError(_('Snapshot ID <%(snapshot_id)s> not found ' 'for stack <%(stack_id)s>') % {'snapshot_id': snapshot_id, 'stack_id': stack_id}) rows = list(six.itervalues(data)) columns = list(six.iterkeys(data)) return columns, rows class RestoreSnapshot(command.Command): """Restore stack snapshot""" log = logging.getLogger(__name__ + ".RestoreSnapshot") def get_parser(self, prog_name): parser = super(RestoreSnapshot, self).get_parser(prog_name) parser.add_argument( 'stack', metavar='', help=_('Name or ID of stack containing the snapshot') ) parser.add_argument( 'snapshot', metavar='', help=_('ID of the snapshot to restore') ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) heat_client = self.app.client_manager.orchestration return self._restore_snapshot(heat_client, parsed_args) def _restore_snapshot(self, heat_client, parsed_args): fields = {'stack_id': parsed_args.stack, 'snapshot_id': parsed_args.snapshot} try: heat_client.stacks.restore(**fields) except heat_exc.HTTPNotFound: raise exc.CommandError(_('Stack %(stack)s or ' 'snapshot %(snapshot)s not found.') % {'stack': parsed_args.stack, 'snapshot': parsed_args.snapshot}) class CreateSnapshot(command.ShowOne): """Create stack snapshot.""" log = logging.getLogger(__name__ + ".CreateSnapshot") def get_parser(self, prog_name): parser = super(CreateSnapshot, self).get_parser(prog_name) parser.add_argument( 'stack', metavar='', help=_('Name or ID of stack') ) parser.add_argument( '--name', metavar='', help=_('Name of snapshot') ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) heat_client = self.app.client_manager.orchestration try: data = heat_client.stacks.snapshot(parsed_args.stack, parsed_args.name) except heat_exc.HTTPNotFound: raise exc.CommandError(_('Stack not found: %s') % parsed_args.stack) columns = [ 'ID', 'name', 'status', 'status_reason', 'data', 'creation_time' ] return (columns, utils.get_dict_properties(data, columns)) class DeleteSnapshot(command.Command): """Delete stack snapshot.""" log = logging.getLogger(__name__ + ".DeleteSnapshot") def get_parser(self, prog_name): parser = super(DeleteSnapshot, self).get_parser(prog_name) parser.add_argument( 'stack', metavar='', help=_('Name or ID of stack') ) parser.add_argument( 'snapshot', metavar='', help=_('ID of stack snapshot') ) parser.add_argument( '-y', '--yes', action='store_true', help=_('Skip yes/no prompt (assume yes)') ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) heat_client = self.app.client_manager.orchestration msg = ('User did not confirm snapshot delete ' '%sso taking no action.') try: if not parsed_args.yes and sys.stdin.isatty(): sys.stdout.write( _('Are you sure you want to delete the snapshot of this ' 'stack [Y/N]?')) prompt_response = sys.stdin.readline().lower() if not prompt_response.startswith('y'): self.log.info(msg, '') return except KeyboardInterrupt: # ctrl-c self.log.info(msg, '(ctrl-c) ') return except EOFError: # ctrl-d self.log.info(msg, '(ctrl-d) ') return try: heat_client.stacks.snapshot_delete(parsed_args.stack, parsed_args.snapshot) except heat_exc.HTTPNotFound: raise exc.CommandError(_('Snapshot ID <%(snapshot_id)s> not found ' 'for stack <%(stack_id)s>') % {'snapshot_id': parsed_args.snapshot, 'stack_id': parsed_args.stack}) python-heatclient-1.14.0/heatclient/osc/v1/event.py0000666000175100017510000002042513234643054022216 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # # Copyright 2015 IBM Corp. import logging import time from cliff.formatters import base from osc_lib.command import command from osc_lib import utils from heatclient._i18n import _ from heatclient.common import event_utils from heatclient.common import utils as heat_utils from heatclient import exc class ShowEvent(command.ShowOne): """Show event details.""" log = logging.getLogger(__name__ + '.ShowEvent') def get_parser(self, prog_name): parser = super(ShowEvent, self).get_parser(prog_name) parser.add_argument( 'stack', metavar='', help=_('Name or ID of stack to show events for') ) parser.add_argument( 'resource', metavar='', help=_('Name of the resource event belongs to') ) parser.add_argument( 'event', metavar='', help=_('ID of event to display details for') ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) client = self.app.client_manager.orchestration fields = { 'stack_id': parsed_args.stack, 'resource_name': parsed_args.resource, 'event_id': parsed_args.event } try: client.stacks.get(parsed_args.stack) client.resources.get(parsed_args.stack, parsed_args.resource) event = client.events.get(**fields) except exc.HTTPNotFound as ex: raise exc.CommandError(str(ex)) formatters = { 'links': heat_utils.link_formatter, 'resource_properties': heat_utils.json_formatter } columns = [] for key in event.to_dict(): columns.append(key) return columns, utils.get_item_properties(event, columns, formatters=formatters) class ListEvent(command.Lister): """List events.""" log = logging.getLogger(__name__ + '.ListEvent') @property def formatter_default(self): return 'log' @property def formatter_namespace(self): return 'heatclient.event.formatter.list' def get_parser(self, prog_name): parser = super(ListEvent, self).get_parser(prog_name) parser.add_argument( 'stack', metavar='', help=_('Name or ID of stack to show events for') ) parser.add_argument( '--resource', metavar='', help=_('Name of resource to show events for. Note: this cannot ' 'be specified with --nested-depth') ) parser.add_argument( '--filter', metavar='', action='append', help=_('Filter parameters to apply on returned events') ) parser.add_argument( '--limit', metavar='', type=int, help=_('Limit the number of events returned') ) parser.add_argument( '--marker', metavar='', help=_('Only return events that appear after the given ID') ) parser.add_argument( '--nested-depth', metavar='', type=int, help=_('Depth of nested stacks from which to display events. ' 'Note: this cannot be specified with --resource') ) parser.add_argument( '--sort', metavar='[:]', action='append', help=_('Sort output by selected keys and directions (asc or desc) ' '(default: asc). Specify multiple times to sort on ' 'multiple keys. Sort key can be: ' '"event_time" (default), "resource_name", "links", ' '"logical_resource_id", "resource_status", ' '"resource_status_reason", "physical_resource_id", or ' '"id". You can leave the key empty and specify ":desc" ' 'for sorting by reverse time.') ) parser.add_argument( '--follow', action='store_true', help=_('Print events until process is halted') ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) client = self.app.client_manager.orchestration columns = ['id', 'resource_status', 'resource_status_reason', 'event_time', 'physical_resource_id'] kwargs = { 'resource_name': parsed_args.resource, 'filters': heat_utils.format_parameters(parsed_args.filter), 'sort_dir': 'asc' } if parsed_args.resource and parsed_args.nested_depth: msg = _('--nested-depth cannot be specified with --resource') raise exc.CommandError(msg) if parsed_args.nested_depth: columns.append('stack_name') nested_depth = parsed_args.nested_depth else: nested_depth = 0 if parsed_args.sort: sorts = [] sort_keys = [] for sort in parsed_args.sort: if sort.startswith(":"): sorts.append(":".join(["event_time", sort.lstrip(":")])) else: sorts.append(sort) sort_keys.append(sort.split(":")[0]) kwargs['sort_keys'] = sort_keys if ":" in parsed_args.sort[0]: kwargs['sort_dir'] = parsed_args.sort[0].split(":")[1] if parsed_args.follow: if parsed_args.formatter != 'log': msg = _('--follow can only be specified with --format log') raise exc.CommandError(msg) marker = parsed_args.marker try: event_log_context = heat_utils.EventLogContext() while True: events = event_utils.get_events( client, stack_id=parsed_args.stack, event_args=kwargs, nested_depth=nested_depth, marker=marker) if events: marker = getattr(events[-1], 'id', None) events_log = heat_utils.event_log_formatter( events, event_log_context) self.app.stdout.write(events_log) self.app.stdout.write('\n') time.sleep(5) # this loop never exits except (KeyboardInterrupt, EOFError): # ctrl-c, ctrl-d return [], [] events = event_utils.get_events( client, stack_id=parsed_args.stack, event_args=kwargs, nested_depth=nested_depth, marker=parsed_args.marker, limit=parsed_args.limit) if parsed_args.sort: events = utils.sort_items(events, ','.join(sorts)) if parsed_args.formatter == 'log': return [], events if len(events): if hasattr(events[0], 'resource_name'): columns.insert(0, 'resource_name') columns.append('logical_resource_id') else: columns.insert(0, 'logical_resource_id') return ( columns, (utils.get_item_properties(s, columns) for s in events) ) class LogFormatter(base.ListFormatter): """A formatter which prints event objects in a log style""" def add_argument_group(self, parser): pass def emit_list(self, column_names, data, stdout, parsed_args): stdout.write(heat_utils.event_log_formatter(data)) stdout.write('\n') python-heatclient-1.14.0/heatclient/osc/v1/software_config.py0000666000175100017510000001650013234643054024253 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # """Orchestration v1 software config action implementations""" import logging from osc_lib.command import command from osc_lib import exceptions as exc from osc_lib import utils import six from six.moves.urllib import request import yaml from heatclient._i18n import _ from heatclient.common import format_utils from heatclient.common import template_format from heatclient.common import utils as heat_utils from heatclient import exc as heat_exc class DeleteConfig(command.Command): """Delete software configs""" log = logging.getLogger(__name__ + ".DeleteConfig") def get_parser(self, prog_name): parser = super(DeleteConfig, self).get_parser(prog_name) parser.add_argument( 'config', metavar='', nargs='+', help=_('IDs of the software configs to delete') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) heat_client = self.app.client_manager.orchestration return _delete_config(heat_client, parsed_args) def _delete_config(heat_client, args): failure_count = 0 for config_id in args.config: try: heat_client.software_configs.delete( config_id=config_id) except Exception as e: if isinstance(e, heat_exc.HTTPNotFound): print(_('Software config with ID %s not found') % config_id) failure_count += 1 continue if failure_count: raise exc.CommandError(_('Unable to delete %(count)s of the ' '%(total)s software configs.') % {'count': failure_count, 'total': len(args.config)}) class ListConfig(command.Lister): """List software configs""" log = logging.getLogger(__name__ + ".ListConfig") def get_parser(self, prog_name): parser = super(ListConfig, self).get_parser(prog_name) parser.add_argument( '--limit', metavar='', help=_('Limit the number of configs returned') ) parser.add_argument( '--marker', metavar='', help=_('Return configs that appear after the given config ID') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) heat_client = self.app.client_manager.orchestration return _list_config(heat_client, parsed_args) def _list_config(heat_client, args): kwargs = {} if args.limit: kwargs['limit'] = args.limit if args.marker: kwargs['marker'] = args.marker scs = heat_client.software_configs.list(**kwargs) columns = ['id', 'name', 'group', 'creation_time'] return (columns, (utils.get_item_properties(s, columns) for s in scs)) class CreateConfig(format_utils.JsonFormat): """Create software config""" log = logging.getLogger(__name__ + ".CreateConfig") def get_parser(self, prog_name): parser = super(CreateConfig, self).get_parser(prog_name) parser.add_argument( 'name', metavar='', help=_('Name of the software config to create') ) parser.add_argument( '--config-file', metavar='', help=_('Path to JSON/YAML containing map defining ' ', , and ') ) parser.add_argument( '--definition-file', metavar='', help=_('Path to software config script/data') ) parser.add_argument( '--group', metavar='', default='Heat::Ungrouped', help=_('Group name of tool expected by the software config') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) heat_client = self.app.client_manager.orchestration return _create_config(heat_client, parsed_args) def _create_config(heat_client, args): config = { 'group': args.group, 'config': '' } defn = {} if args.definition_file: defn_url = heat_utils.normalise_file_path_to_url( args.definition_file) defn_raw = request.urlopen(defn_url).read() or '{}' defn = yaml.load(defn_raw, Loader=template_format.yaml_loader) config['inputs'] = defn.get('inputs', []) config['outputs'] = defn.get('outputs', []) config['options'] = defn.get('options', {}) if args.config_file: config_url = heat_utils.normalise_file_path_to_url( args.config_file) config['config'] = request.urlopen(config_url).read() # build a mini-template with a config resource and validate it validate_template = { 'heat_template_version': '2013-05-23', 'resources': { args.name: { 'type': 'OS::Heat::SoftwareConfig', 'properties': config } } } heat_client.stacks.validate(template=validate_template) config['name'] = args.name sc = heat_client.software_configs.create(**config).to_dict() rows = list(six.itervalues(sc)) columns = list(six.iterkeys(sc)) return columns, rows class ShowConfig(format_utils.YamlFormat): """Show software config details""" log = logging.getLogger(__name__ + ".ShowConfig") def get_parser(self, prog_name): parser = super(ShowConfig, self).get_parser(prog_name) parser.add_argument( 'config', metavar='', help=_('ID of the config') ) parser.add_argument( '--config-only', default=False, action="store_true", help=_('Only display the value of the property.') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) heat_client = self.app.client_manager.orchestration return _show_config(heat_client, config_id=parsed_args.config, config_only=parsed_args.config_only) def _show_config(heat_client, config_id, config_only): try: sc = heat_client.software_configs.get(config_id=config_id) except heat_exc.HTTPNotFound: raise exc.CommandError(_('Configuration not found: %s') % config_id) columns = None rows = None if config_only: print(sc.config) else: columns = ( 'id', 'name', 'group', 'config', 'inputs', 'outputs', 'options', 'creation_time', ) rows = utils.get_dict_properties(sc.to_dict(), columns) return columns, rows python-heatclient-1.14.0/heatclient/osc/v1/software_deployment.py0000666000175100017510000003132513234643054025170 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # """Orchestration v1 Software Deployment action implementations""" import logging from osc_lib.command import command from osc_lib import exceptions as exc from osc_lib import utils from oslo_serialization import jsonutils from heatclient._i18n import _ from heatclient.common import deployment_utils from heatclient.common import format_utils from heatclient.common import utils as heat_utils from heatclient import exc as heat_exc class CreateDeployment(format_utils.YamlFormat): """Create a software deployment.""" log = logging.getLogger(__name__ + '.CreateDeployment') def get_parser(self, prog_name): parser = super(CreateDeployment, self).get_parser(prog_name) parser.add_argument( 'name', metavar='', help=_('Name of the derived config associated with this ' 'deployment. This is used to apply a sort order to the ' 'list of configurations currently deployed to the server.') ) parser.add_argument( '--input-value', metavar='', action='append', help=_('Input value to set on the deployment. This can be ' 'specified multiple times.') ) parser.add_argument( '--action', metavar='', default='UPDATE', help=_('Name of an action for this deployment. This can be a ' 'custom action, or one of CREATE, UPDATE, DELETE, SUSPEND, ' 'RESUME. Default is UPDATE') ) parser.add_argument( '--config', metavar='', help=_('ID of the configuration to deploy') ) parser.add_argument( '--signal-transport', metavar='', default='TEMP_URL_SIGNAL', help=_('How the server should signal to heat with the deployment ' 'output values. TEMP_URL_SIGNAL will create a Swift ' 'TempURL to be signaled via HTTP PUT. ZAQAR_SIGNAL will ' 'create a dedicated zaqar queue to be signaled using the ' 'provided keystone credentials.NO_SIGNAL will result in ' 'the resource going to the COMPLETE state without waiting ' 'for any signal') ) parser.add_argument( '--container', metavar='', help=_('Optional name of container to store TEMP_URL_SIGNAL ' 'objects in. If not specified a container will be created ' 'with a name derived from the DEPLOY_NAME') ) parser.add_argument( '--timeout', metavar='', type=int, default=60, help=_('Deployment timeout in minutes') ) parser.add_argument( '--server', metavar='', required=True, help=_('ID of the server being deployed to') ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) client = self.app.client_manager.orchestration config = {} if parsed_args.config: try: config = client.software_configs.get(parsed_args.config) except heat_exc.HTTPNotFound: msg = (_('Software configuration not found: %s') % parsed_args.config) raise exc.CommandError(msg) derived_params = deployment_utils.build_derived_config_params( parsed_args.action, config, parsed_args.name, heat_utils.format_parameters(parsed_args.input_value, False), parsed_args.server, parsed_args.signal_transport, signal_id=deployment_utils.build_signal_id(client, parsed_args) ) derived_config = client.software_configs.create(**derived_params) sd = client.software_deployments.create( config_id=derived_config.id, server_id=parsed_args.server, action=parsed_args.action, status='IN_PROGRESS' ) return zip(*sorted(sd.to_dict().items())) class DeleteDeployment(command.Command): """Delete software deployment(s) and correlative config(s).""" log = logging.getLogger(__name__ + '.DeleteDeployment') def get_parser(self, prog_name): parser = super(DeleteDeployment, self).get_parser(prog_name) parser.add_argument( 'deployment', metavar='', nargs='+', help=_('ID of the deployment(s) to delete.') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) hc = self.app.client_manager.orchestration failure_count = 0 for deploy_id in parsed_args.deployment: try: sd = hc.software_deployments.get(deployment_id=deploy_id) hc.software_deployments.delete( deployment_id=deploy_id) except Exception as e: if isinstance(e, heat_exc.HTTPNotFound): print(_('Deployment with ID %s not found') % deploy_id) else: print(_('Deployment with ID %s failed to delete') % deploy_id) failure_count += 1 continue # just try best to delete the corresponding config try: config_id = getattr(sd, 'config_id') hc.software_configs.delete(config_id=config_id) except Exception: print(_('Failed to delete the correlative config' ' %(config_id)s of deployment %(deploy_id)s') % {'config_id': config_id, 'deploy_id': deploy_id}) if failure_count: raise exc.CommandError(_('Unable to delete %(count)s of the ' '%(total)s deployments.') % {'count': failure_count, 'total': len(parsed_args.deployment)}) class ListDeployment(command.Lister): """List software deployments.""" log = logging.getLogger(__name__ + '.ListDeployment') def get_parser(self, prog_name): parser = super(ListDeployment, self).get_parser(prog_name) parser.add_argument( '--server', metavar='', help=_('ID of the server to fetch deployments for') ) parser.add_argument( '--long', action='store_true', help=_('List more fields in output') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) heat_client = self.app.client_manager.orchestration return _list_deployment(heat_client, args=parsed_args) def _list_deployment(heat_client, args=None): kwargs = {'server_id': args.server} if args.server else {} columns = ['id', 'config_id', 'server_id', 'action', 'status'] if args.long: columns.append('creation_time') columns.append('status_reason') deployments = heat_client.software_deployments.list(**kwargs) return ( columns, (utils.get_item_properties(s, columns) for s in deployments) ) class ShowDeployment(command.ShowOne): """Show SoftwareDeployment Details.""" log = logging.getLogger(__name__ + ".ShowSoftwareDeployment") def get_parser(self, prog_name): parser = super(ShowDeployment, self).get_parser(prog_name) parser.add_argument( 'deployment', metavar='', help=_('ID of the deployment') ) parser.add_argument( '--long', action='store_true', help=_('Show more fields in output') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) heat_client = self.app.client_manager.orchestration try: data = heat_client.software_deployments.get( deployment_id=parsed_args.deployment) except heat_exc.HTTPNotFound: raise exc.CommandError( _('Software Deployment not found: %s') % parsed_args.deployment) else: columns = [ 'id', 'server_id', 'config_id', 'creation_time', 'updated_time', 'status', 'status_reason', 'input_values', 'action', ] if parsed_args.long: columns.append('output_values') return columns, utils.get_item_properties(data, columns) class ShowMetadataDeployment(command.Command): """Get deployment configuration metadata for the specified server.""" log = logging.getLogger(__name__ + '.ShowMetadataDeployment') def get_parser(self, prog_name): parser = super(ShowMetadataDeployment, self).get_parser(prog_name) parser.add_argument( 'server', metavar='', help=_('ID of the server to fetch deployments for') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) heat_client = self.app.client_manager.orchestration md = heat_client.software_deployments.metadata( server_id=parsed_args.server) print(jsonutils.dumps(md, indent=2)) class ShowOutputDeployment(command.Command): """Show a specific deployment output.""" log = logging.getLogger(__name__ + '.ShowOutputDeployment') def get_parser(self, prog_name): parser = super(ShowOutputDeployment, self).get_parser(prog_name) parser.add_argument( 'deployment', metavar='', help=_('ID of deployment to show the output for') ) parser.add_argument( 'output', metavar='', nargs='?', default=None, help=_('Name of an output to display') ) parser.add_argument( '--all', default=False, action='store_true', help=_('Display all deployment outputs') ) parser.add_argument( '--long', action='store_true', default=False, help='Show full deployment logs in output', ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) heat_client = self.app.client_manager.orchestration if (not parsed_args.all and parsed_args.output is None or parsed_args.all and parsed_args.output is not None): raise exc.CommandError( _('Error: either %(output)s or %(all)s argument is needed.') % {'output': '', 'all': '--all'}) try: sd = heat_client.software_deployments.get( deployment_id=parsed_args.deployment) except heat_exc.HTTPNotFound: raise exc.CommandError(_('Deployment not found: %s') % parsed_args.deployment) outputs = sd.output_values if outputs: if parsed_args.all: print('output_values:\n') for k in outputs.keys(): format_utils.print_software_deployment_output( data=outputs, name=k, long=parsed_args.long) else: if parsed_args.output not in outputs: msg = (_('Output %(output)s does not exist in deployment' ' %(deployment)s') % {'output': parsed_args.output, 'deployment': parsed_args.deployment}) raise exc.CommandError(msg) else: print('output_value:\n') format_utils.print_software_deployment_output( data=outputs, name=parsed_args.output) python-heatclient-1.14.0/heatclient/osc/v1/resource.py0000666000175100017510000002357713234643054022737 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # """Orchestration v1 Stack action implementations""" import logging from osc_lib.command import command from osc_lib import exceptions as exc from osc_lib.i18n import _ from osc_lib import utils from oslo_serialization import jsonutils import six from six.moves.urllib import request from heatclient.common import format_utils from heatclient.common import utils as heat_utils from heatclient import exc as heat_exc class ResourceShow(command.ShowOne): """Display stack resource.""" log = logging.getLogger(__name__ + '.ResourceShowStack') def get_parser(self, prog_name): parser = super(ResourceShow, self).get_parser(prog_name) parser.add_argument( 'stack', metavar='', help=_('Name or ID of stack to query') ) parser.add_argument( 'resource', metavar='', help=_('Name of resource') ) parser.add_argument( '--with-attr', metavar='', action='append', help=_('Attribute to show, can be specified multiple times') ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) client = self.app.client_manager.orchestration try: resource = client.resources.get(parsed_args.stack, parsed_args.resource, with_attr=parsed_args.with_attr) except heat_exc.HTTPNotFound: msg = (_('Stack or resource not found: %(stack)s %(resource)s') % {'stack': parsed_args.stack, 'resource': parsed_args.resource}) raise exc.CommandError(msg) return self.dict2columns(resource.to_dict()) class ResourceList(command.Lister): """List stack resources.""" log = logging.getLogger(__name__ + '.ResourceListStack') @property def formatter_namespace(self): return 'heatclient.resource.formatter.list' def get_parser(self, prog_name): parser = super(ResourceList, self).get_parser(prog_name) parser.add_argument( 'stack', metavar='', help=_('Name or ID of stack to query') ) parser.add_argument( '--long', action='store_true', help=_('Enable detailed information presented for each resource ' 'in resource list') ) parser.add_argument( '-n', '--nested-depth', metavar='', type=int, help=_('Depth of nested stacks from which to display resources') ) parser.add_argument( '--filter', metavar='', action='append', help=_('Filter parameters to apply on returned resources based on ' 'their name, status, type, action, id and ' 'physical_resource_id') ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) client = self.app.client_manager.orchestration fields = { 'nested_depth': parsed_args.nested_depth, 'with_detail': parsed_args.long, 'filters': heat_utils.format_parameters(parsed_args.filter), } try: resources = client.resources.list(parsed_args.stack, **fields) except heat_exc.HTTPNotFound: msg = _('Stack not found: %s') % parsed_args.stack raise exc.CommandError(msg) if parsed_args.formatter == 'dot': return [], resources columns = ['physical_resource_id', 'resource_type', 'resource_status', 'updated_time'] if len(resources) >= 1 and not hasattr(resources[0], 'resource_name'): columns.insert(0, 'logical_resource_id') else: columns.insert(0, 'resource_name') if parsed_args.nested_depth or parsed_args.long: columns.append('stack_name') return ( columns, (utils.get_item_properties(r, columns) for r in resources) ) class ResourceMetadata(format_utils.JsonFormat): """Show resource metadata""" log = logging.getLogger(__name__ + ".ResourceMetadata") def get_parser(self, prog_name): parser = super(ResourceMetadata, self).get_parser(prog_name) parser.add_argument( 'stack', metavar='', help=_('Stack to display (name or ID)'), ) parser.add_argument( 'resource', metavar='', help=_('Name of the resource to show the metadata for')) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) heat_client = self.app.client_manager.orchestration return _resource_metadata(heat_client, parsed_args) def _resource_metadata(heat_client, args): fields = {'stack_id': args.stack, 'resource_name': args.resource} try: metadata = heat_client.resources.metadata(**fields) except heat_exc.HTTPNotFound: raise exc.CommandError(_('Stack %(stack)s or resource %(resource)s ' 'not found.') % {'stack': args.stack, 'resource': args.resource}) data = list(six.itervalues(metadata)) columns = list(six.iterkeys(metadata)) return columns, data class ResourceSignal(command.Command): """Signal a resource with optional data.""" log = logging.getLogger(__name__ + ".ResourceSignal") def get_parser(self, prog_name): parser = super(ResourceSignal, self).get_parser(prog_name) parser.add_argument( 'stack', metavar='', help=_('Name or ID of stack the resource belongs to'), ) parser.add_argument( 'resource', metavar='', help=_('Name of the resoure to signal'), ) parser.add_argument( '--data', metavar='', help=_('JSON Data to send to the signal handler') ) parser.add_argument( '--data-file', metavar='', help=_('File containing JSON data to send to the signal handler') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) heat_client = self.app.client_manager.orchestration return _resource_signal(heat_client, parsed_args) def _resource_signal(heat_client, args): fields = {'stack_id': args.stack, 'resource_name': args.resource} data = args.data data_file = args.data_file if data and data_file: raise exc.CommandError(_('Should only specify one of data or ' 'data-file')) if data_file: data_url = heat_utils.normalise_file_path_to_url(data_file) data = request.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: heat_client.resources.signal(**fields) except heat_exc.HTTPNotFound: raise exc.CommandError(_('Stack %(stack)s or resource %(resource)s ' 'not found.') % {'stack': args.stack, 'resource': args.resource}) class ResourceMarkUnhealthy(command.Command): """Set resource's health.""" log = logging.getLogger(__name__ + ".ResourceMarkUnhealthy") def get_parser(self, prog_name): parser = super(ResourceMarkUnhealthy, self).get_parser(prog_name) parser.add_argument( 'stack', metavar='', help=_('Name or ID of stack the resource belongs to') ) parser.add_argument( 'resource', metavar='', help=_('Name of the resource') ) parser.add_argument( 'reason', default="", nargs='?', help=_('Reason for state change') ) parser.add_argument( '--reset', default=False, action="store_true", help=_('Set the resource as healthy') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) heat_client = self.app.client_manager.orchestration fields = {'stack_id': parsed_args.stack, 'resource_name': parsed_args.resource, 'mark_unhealthy': not parsed_args.reset, 'resource_status_reason': parsed_args.reason} try: heat_client.resources.mark_unhealthy(**fields) except heat_exc.HTTPNotFound: raise exc.CommandError(_('Stack or resource not found: ' '%(id)s %(resource)s') % {'id': parsed_args.stack, 'resource': parsed_args.resource}) python-heatclient-1.14.0/heatclient/osc/v1/__init__.py0000666000175100017510000000000013234643054022617 0ustar zuulzuul00000000000000python-heatclient-1.14.0/heatclient/osc/v1/resource_type.py0000666000175100017510000001114713234643054023766 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # """Orchestration v1 resource type implementations""" import logging from osc_lib.command import command from osc_lib import exceptions as exc from osc_lib.i18n import _ import six from heatclient.common import format_utils from heatclient.common import utils as heat_utils from heatclient import exc as heat_exc class ResourceTypeShow(format_utils.YamlFormat): """Show details and optionally generate a template for a resource type.""" log = logging.getLogger(__name__ + ".ResourceTypeShow") def get_parser(self, prog_name): parser = super(ResourceTypeShow, self).get_parser(prog_name) parser.add_argument( 'resource_type', metavar='', help=_('Resource type to show details for'), ) parser.add_argument( '--template-type', metavar='', help=_('Optional template type to generate, hot or cfn') ) parser.add_argument( '--long', default=False, action='store_true', help=_('Show resource type with corresponding description.') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) if parsed_args.template_type is not None and parsed_args.long: msg = _('Cannot use --template-type and --long in one time.') raise exc.CommandError(msg) heat_client = self.app.client_manager.orchestration return _show_resourcetype(heat_client, parsed_args) def _show_resourcetype(heat_client, parsed_args): try: if parsed_args.template_type: template_type = parsed_args.template_type.lower() if template_type not in ('hot', 'cfn'): raise exc.CommandError( _('Template type invalid: %s') % parsed_args.template_type) fields = {'resource_type': parsed_args.resource_type, 'template_type': template_type} data = heat_client.resource_types.generate_template(**fields) else: data = heat_client.resource_types.get(parsed_args.resource_type, parsed_args.long) except heat_exc.HTTPNotFound: raise exc.CommandError( _('Resource type not found: %s') % parsed_args.resource_type) rows = list(six.itervalues(data)) columns = list(six.iterkeys(data)) return columns, rows class ResourceTypeList(command.Lister): """List resource types.""" log = logging.getLogger(__name__ + '.ResourceTypeList') def get_parser(self, prog_name): parser = super(ResourceTypeList, self).get_parser(prog_name) parser.add_argument( '--filter', dest='filter', metavar='', help=_('Filter parameters to apply on returned resource types. ' 'This can be specified multiple times. It can be any of ' 'name, version or support_status'), action='append' ) parser.add_argument( '--long', default=False, action='store_true', help=_('Show resource types with corresponding description of ' 'each resource type.') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) heat_client = self.app.client_manager.orchestration return _list_resourcetypes(heat_client, parsed_args) def _list_resourcetypes(heat_client, parsed_args): resource_types = heat_client.resource_types.list( filters=heat_utils.format_parameters(parsed_args.filter), with_description=parsed_args.long ) if parsed_args.long: columns = ['Resource Type', 'Description'] rows = sorted([r.resource_type, r.description] for r in resource_types) else: columns = ['Resource Type'] rows = sorted([r.resource_type] for r in resource_types) return columns, rows python-heatclient-1.14.0/heatclient/osc/v1/service.py0000666000175100017510000000255113234643054022535 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # """Orchestration v1 Service action implementations""" import logging from osc_lib.command import command from osc_lib import utils class ListService(command.Lister): """List the Heat engines.""" log = logging.getLogger(__name__ + ".ListService") def get_parser(self, prog_name): parser = super(ListService, self).get_parser(prog_name) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) heat_client = self.app.client_manager.orchestration columns = ['Hostname', 'Binary', 'Engine ID', 'Host', 'Topic', 'Updated At', 'Status'] services = heat_client.services.list() return ( columns, (utils.get_item_properties(s, columns) for s in services) ) python-heatclient-1.14.0/heatclient/osc/v1/stack.py0000666000175100017510000013621113234643054022203 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # """Orchestration v1 Stack action implementations""" import logging import sys from osc_lib.command import command from osc_lib import exceptions as exc from osc_lib import utils from oslo_serialization import jsonutils import six from six.moves.urllib import request from heatclient._i18n import _ from heatclient.common import event_utils from heatclient.common import format_utils from heatclient.common import hook_utils from heatclient.common import http from heatclient.common import template_utils from heatclient.common import utils as heat_utils from heatclient import exc as heat_exc class CreateStack(command.ShowOne): """Create a stack.""" log = logging.getLogger(__name__ + '.CreateStack') def get_parser(self, prog_name): parser = super(CreateStack, self).get_parser(prog_name) parser.add_argument( '-e', '--environment', metavar='', action='append', help=_('Path to the environment. Can be specified multiple times') ) parser.add_argument( '--timeout', metavar='', type=int, help=_('Stack creating timeout in minutes') ) parser.add_argument( '--pre-create', metavar='', default=None, action='append', help=_('Name of a resource to set a pre-create hook to. Resources ' 'in nested stacks can be set using slash as a separator: ' 'nested_stack/another/my_resource. You can use wildcards ' 'to match multiple stacks or resources: ' 'nested_stack/an*/*_resource. This can be specified ' 'multiple times') ) parser.add_argument( '--enable-rollback', action='store_true', help=_('Enable rollback on create/update failure') ) parser.add_argument( '--parameter', metavar='', action='append', help=_('Parameter values used to create the stack. This can be ' 'specified multiple times') ) parser.add_argument( '--parameter-file', metavar='', action='append', help=_('Parameter values from file used to create the stack. ' 'This can be specified multiple times. Parameter values ' 'would be the content of the file') ) parser.add_argument( '--wait', action='store_true', help=_('Wait until stack goes to CREATE_COMPLETE or CREATE_FAILED') ) parser.add_argument( '--tags', metavar='', help=_('A list of tags to associate with the stack') ) parser.add_argument( '--dry-run', action='store_true', help=_('Do not actually perform the stack create, but show what ' 'would be created') ) parser.add_argument( 'name', metavar='', help=_('Name of the stack to create') ) parser.add_argument( '-t', '--template', metavar='