././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1709284674.571469 python-heatclient-3.5.0/0000775000175000017500000000000000000000000015201 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/.coveragerc0000664000175000017500000000015700000000000017325 0ustar00zuulzuul00000000000000[run] branch = True source = heatclient omit = heatclient/tests/* [report] ignore_errors = True precision = 2 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/.stestr.conf0000664000175000017500000000011000000000000017442 0ustar00zuulzuul00000000000000[DEFAULT] test_path=${OS_TEST_PATH:-./heatclient/tests/unit} top_dir=./ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/.zuul.yaml0000664000175000017500000000142400000000000017143 0ustar00zuulzuul00000000000000- job: name: heatclient-functional parent: devstack-tox-functional timeout: 4200 required-projects: - openstack/heat - openstack/python-heatclient vars: openrc_enable_export: true devstack_plugins: heat: https://opendev.org/openstack/heat irrelevant-files: - ^(test-|)requirements.txt$ - ^setup.cfg$ - ^doc/.*$ - ^.*\.rst$ - ^releasenotes/.*$ - ^heatclient/tests/.*$ - project: templates: - openstack-cover-jobs - openstack-python3-jobs - check-requirements - openstackclient-plugin-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 check: jobs: - heatclient-functional gate: jobs: - heatclient-functional ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284674.0 python-heatclient-3.5.0/AUTHORS0000664000175000017500000001640500000000000016257 0ustar00zuulzuul00000000000000Adrien 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 Bernhard M. Wiedemann Bin Zhou Bo Wang Bryan D. Payne Bryan Jones Cao Xuan Hoang Chen ChenZheng Chmouel Boudjnah Christian Berendt Chuck Short Clayton O'Neill Clenimar Filemon Clint Byrum Corey Bryant 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 Ghanshyam Mann Gábor Antal Herman Ge Hervé Beraud Ian Main Ian Wienand Itxaka Ivan Kolodyazhny JUN JIE NAN JUNJIE NAN James E. Blair 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 Luigi Toscano M V P Nitesh Mark Vanderwiel Masahito Muroi Mehdi Abaakouk Monty Taylor Nachi Ueno Nam Nguyen Hoai Naohiro Tamura Nguyen Hai Oleg Khavroshin Oleksii Chuprykov Ondřej Nový OpenStack Release Bot Pablo Andres Fuente Pablo Iranzo Gómez PanFengyun PanFengyun Paul Breaux Paul Van Eck Pavlo Shchelokovskyy 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 Sean McGinnis 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 Kajinami Takashi Kajinami Takashi NATSUME Tang Chen Tetiana Lashchova Tetyana Lashchova Thomas Bechtold Thomas Goirand Thomas Herve Thomas Herve Tim Schnell Tomas Sedovic Tomas Sedovic Tovin Seven Unmesh Gurjar Van Hung Pham Victor Morales Vieri <15050873171@163.com> Vijayaguru Guruchave Vijendar Komalla Vijendra Soni Vu Cong Tuan Winnie Tsang Zane Bitter Zhang Yang ZhiQiang Fan ZhiQiang Fan ZhijunWei anusha-rayani-7 bhagyashris blue55 cedric.brandily chenhaiq chenxiao dengzhaosen dixiaoli gong yong sheng huang.zhiping huangtianhua jacky06 ji-xuepeng kairat_kushaev likui lingyongxu liu-sheng liuqing liyi llg8212 lvdongbing neetu niuke rabi rajat29 rico.lin ricolin ricolin ricolin ricolin root shizhihui shu-mutou sonu.kumar sridhargaddam sunjia tamilhce tanlin tengqm ting.wang xiaolihope yuyafei zengyingzhe zhang-jinnan zhangboye zhangguoqing zhufl zhulingjie zzxwill ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/CONTRIBUTING.rst0000664000175000017500000000124100000000000017640 0ustar00zuulzuul00000000000000The source repository for this project can be found at: https://opendev.org/openstack/python-heatclient Pull requests submitted through GitHub are not monitored. To start contributing to OpenStack, follow the steps in the contribution guide to set up and use Gerrit: https://docs.openstack.org/contributors/code-and-documentation/quick-start.html Bugs should be filed on Storyboard,: https://storyboard.openstack.org/#!/project/openstack/python-heatclient For more specific information about contributing to this repository, see the python-heatclient contributor guide: https://docs.openstack.org/python-heatclient/latest/contributor/contributing.html ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284674.0 python-heatclient-3.5.0/ChangeLog0000664000175000017500000011377000000000000016764 0ustar00zuulzuul00000000000000CHANGES ======= 3.5.0 ----- * reno: Update master for unmaintained/yoga * Bump hacking 3.4.0 ----- * Remove translation sections from setup.cfg * Add Python 3.11 to supported runtimes * Update master for stable/2023.2 * Fix release note generation * Remove some notes no longer valid 3.3.0 ----- * Add Python 3.10 to supported runtime * Update master for stable/2023.1 3.2.0 ----- * Adapt to tox 4 * Switch to 2023.1 Python3 unit tests and generic template name * Make tests pass after 2038 * Fix osc sd create command * Imported Translations from Zanata * Update master for stable/zed * remove unicode prefix from code (2) * remove unicode prefix from code (1) 3.1.0 ----- * Accept sha256 hash for swift tempurl * Remove leftover of Python <= 2.6 support 3.0.0 ----- * Drop lower-constraints.txt and its testing * Encode urls in unit tests * The Python 3.6 and Python 3.7 Support has been dropped since zed * Bump tox minversion to 3.18.0 * Remove six * Add Python3 zed unit tests * Update master for stable/yoga 2.5.1 ----- * setup.cfg: Replace dashes by underscores 2.5.0 ----- * Update python testing classifier * Replace deprecated import of ABCs from collections * Fix test and lower constraints * Add Python3 yoga unit tests * Update master for stable/xena 2.4.0 ----- * Replace oslo\_utils.fnmatch with fnmatch * fix lower constraint job * [ussuri][goal] Update contributor documentation * Add Python3 xena unit tests * Update master for stable/wallaby * Replace deprecated UPPER\_CONSTRAINTS\_FILE variable 2.3.0 ----- * Python 3.9: base64.encodestring is removed * Add Python3 wallaby unit tests * Update master for stable/victoria * [goal] Migrate testing to ubuntu focal 2.2.1 ----- * Native Zuul v3 version of the functional legacy job 2.2.0 ----- * Stop to use the \_\_future\_\_ module * Switch to newer openstackdocstheme and reno versions * Fix hacking min version to 3.0.1 * Use unittest.mock instead of third party mock * Bump default tox env from py37 to py38 * Add py38 package metadata * Add Python3 victoria unit tests * Update master for stable/ussuri 2.1.0 ----- * Update hacking for Python3 * Remove dependency on mox3 * Remove mox usage from test\_shell 2.0.0 ----- * [ussuri][goal] Drop python 2.7 support and testing * tests: Convert 'test\_resources' to mock * tests: Pre-mox removal cleanup * Cleanup of .keys() from dict\_object.keys() \*in\* operator * Allow to set poll interval to OSC stack create * Update master for stable/train 1.18.0 ------ * Update stack\_id clearify for heat client functions * Replace git.openstack.org URLs with opendev.org URLs * Fix unit tests * Add Python 3 Train unit tests * Cap sphinx for py2 to match global requirements * Fix missing print format * OpenDev Migration Patch * Dropping the py35 testing * Replace openstack.org git:// URLs with https:// * Update master for stable/stein * Set Content-Type header explictly for SessionClient * add python 3.7 unit test job * Ignore not found when delete in test 1.17.0 ------ * Update the bugs link to storyboard * Change openstack-dev to openstack-discuss * Update min tox version to 2.0 * Replace deprecated "decodestring()" by "decodebytes()" * Don't quote {posargs} in tox.ini * Set X-Region-Name in header when using SessionClient * Trivial: Update pypi url to new url * Use templates for cover and lower-constraints * Don't change pyyaml behavior * Migrate the link of bug report button to storyboard * Add files-container option for stack create and update * add python 3.6 unit test job * switch documentation job to new PTI * import zuul job settings from project-config * fix bug link in readme * Update reno for stable/rocky * Switch to stestr 1.16.1 ------ * Load files from adopt file when adopt stack * Handle token\_endpoint auth type in osc plugin * Update the documentation link for doc migration 1.16.0 ------ * Mark stacks returned by get loaded * Remove PyPI downloads * fix tox python3 overrides * Fix osc cli docs 1.15.0 ------ * Change testenv from py34 to py35 * Use assertRegex instead of assertRegexpMatches * add lower-constraints job * Improve titles of generated API documentation * Add heat CLI test for stack-show * Follow the new PTI for document build * Updated from global requirements * Updated from global requirements * Updated from global requirements * Update reno for stable/queens * Fix log\_http\_request function in http module 1.14.0 ------ * Updated from global requirements * Zuul: Remove project name * Document osc CLI 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 * [policy in code] Change policy description * 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 * Mark python-heatclient as stable * 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 * Using fixtures instead of deprecated mockpatch module * 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 * Enable some off-by-default checks * 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/LICENSE0000664000175000017500000002363600000000000016220 0ustar00zuulzuul00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1709284674.571469 python-heatclient-3.5.0/PKG-INFO0000664000175000017500000000565400000000000016310 0ustar00zuulzuul00000000000000Metadata-Version: 1.2 Name: python-heatclient Version: 3.5.0 Summary: OpenStack Orchestration API Client Library Home-page: https://docs.openstack.org/python-heatclient/latest Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-heatclient.svg :target: https://governance.openstack.org/tc/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.org/project/python-heatclient/ :alt: Latest Version 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.org/project/python-heatclient .. _Online Documentation: https://docs.openstack.org/python-heatclient/latest .. _Launchpad project: https://launchpad.net/python-heatclient .. _Blueprints: https://blueprints.launchpad.net/python-heatclient .. _Bugs: https://storyboard.openstack.org/#!/project/openstack/python-heatclient .. _Source: https://opendev.org/openstack/python-heatclient .. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html .. _Specs: https://specs.openstack.org/openstack/heat-specs/ .. _Template: https://opendev.org/openstack/heat-templates/ Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console 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 :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: Implementation :: CPython Requires-Python: >=3.8 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/README.rst0000664000175000017500000000300300000000000016664 0ustar00zuulzuul00000000000000======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-heatclient.svg :target: https://governance.openstack.org/tc/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.org/project/python-heatclient/ :alt: Latest Version 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.org/project/python-heatclient .. _Online Documentation: https://docs.openstack.org/python-heatclient/latest .. _Launchpad project: https://launchpad.net/python-heatclient .. _Blueprints: https://blueprints.launchpad.net/python-heatclient .. _Bugs: https://storyboard.openstack.org/#!/project/openstack/python-heatclient .. _Source: https://opendev.org/openstack/python-heatclient .. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html .. _Specs: https://specs.openstack.org/openstack/heat-specs/ .. _Template: https://opendev.org/openstack/heat-templates/ ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1709284674.5514681 python-heatclient-3.5.0/doc/0000775000175000017500000000000000000000000015746 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/doc/.gitignore0000664000175000017500000000002300000000000017731 0ustar00zuulzuul00000000000000build/ source/ref/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/doc/Makefile0000664000175000017500000000616000000000000017411 0ustar00zuulzuul00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXSOURCE = source PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SPHINXSOURCE) .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-heatclient.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-heatclient.qhc" latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/doc/requirements.txt0000664000175000017500000000020300000000000021225 0ustar00zuulzuul00000000000000openstackdocstheme>=2.2.1 # Apache-2.0 reno>=3.1.0 # Apache-2.0 sphinx>=2.0.0,!=2.1.0 # BSD sphinxcontrib-httpdomain>=1.3.0 # BSD ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1709284674.5514681 python-heatclient-3.5.0/doc/source/0000775000175000017500000000000000000000000017246 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1709284674.5514681 python-heatclient-3.5.0/doc/source/cli/0000775000175000017500000000000000000000000020015 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/doc/source/cli/index.rst0000664000175000017500000000012500000000000021654 0ustar00zuulzuul00000000000000============ Command List ============ .. toctree:: :glob: :maxdepth: 3 * ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/doc/source/cli/orchestration.rst0000664000175000017500000000131100000000000023427 0ustar00zuulzuul00000000000000============= orchestration ============= .. autoprogram-cliff:: openstack.orchestration.v1 :command: orchestration template validate .. autoprogram-cliff:: openstack.orchestration.v1 :command: orchestration template version list .. autoprogram-cliff:: openstack.orchestration.v1 :command: orchestration template function list .. autoprogram-cliff:: openstack.orchestration.v1 :command: orchestration resource type list .. autoprogram-cliff:: openstack.orchestration.v1 :command: orchestration resource type show .. autoprogram-cliff:: openstack.orchestration.v1 :command: orchestration build info .. autoprogram-cliff:: openstack.orchestration.v1 :command: orchestration service list ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/doc/source/cli/software_config.rst0000664000175000017500000000061100000000000023724 0ustar00zuulzuul00000000000000=============== software config =============== .. autoprogram-cliff:: openstack.orchestration.v1 :command: software config list .. autoprogram-cliff:: openstack.orchestration.v1 :command: software config create .. autoprogram-cliff:: openstack.orchestration.v1 :command: software config show .. autoprogram-cliff:: openstack.orchestration.v1 :command: software config delete ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/doc/source/cli/software_deployment.rst0000664000175000017500000000114700000000000024644 0ustar00zuulzuul00000000000000=================== software deployment =================== .. autoprogram-cliff:: openstack.orchestration.v1 :command: software deployment list .. autoprogram-cliff:: openstack.orchestration.v1 :command: software deployment create .. autoprogram-cliff:: openstack.orchestration.v1 :command: software deployment show .. autoprogram-cliff:: openstack.orchestration.v1 :command: software deployment output show .. autoprogram-cliff:: openstack.orchestration.v1 :command: software deployment delete .. autoprogram-cliff:: openstack.orchestration.v1 :command: software deployment metadata show ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/doc/source/cli/stack.rst0000664000175000017500000000536100000000000021661 0ustar00zuulzuul00000000000000===== stack ===== Basic actions ------------- .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack list .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack create .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack update .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack cancel .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack show .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack delete Outputs ------- .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack output list .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack output show Resources --------- .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack resource list .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack resource show .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack resource metadata .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack resource mark unhealthy .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack resource signal Events ------ .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack event list .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack event show .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack failures list Input data ---------- .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack template show .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack environment show .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack file list User hooks ---------- .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack hook poll .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack hook clear Other actions ------------- .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack check .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack suspend .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack resume .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack export .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack abandon .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack adopt Snapshots --------- .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack snapshot list .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack snapshot create .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack snapshot restore .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack snapshot show .. autoprogram-cliff:: openstack.orchestration.v1 :command: stack snapshot delete ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/doc/source/conf.py0000664000175000017500000002005200000000000020544 0ustar00zuulzuul00000000000000# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # # python-heatclient documentation build configuration file, created by # sphinx-quickstart on Sun Dec 6 14:19:25 2009. # # This file is execfile()d with the current directory set to its containing # dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.append(os.path.abspath('.')) exec(open(os.path.join("ext", "gen_ref.py")).read()) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'openstackdocstheme', 'cliff.sphinxext'] # Add any paths that contain templates here, relative to this directory. templates_path = [] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' # General information about the project. copyright = 'OpenStack Contributors' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['**/#*', '**~', '**/#*#'] # The reST default role (used for this markup: `text`) # to use for all documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'native' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] primary_domain = 'py' nitpicky = False # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme_path = ['.'] html_theme = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. html_theme_options = { "nosidebar": "false" } # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ['_static'] # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'python-heatclientdoc' # -- Options for LaTeX output ------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]) latex_documents = [ ('index', 'python-heatclient.tex', 'python-heatclient Documentation', 'OpenStack Foundation', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output ------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('man/heat', 'heat', 'Command line access to the heat project.', ['Heat Developers'], 1), ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ----------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'Heat', 'Heat Documentation', 'Heat Developers', 'Heat', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # -- Options for openstackdocstheme ------------------------------------------- openstackdocs_repo_name = 'openstack/python-heatclient' openstackdocs_use_storyboard = True ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1709284674.5514681 python-heatclient-3.5.0/doc/source/contributor/0000775000175000017500000000000000000000000021620 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/doc/source/contributor/contributing.rst0000664000175000017500000000373100000000000025065 0ustar00zuulzuul00000000000000============================ So You Want to Contribute... ============================ For general information on contributing to OpenStack, please check out the `contributor guide `_ to get started. It covers all the basics that are common to all OpenStack projects: the accounts you need, the basics of interacting with our Gerrit review system, how we communicate as a community, etc. Below will cover the more project specific information you need to get started with python-heatclient. Communication ~~~~~~~~~~~~~ * IRC channel #heat at OFTC * Mailing list (prefix subjects with ``[heat]`` for faster responses) http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-discuss Contacting the Core Team ~~~~~~~~~~~~~~~~~~~~~~~~ Please refer the `python-heatclient Core Team `_ contacts. New Feature Planning ~~~~~~~~~~~~~~~~~~~~ python-heatclient features are tracked on `Storyboard `_. Please specify the reported story with `python-heatclient`. Task Tracking ~~~~~~~~~~~~~ We track our tasks in `Storyboard `_. Please specify the reported story with `python-heatclient`. Reporting a Bug ~~~~~~~~~~~~~~~ You found an issue and want to make sure we are aware of it? You can do so on `Storyboard `_. Please specify the reported story with `python-heatclient`. Getting Your Patch Merged ~~~~~~~~~~~~~~~~~~~~~~~~~ All changes proposed to the python-heatclient project require one or two +2 votes from python-heatclient core reviewers before one of the core reviewers can approve patch by giving ``Workflow +1`` vote. Project Team Lead Duties ~~~~~~~~~~~~~~~~~~~~~~~~ All common PTL duties are enumerated in the `PTL guide `_. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1709284674.5514681 python-heatclient-3.5.0/doc/source/ext/0000775000175000017500000000000000000000000020046 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/doc/source/ext/gen_ref.py0000664000175000017500000000455600000000000022037 0ustar00zuulzuul00000000000000# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os import sys BASE_DIR = os.path.dirname(os.path.abspath(__file__)) ROOT = os.path.abspath(os.path.join(BASE_DIR, "..", "..", "..")) sys.path.insert(0, ROOT) sys.path.insert(0, BASE_DIR) def gen_ref(ver, title, names): refdir = os.path.join(BASE_DIR, "ref") pkg = "heatclient" if ver: pkg = "%s.%s" % (pkg, ver) refdir = os.path.join(refdir, ver) if not os.path.exists(refdir): os.makedirs(refdir) idxpath = os.path.join(refdir, "index.rst") with open(idxpath, "w") as idx: idx.write(("%(title)s\n" "%(signs)s\n" "\n" ".. toctree::\n" " :maxdepth: 1\n" "\n") % {"title": title, "signs": "=" * len(title)}) for name, sec_title in names.items(): idx.write(" %s\n" % name) rstpath = os.path.join(refdir, "%s.rst" % name) with open(rstpath, "w") as rst: rst.write(("%(title)s\n" "%(signs)s\n" "\n" ".. automodule:: %(pkg)s.%(name)s\n" " :members:\n" " :undoc-members:\n" " :show-inheritance:\n" " :noindex:\n") % {"title": sec_title, "signs": "=" * len(sec_title), "pkg": pkg, "name": name}) gen_ref("", "Client Reference", {"client": "Client", "exc": "Exceptions"}) gen_ref("v1", "Version 1 API Reference", {"stacks": "Stacks", "resources": "Resources", "events": "Events", "actions": "Actions", "software_configs": "Software Configs", "software_deployments": "Software Deployments"}) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/doc/source/index.rst0000664000175000017500000000750100000000000021112 0ustar00zuulzuul00000000000000Python bindings to the OpenStack Heat API ========================================= This is a client for OpenStack Heat API. There's a Python API (the :mod:`heatclient` module), a `python-openstackclient`_ plugin for command-line use, and a legacy command-line script (installed as :program:`heat`). ========== Python API ========== In order to use the python api directly, you must first obtain an auth token and identify which endpoint you wish to speak to:: >>> tenant_id = 'b363706f891f48019483f8bd6503c54b' >>> heat_url = 'http://heat.example.org:8004/v1/%s' % tenant_id >>> auth_token = '3bcc3d3a03f44e3d8377f9247b0ad155' Once you have done so, you can use the API like so:: >>> from heatclient.client import Client >>> heat = Client('1', endpoint=heat_url, token=auth_token) Alternatively, you can create a client instance using the keystoneauth session API:: >>> from keystoneauth1 import loading >>> from keystoneauth1 import session >>> from heatclient import client >>> loader = loading.get_plugin_loader('password') >>> auth = loader.load_from_options(auth_url=AUTH_URL, ... username=USERNAME, ... password=PASSWORD, ... project_id=PROJECT_ID) >>> sess = session.Session(auth=auth) >>> heat = client.Client('1', session=sess) >>> heat.stacks.list() If you have PROJECT_NAME instead of a PROJECT_ID, use the project_name parameter. Similarly, if your cloud uses keystone v3 and you have a DOMAIN_NAME or DOMAIN_ID, provide it as `user_domain_(name|id)` and if you are using a PROJECT_NAME also provide the domain information as `project_domain_(name|id)`. For more information on keystoneauth API, see `Using Sessions`_. .. _Using Sessions: https://docs.openstack.org/keystoneauth/latest/using-sessions.html Reference --------- .. toctree:: :maxdepth: 1 ref/index ref/v1/index ============================ OpenStackClient Command Line ============================ The preferred way of accessing Heat via the command line is using the python-heatclient's plugin for `python-openstackclient`_. Heat commands are available through the ``openstack`` CLI command when both python-heatclient and python-openstackclient are installed. .. toctree:: :maxdepth: 2 cli/index .. _python-openstackclient: https://docs.openstack.org/python-openstackclient ======================== Legacy Command-line Tool ======================== The ``heat`` command is provided as a legacy CLI option. Users should prefer using the python-openstackclient plugin via the ``openstack`` command instead. In order to use the CLI, you must provide your OpenStack username, password, tenant, and auth endpoint. Use the corresponding configuration options (``--os-username``, ``--os-password``, ``--os-tenant-id``, and ``--os-auth-url``) or set them in environment variables:: export OS_USERNAME=user export OS_PASSWORD=pass export OS_TENANT_ID=b363706f891f48019483f8bd6503c54b export OS_AUTH_URL=http://auth.example.com:5000/v2.0 The command line tool will attempt to reauthenticate using your provided credentials for every request. You can override this behavior by manually supplying an auth token using ``--heat-url`` and ``--os-auth-token``. You can alternatively set these environment variables:: export HEAT_URL=http://heat.example.org:8004/v1/b363706f891f48019483f8bd6503c54b export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155 Once you've configured your authentication parameters, you can run ``heat help`` to see a complete listing of available commands. Man Pages --------- .. toctree:: :maxdepth: 1 man/heat For Contributors ================ * If you are a new contributor to python-heatclient please refer: :doc:`contributor/contributing` .. toctree:: :hidden: contributor/contributing ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1709284674.5514681 python-heatclient-3.5.0/doc/source/man/0000775000175000017500000000000000000000000020021 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/doc/source/man/heat.rst0000664000175000017500000000322300000000000021474 0ustar00zuulzuul00000000000000==== heat ==== .. program:: heat SYNOPSIS ======== `heat` [options] [command-options] `heat help` `heat help` DESCRIPTION =========== `heat` is a command line client for controlling OpenStack Heat. Before the `heat` command is issued, ensure the environment contains the necessary variables so that the CLI can pass user credentials to the server. See `Getting Credentials for a CLI` section of `OpenStack CLI Guide` for more info. OPTIONS ======= To get a list of available commands and options run:: heat help To get usage and options of a command run:: heat help EXAMPLES ======== Get information about stack-create command:: heat help stack-create List available stacks:: heat stack-list List available resources in a stack:: heat resource-list Create a stack:: heat stack-create mystack -f some-template.yaml -P "KeyName=mine" View stack information:: heat stack-show mystack List stack outputs:: heat output-list Show the value of a single output:: heat output-show List events:: heat event-list mystack Delete a stack:: heat stack-delete mystack Abandon a stack:: heat stack-abandon mystack Adopt a stack :: heat stack-adopt -a mystack List heat-engines running status :: heat service-list Note: stack-adopt and stack-abandon commands are not available by default. Please ask your OpenStack operator to enable this feature. BUGS ==== Heat client is hosted in Launchpad so you can view current bugs at https://storyboard.openstack.org/#!/project/openstack/python-heatclient. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1709284674.5514681 python-heatclient-3.5.0/heatclient/0000775000175000017500000000000000000000000017321 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/__init__.py0000664000175000017500000000122400000000000021431 0ustar00zuulzuul00000000000000# 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() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/_i18n.py0000664000175000017500000000237400000000000020617 0ustar00zuulzuul00000000000000# 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 https://docs.openstack.org/oslo.i18n/latest/user/index.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() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/client.py0000664000175000017500000000154100000000000021152 0ustar00zuulzuul00000000000000# 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) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1709284674.5554683 python-heatclient-3.5.0/heatclient/common/0000775000175000017500000000000000000000000020611 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/common/__init__.py0000664000175000017500000000000000000000000022710 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/common/base.py0000664000175000017500000004146000000000000022102 0ustar00zuulzuul00000000000000# 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 from 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) class ManagerWithFind(BaseManager, metaclass=abc.ABCMeta): """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__ 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) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/common/deployment_utils.py0000664000175000017500000001227600000000000024573 0ustar00zuulzuul00000000000000# 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 from swiftclient import client as sc from swiftclient import utils as swiftclient_utils from urllib import parse as urlparse 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: str(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 # NOTE(pas-ha) only heatclient has os-no-client-auth arg, # osc plugin does not have it if getattr(args, 'os_no_client_auth', False): 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) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/common/environment_format.py0000664000175000017500000000436100000000000025103 0ustar00zuulzuul00000000000000# 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] = {} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/common/event_utils.py0000664000175000017500000002441700000000000023534 0ustar00zuulzuul00000000000000# 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_links = [link for link in first_links if link.get('rel') == 'root_stack'] if root_stack_links: # 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 = {link.get('rel'): link.get('href') for link 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 # noqa: E731 else: stop_check = lambda a: a.endswith('_COMPLETE') or a.endswith('_FAILED') # noqa E731 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 = {link.get('rel'): link.get('href') for link 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/common/format_utils.py0000664000175000017500000000520100000000000023671 0ustar00zuulzuul00000000000000# 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 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 = str(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))) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/common/hook_utils.py0000664000175000017500000000571000000000000023346 0ustar00zuulzuul00000000000000# 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 fnmatch import logging 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)s", {'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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/common/http.py0000664000175000017500000003333200000000000022146 0ustar00zuulzuul00000000000000# 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 from 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']) if not parse.urlparse(url).netloc: url = self.endpoint + url curl.append(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, bytes): 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) headers = kwargs.setdefault('headers', {}) headers.setdefault('Content-Type', 'application/json') 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 if 'region_name' in kwargs: kwargs['additional_headers'] = { 'X-Region-Name': kwargs['region_name']} 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) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/common/resource_formatter.py0000664000175000017500000001265000000000000025101 0ustar00zuulzuul00000000000000# 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 = {link['rel']: link['href'] for link 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') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/common/template_format.py0000664000175000017500000000542300000000000024352 0ustar00zuulzuul00000000000000# 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_base = yaml.CSafeLoader else: yaml_loader_base = yaml.SafeLoader if hasattr(yaml, 'CSafeDumper'): yaml_dumper_base = yaml.CSafeDumper else: yaml_dumper_base = yaml.SafeDumper # We create custom class to not overriden the default yaml behavior class yaml_loader(yaml_loader_base): pass class yaml_dumper(yaml_dumper_base): pass 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('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('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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/common/template_utils.py0000664000175000017500000003520400000000000024222 0ustar00zuulzuul00000000000000# 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 collections import abc from oslo_serialization import jsonutils from urllib import error from urllib import parse from 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, fetch_child=True): """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 :param fetch_child: Whether to fetch the child templates :returns: get_file dict and template contents :raises: error.URLError """ try: return get_template_contents(template_file=template_path, existing=existing, fetch_child=fetch_child) except error.URLError as template_file_exc: try: return get_template_contents(template_object=template_path, object_request=object_request, existing=existing, fetch_child=fetch_child) 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, fetch_child=True): 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, bytes): 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}) if files is None: files = {} if fetch_child: tmpl_base_url = utils.base_url_for_url(template_url) 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, str): 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, bytes): 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 = from_data.values() 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, abc.Mapping): r = deep_update(old.get(k, {}), v) old[k] = r elif v is None and isinstance(old.get(k), abc.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, fetch_env_files=True): """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 :param fetch_env_files: fetch env_files or leave it to server """ 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: if fetch_env_files: 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) env_url = utils.normalise_file_path_to_url(env_path) else: env_url = env_path if env_list_tracker is not None: 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/common/utils.py0000664000175000017500000003313200000000000022325 0ustar00zuulzuul00000000000000# 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 from urllib import error from urllib import parse from 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) print(encodeutils.safe_encode(pt.get_string(**kwargs)).decode()) def link_formatter(links): def format_link(link): if 'rel' in link: return "%s (%s)" % (link.get('href', ''), link.get('rel', '')) else: return "%s" % (link.get('href', '')) return '\n'.join(format_link(link) for link in links or []) def resource_nested_identifier(rsrc): nested_link = [link for link in rsrc.links or [] if link.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: 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 link in getattr(event, 'links', []): if link.get('rel') == 'stack': if 'href' not in link: return None stack_link = link['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) print(encodeutils.safe_encode(pt.get_string()).decode()) 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.encodebytes(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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/exc.py0000664000175000017500000001110200000000000020445 0ustar00zuulzuul00000000000000# 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 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1709284674.5554683 python-heatclient-3.5.0/heatclient/osc/0000775000175000017500000000000000000000000020105 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/osc/__init__.py0000664000175000017500000000000000000000000022204 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/osc/plugin.py0000664000175000017500000000470500000000000021763 0ustar00zuulzuul00000000000000# 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. # """OpenStackClient plugin for Orchestration service.""" import logging from osc_lib import utils LOG = logging.getLogger(__name__) DEFAULT_ORCHESTRATION_API_VERSION = '1' API_VERSION_OPTION = 'os_orchestration_api_version' API_NAME = 'orchestration' API_VERSIONS = { '1': 'heatclient.v1.client.Client', } def make_client(instance): """Returns an orchestration service client""" heat_client = utils.get_client_class( API_NAME, instance._api_version[API_NAME], API_VERSIONS) LOG.debug('Instantiating orchestration client: %s', heat_client) kwargs = {'region_name': instance.region_name, 'interface': instance.interface} if instance.session: kwargs.update({'session': instance.session, 'service_type': API_NAME}) elif instance.auth_plugin_name == 'token_endpoint': kwargs.update({'endpoint': instance.auth.url, 'token': instance.auth.token}) else: endpoint = instance.get_endpoint_for_service_type( API_NAME, region_name=instance.region_name, interface=instance.interface, ) kwargs.update({'endpoint': endpoint, 'auth_url': instance.auth.auth_url, 'username': instance.auth_ref.username, 'token': instance.auth_ref.auth_token}) client = heat_client(**kwargs) return client def build_option_parser(parser): """Hook to add global options""" parser.add_argument( '--os-orchestration-api-version', metavar='', default=utils.env( 'OS_ORCHESTRATION_API_VERSION', default=DEFAULT_ORCHESTRATION_API_VERSION), help='Orchestration API version, default=' + DEFAULT_ORCHESTRATION_API_VERSION + ' (Env: OS_ORCHESTRATION_API_VERSION)') return parser ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1709284674.5554683 python-heatclient-3.5.0/heatclient/osc/v1/0000775000175000017500000000000000000000000020433 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/osc/v1/__init__.py0000664000175000017500000000000000000000000022532 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/osc/v1/build_info.py0000664000175000017500000000277000000000000023125 0ustar00zuulzuul00000000000000# 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 build info action implementations""" import logging from osc_lib.command import command from osc_lib import utils from heatclient.common import utils as heat_utils class BuildInfo(command.ShowOne): """Retrieve build information.""" log = logging.getLogger(__name__ + ".BuildInfo") def get_parser(self, prog_name): parser = super(BuildInfo, 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 result = heat_client.build_info.build_info() formatters = { 'api': heat_utils.json_formatter, 'engine': heat_utils.json_formatter, } columns = sorted(list(result.keys())) return columns, utils.get_dict_properties(result, columns, formatters=formatters) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/osc/v1/event.py0000664000175000017500000002042500000000000022131 0ustar00zuulzuul00000000000000# 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') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/osc/v1/resource.py0000664000175000017500000002353400000000000022643 0ustar00zuulzuul00000000000000# 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 from 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(metadata.values()) columns = list(metadata.keys()) 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}) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/osc/v1/resource_type.py0000664000175000017500000001111600000000000023675 0ustar00zuulzuul00000000000000# 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 _ 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(data.values()) columns = list(data.keys()) 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/osc/v1/service.py0000664000175000017500000000255100000000000022450 0ustar00zuulzuul00000000000000# 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) ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/osc/v1/snapshot.py0000664000175000017500000001753000000000000022652 0ustar00zuulzuul00000000000000# 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 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(data.values()) columns = list(data.keys()) 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}) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/osc/v1/software_config.py0000664000175000017500000001643500000000000024175 0ustar00zuulzuul00000000000000# 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 from 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(sc.values()) columns = list(sc.keys()) 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/osc/v1/software_deployment.py0000664000175000017500000003131600000000000025103 0ustar00zuulzuul00000000000000# 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: 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) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1709284644.0 python-heatclient-3.5.0/heatclient/osc/v1/stack.py0000664000175000017500000014121600000000000022117 0ustar00zuulzuul00000000000000# 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 from urllib import request import yaml 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( '-s', '--files-container', metavar='', help=_('Swift files container name. Local files other than ' 'root template would be ignored. If other files are not ' 'found in swift, heat engine would raise an error.') ) 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( '--poll', metavar='SECONDS', type=int, default=5, help=_('Poll interval in seconds for use with --wait, ' 'defaults to 5.') ) 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='