From c14ba08d324370487010f92ca83201e76837011a Mon Sep 17 00:00:00 2001 From: Dougal Matthews Date: Thu, 21 Jan 2016 15:15:13 +0000 Subject: [PATCH] Retire the Tuskar Client codebase Change-Id: Iee7117358100a1b5cfb365103e6e2ac247ea6178 Depends-On: I904b2f27591333e104bf9080bb8c3876fcb3596c --- .testr.conf | 4 - .travis.yml | 12 - LICENSE | 175 ------ MANIFEST.in | 5 - README | 10 + README.rst | 58 -- doc/.gitignore | 2 - doc/source/_static/basic.css | 416 -------------- doc/source/_static/default.css | 230 -------- doc/source/_static/header-line.gif | Bin 48 -> 0 bytes doc/source/_static/header_bg.jpg | Bin 3738 -> 0 bytes doc/source/_static/jquery.tweet.js | 154 ----- doc/source/_static/nature.css | 245 -------- doc/source/_static/openstack_logo.png | Bin 3670 -> 0 bytes doc/source/_static/pygments.css | 62 -- doc/source/_static/tweaks.css | 94 --- doc/source/_theme/layout.html | 83 --- doc/source/_theme/theme.conf | 4 - doc/source/cli/v2/index.rst | 8 - doc/source/cli/v2/plans.rst | 229 -------- doc/source/cli/v2/roles.rst | 27 - doc/source/conf.py | 264 --------- doc/source/index.rst | 51 -- doc/source/man/tuskar.rst | 56 -- openstack-common.conf | 8 - requirements.txt | 14 - setup.cfg | 51 -- setup.py | 29 - test-requirements.txt | 14 - tools/tuskar_with_venv.sh | 4 - tox.ini | 39 -- tuskarclient/__init__.py | 16 - tuskarclient/client.py | 63 -- tuskarclient/common/__init__.py | 0 tuskarclient/common/auth.py | 82 --- tuskarclient/common/formatting.py | 132 ----- tuskarclient/common/utils.py | 168 ------ tuskarclient/i18n.py | 35 -- tuskarclient/openstack/__init__.py | 0 tuskarclient/openstack/common/__init__.py | 0 tuskarclient/openstack/common/_i18n.py | 45 -- .../openstack/common/apiclient/__init__.py | 0 .../openstack/common/apiclient/auth.py | 234 -------- .../openstack/common/apiclient/base.py | 539 ------------------ .../openstack/common/apiclient/client.py | 388 ------------- .../openstack/common/apiclient/exceptions.py | 479 ---------------- .../openstack/common/apiclient/fake_client.py | 190 ------ .../openstack/common/apiclient/utils.py | 100 ---- tuskarclient/openstack/common/cliutils.py | 271 --------- tuskarclient/openstack/common/uuidutils.py | 37 -- tuskarclient/osc/__init__.py | 0 tuskarclient/osc/plugin.py | 61 -- tuskarclient/osc/v2/__init__.py | 0 tuskarclient/osc/v2/plan.py | 344 ----------- tuskarclient/osc/v2/role.py | 38 -- tuskarclient/shell.py | 257 --------- tuskarclient/tests/__init__.py | 0 tuskarclient/tests/common/__init__.py | 0 tuskarclient/tests/common/test_auth.py | 123 ---- tuskarclient/tests/common/test_formatting.py | 99 ---- tuskarclient/tests/common/test_utils.py | 154 ----- tuskarclient/tests/integration/__init__.py | 0 .../tests/integration/test_help_command.py | 101 ---- tuskarclient/tests/osc/__init__.py | 0 tuskarclient/tests/osc/test_plugin.py | 42 -- tuskarclient/tests/osc/v2/__init__.py | 0 tuskarclient/tests/osc/v2/fakes.py | 44 -- tuskarclient/tests/osc/v2/test_plans.py | 340 ----------- tuskarclient/tests/osc/v2/test_roles.py | 46 -- tuskarclient/tests/test_client.py | 90 --- tuskarclient/tests/test_shell.py | 71 --- tuskarclient/tests/utils.py | 327 ----------- tuskarclient/tests/v2/__init__.py | 0 tuskarclient/tests/v2/test_client.py | 29 - tuskarclient/tests/v2/test_plans.py | 147 ----- tuskarclient/tests/v2/test_plans_shell.py | 384 ------------- tuskarclient/tests/v2/test_roles.py | 44 -- tuskarclient/tests/v2/test_roles_shell.py | 47 -- tuskarclient/v2/__init__.py | 0 tuskarclient/v2/client.py | 29 - tuskarclient/v2/plans.py | 168 ------ tuskarclient/v2/plans_shell.py | 302 ---------- tuskarclient/v2/roles.py | 47 -- tuskarclient/v2/roles_shell.py | 31 - tuskarclient/v2/shell.py | 32 -- 85 files changed, 10 insertions(+), 8514 deletions(-) delete mode 100644 .testr.conf delete mode 100644 .travis.yml delete mode 100644 LICENSE delete mode 100644 MANIFEST.in create mode 100644 README delete mode 100644 README.rst delete mode 100644 doc/.gitignore delete mode 100644 doc/source/_static/basic.css delete mode 100644 doc/source/_static/default.css delete mode 100644 doc/source/_static/header-line.gif delete mode 100644 doc/source/_static/header_bg.jpg delete mode 100644 doc/source/_static/jquery.tweet.js delete mode 100644 doc/source/_static/nature.css delete mode 100644 doc/source/_static/openstack_logo.png delete mode 100644 doc/source/_static/pygments.css delete mode 100644 doc/source/_static/tweaks.css delete mode 100644 doc/source/_theme/layout.html delete mode 100644 doc/source/_theme/theme.conf delete mode 100644 doc/source/cli/v2/index.rst delete mode 100644 doc/source/cli/v2/plans.rst delete mode 100644 doc/source/cli/v2/roles.rst delete mode 100644 doc/source/conf.py delete mode 100644 doc/source/index.rst delete mode 100644 doc/source/man/tuskar.rst delete mode 100644 openstack-common.conf delete mode 100644 requirements.txt delete mode 100644 setup.cfg delete mode 100644 setup.py delete mode 100644 test-requirements.txt delete mode 100755 tools/tuskar_with_venv.sh delete mode 100644 tox.ini delete mode 100644 tuskarclient/__init__.py delete mode 100644 tuskarclient/client.py delete mode 100644 tuskarclient/common/__init__.py delete mode 100644 tuskarclient/common/auth.py delete mode 100644 tuskarclient/common/formatting.py delete mode 100644 tuskarclient/common/utils.py delete mode 100644 tuskarclient/i18n.py delete mode 100644 tuskarclient/openstack/__init__.py delete mode 100644 tuskarclient/openstack/common/__init__.py delete mode 100644 tuskarclient/openstack/common/_i18n.py delete mode 100644 tuskarclient/openstack/common/apiclient/__init__.py delete mode 100644 tuskarclient/openstack/common/apiclient/auth.py delete mode 100644 tuskarclient/openstack/common/apiclient/base.py delete mode 100644 tuskarclient/openstack/common/apiclient/client.py delete mode 100644 tuskarclient/openstack/common/apiclient/exceptions.py delete mode 100644 tuskarclient/openstack/common/apiclient/fake_client.py delete mode 100644 tuskarclient/openstack/common/apiclient/utils.py delete mode 100644 tuskarclient/openstack/common/cliutils.py delete mode 100644 tuskarclient/openstack/common/uuidutils.py delete mode 100644 tuskarclient/osc/__init__.py delete mode 100644 tuskarclient/osc/plugin.py delete mode 100644 tuskarclient/osc/v2/__init__.py delete mode 100644 tuskarclient/osc/v2/plan.py delete mode 100644 tuskarclient/osc/v2/role.py delete mode 100755 tuskarclient/shell.py delete mode 100644 tuskarclient/tests/__init__.py delete mode 100644 tuskarclient/tests/common/__init__.py delete mode 100644 tuskarclient/tests/common/test_auth.py delete mode 100644 tuskarclient/tests/common/test_formatting.py delete mode 100644 tuskarclient/tests/common/test_utils.py delete mode 100644 tuskarclient/tests/integration/__init__.py delete mode 100644 tuskarclient/tests/integration/test_help_command.py delete mode 100644 tuskarclient/tests/osc/__init__.py delete mode 100644 tuskarclient/tests/osc/test_plugin.py delete mode 100644 tuskarclient/tests/osc/v2/__init__.py delete mode 100644 tuskarclient/tests/osc/v2/fakes.py delete mode 100644 tuskarclient/tests/osc/v2/test_plans.py delete mode 100644 tuskarclient/tests/osc/v2/test_roles.py delete mode 100644 tuskarclient/tests/test_client.py delete mode 100644 tuskarclient/tests/test_shell.py delete mode 100644 tuskarclient/tests/utils.py delete mode 100644 tuskarclient/tests/v2/__init__.py delete mode 100644 tuskarclient/tests/v2/test_client.py delete mode 100644 tuskarclient/tests/v2/test_plans.py delete mode 100644 tuskarclient/tests/v2/test_plans_shell.py delete mode 100644 tuskarclient/tests/v2/test_roles.py delete mode 100644 tuskarclient/tests/v2/test_roles_shell.py delete mode 100644 tuskarclient/v2/__init__.py delete mode 100644 tuskarclient/v2/client.py delete mode 100644 tuskarclient/v2/plans.py delete mode 100644 tuskarclient/v2/plans_shell.py delete mode 100644 tuskarclient/v2/roles.py delete mode 100644 tuskarclient/v2/roles_shell.py delete mode 100644 tuskarclient/v2/shell.py diff --git a/.testr.conf b/.testr.conf deleted file mode 100644 index 60477e8..0000000 --- a/.testr.conf +++ /dev/null @@ -1,4 +0,0 @@ -[DEFAULT] -test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} ${PYTHON:-python} -m subunit.run discover -t ./ ./ $LISTOPT $IDOPTION -test_id_option=--load-list $IDFILE -test_list_option=--list diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e6d4c03..0000000 --- a/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -language: python -env: - - TOX_ENV=py27 - - TOX_ENV=pep8 - - TOX_ENV=cover -before_install: - - pip install tox --use-mirrors - - if [ "x$TOX_ENV" = 'xcover' ]; then pip install coveralls --use-mirrors; fi -script: - - tox -e $TOX_ENV -after_success: - - if [ "x$TOX_ENV" = 'xcover' ]; then coveralls; fi diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 67db858..0000000 --- a/LICENSE +++ /dev/null @@ -1,175 +0,0 @@ - - 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. diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 86ea968..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,5 +0,0 @@ -include AUTHORS -include ChangeLog - -exclude .gitignore -exclude .gitreview diff --git a/README b/README new file mode 100644 index 0000000..3a1e7f8 --- /dev/null +++ b/README @@ -0,0 +1,10 @@ +This project is no longer maintained. + +The contents of this repository are still available in the Git +source code management system. To see the contents of this +repository before it reached its end of life, please check out the +previous commit with "git checkout HEAD^1". + +For any further questions, please email +openstack-dev@lists.openstack.org or join #openstack-dev or #tripleo +on Freenode. diff --git a/README.rst b/README.rst deleted file mode 100644 index cac3abc..0000000 --- a/README.rst +++ /dev/null @@ -1,58 +0,0 @@ -=================== -python-tuskarclient -=================== - -python-tuskarclient is a Python client and a command-line interface -for `Tuskar `_. - - -Getting Started -=============== - -Clone the repo:: - - $ git clone https://git.openstack.org/openstack/python-tuskarclient - -Then, use ``tox`` to set up a virtual environment and run tests:: - - $ cd python-tuskarclient - $ tox - -When this is done, activate your virtual environment:: - - $ source .tox/py27/bin/activate - -Finally, use this script to build the wrapper script in your virtual -environment for the CLI tools:: - - $ python setup.py develop - - -Use from Python -=============== - -For using ``python-tuskarclient`` within a Python application, `this -wiki page `_ -provides the most complete documentation. - -Use from the CLI -================ - -On the command line, ``python-tuskarclient`` implements the ``tuskar`` -command. - -First, be sure to run all of the steps in the Getting Started section, -above, and that you have not deactivated your virtual environment. - -Then, export these two environment variables, customizing them if -necessary:: - - $ export OS_AUTH_TOKEN=nopass - $ export TUSKAR_URL=http://localhost:8585/v2 - -(Note that 'nopass' is the correct value in a default setup with no -authentication.) - -Now you may interact with Tuskar by using the ``tuskar`` -command. ``tuskar --help`` with list full usage details. You can use -``tuskar rack-list`` as an example. diff --git a/doc/.gitignore b/doc/.gitignore deleted file mode 100644 index 8e0be80..0000000 --- a/doc/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -build/ -source/ref/ diff --git a/doc/source/_static/basic.css b/doc/source/_static/basic.css deleted file mode 100644 index d909ce3..0000000 --- a/doc/source/_static/basic.css +++ /dev/null @@ -1,416 +0,0 @@ -/** - * Sphinx stylesheet -- basic theme - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - */ - -/* -- main layout ----------------------------------------------------------- */ - -div.clearer { - clear: both; -} - -/* -- relbar ---------------------------------------------------------------- */ - -div.related { - width: 100%; - font-size: 90%; -} - -div.related h3 { - display: none; -} - -div.related ul { - margin: 0; - padding: 0 0 0 10px; - list-style: none; -} - -div.related li { - display: inline; -} - -div.related li.right { - float: right; - margin-right: 5px; -} - -/* -- sidebar --------------------------------------------------------------- */ - -div.sphinxsidebarwrapper { - padding: 10px 5px 0 10px; -} - -div.sphinxsidebar { - float: left; - width: 230px; - margin-left: -100%; - font-size: 90%; -} - -div.sphinxsidebar ul { - list-style: none; -} - -div.sphinxsidebar ul ul, -div.sphinxsidebar ul.want-points { - margin-left: 20px; - list-style: square; -} - -div.sphinxsidebar ul ul { - margin-top: 0; - margin-bottom: 0; -} - -div.sphinxsidebar form { - margin-top: 10px; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - -img { - border: 0; -} - -/* -- search page ----------------------------------------------------------- */ - -ul.search { - margin: 10px 0 0 20px; - padding: 0; -} - -ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; -} - -ul.search li a { - font-weight: bold; -} - -ul.search li div.context { - color: #888; - margin: 2px 0 0 30px; - text-align: left; -} - -ul.keywordmatches li.goodmatch a { - font-weight: bold; -} - -/* -- index page ------------------------------------------------------------ */ - -table.contentstable { - width: 90%; -} - -table.contentstable p.biglink { - line-height: 150%; -} - -a.biglink { - font-size: 1.3em; -} - -span.linkdescr { - font-style: italic; - padding-top: 5px; - font-size: 90%; -} - -/* -- general index --------------------------------------------------------- */ - -table.indextable td { - text-align: left; - vertical-align: top; -} - -table.indextable dl, table.indextable dd { - margin-top: 0; - margin-bottom: 0; -} - -table.indextable tr.pcap { - height: 10px; -} - -table.indextable tr.cap { - margin-top: 10px; - background-color: #f2f2f2; -} - -img.toggler { - margin-right: 3px; - margin-top: 3px; - cursor: pointer; -} - -/* -- general body styles --------------------------------------------------- */ - -a.headerlink { - visibility: hidden; -} - -h1:hover > a.headerlink, -h2:hover > a.headerlink, -h3:hover > a.headerlink, -h4:hover > a.headerlink, -h5:hover > a.headerlink, -h6:hover > a.headerlink, -dt:hover > a.headerlink { - visibility: visible; -} - -div.body p.caption { - text-align: inherit; -} - -div.body td { - text-align: left; -} - -.field-list ul { - padding-left: 1em; -} - -.first { -} - -p.rubric { - margin-top: 30px; - font-weight: bold; -} - -/* -- sidebars -------------------------------------------------------------- */ - -div.sidebar { - margin: 0 0 0.5em 1em; - border: 1px solid #ddb; - padding: 7px 7px 0 7px; - background-color: #ffe; - width: 40%; - float: right; -} - -p.sidebar-title { - font-weight: bold; -} - -/* -- topics ---------------------------------------------------------------- */ - -div.topic { - border: 1px solid #ccc; - padding: 7px 7px 0 7px; - margin: 10px 0 10px 0; -} - -p.topic-title { - font-size: 1.1em; - font-weight: bold; - margin-top: 10px; -} - -/* -- admonitions ----------------------------------------------------------- */ - -div.admonition { - margin-top: 10px; - margin-bottom: 10px; - padding: 7px; -} - -div.admonition dt { - font-weight: bold; -} - -div.admonition dl { - margin-bottom: 0; -} - -p.admonition-title { - margin: 0px 10px 5px 0px; - font-weight: bold; -} - -div.body p.centered { - text-align: center; - margin-top: 25px; -} - -/* -- tables ---------------------------------------------------------------- */ - -table.docutils { - border: 0; - border-collapse: collapse; -} - -table.docutils td, table.docutils th { - padding: 1px 8px 1px 0; - border-top: 0; - border-left: 0; - border-right: 0; - border-bottom: 1px solid #aaa; -} - -table.field-list td, table.field-list th { - border: 0 !important; -} - -table.footnote td, table.footnote th { - border: 0 !important; -} - -th { - text-align: left; - padding-right: 5px; -} - -/* -- other body styles ----------------------------------------------------- */ - -dl { - margin-bottom: 15px; -} - -dd p { - margin-top: 0px; -} - -dd ul, dd table { - margin-bottom: 10px; -} - -dd { - margin-top: 3px; - margin-bottom: 10px; - margin-left: 30px; -} - -dt:target, .highlight { - background-color: #fbe54e; -} - -dl.glossary dt { - font-weight: bold; - font-size: 1.1em; -} - -.field-list ul { - margin: 0; - padding-left: 1em; -} - -.field-list p { - margin: 0; -} - -.refcount { - color: #060; -} - -.optional { - font-size: 1.3em; -} - -.versionmodified { - font-style: italic; -} - -.system-message { - background-color: #fda; - padding: 5px; - border: 3px solid red; -} - -.footnote:target { - background-color: #ffa -} - -.line-block { - display: block; - margin-top: 1em; - margin-bottom: 1em; -} - -.line-block .line-block { - margin-top: 0; - margin-bottom: 0; - margin-left: 1.5em; -} - -/* -- code displays --------------------------------------------------------- */ - -pre { - overflow: auto; -} - -td.linenos pre { - padding: 5px 0px; - border: 0; - background-color: transparent; - color: #aaa; -} - -table.highlighttable { - margin-left: 0.5em; -} - -table.highlighttable td { - padding: 0 0.5em 0 0.5em; -} - -tt.descname { - background-color: transparent; - font-weight: bold; - font-size: 1.2em; -} - -tt.descclassname { - background-color: transparent; -} - -tt.xref, a tt { - background-color: transparent; - font-weight: bold; -} - -h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { - background-color: transparent; -} - -/* -- math display ---------------------------------------------------------- */ - -img.math { - vertical-align: middle; -} - -div.body div.math p { - text-align: center; -} - -span.eqno { - float: right; -} - -/* -- printout stylesheet --------------------------------------------------- */ - -@media print { - div.document, - div.documentwrapper, - div.bodywrapper { - margin: 0 !important; - width: 100%; - } - - div.sphinxsidebar, - div.related, - div.footer, - #top-link { - display: none; - } -} diff --git a/doc/source/_static/default.css b/doc/source/_static/default.css deleted file mode 100644 index c8091ec..0000000 --- a/doc/source/_static/default.css +++ /dev/null @@ -1,230 +0,0 @@ -/** - * Sphinx stylesheet -- default theme - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - */ - -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -body { - font-family: sans-serif; - font-size: 100%; - background-color: #11303d; - color: #000; - margin: 0; - padding: 0; -} - -div.document { - background-color: #1c4e63; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 0 0 0 230px; -} - -div.body { - background-color: #ffffff; - color: #000000; - padding: 0 20px 30px 20px; -} - -div.footer { - color: #ffffff; - width: 100%; - padding: 9px 0 9px 0; - text-align: center; - font-size: 75%; -} - -div.footer a { - color: #ffffff; - text-decoration: underline; -} - -div.related { - background-color: #133f52; - line-height: 30px; - color: #ffffff; -} - -div.related a { - color: #ffffff; -} - -div.sphinxsidebar { -} - -div.sphinxsidebar h3 { - font-family: 'Trebuchet MS', sans-serif; - color: #ffffff; - font-size: 1.4em; - font-weight: normal; - margin: 0; - padding: 0; -} - -div.sphinxsidebar h3 a { - color: #ffffff; -} - -div.sphinxsidebar h4 { - font-family: 'Trebuchet MS', sans-serif; - color: #ffffff; - font-size: 1.3em; - font-weight: normal; - margin: 5px 0 0 0; - padding: 0; -} - -div.sphinxsidebar p { - color: #ffffff; -} - -div.sphinxsidebar p.topless { - margin: 5px 10px 10px 10px; -} - -div.sphinxsidebar ul { - margin: 10px; - padding: 0; - color: #ffffff; -} - -div.sphinxsidebar a { - color: #98dbcc; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - -/* -- body styles ----------------------------------------------------------- */ - -a { - color: #355f7c; - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - -div.body p, div.body dd, div.body li { - text-align: left; - line-height: 130%; -} - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: 'Trebuchet MS', sans-serif; - background-color: #f2f2f2; - font-weight: normal; - color: #20435c; - border-bottom: 1px solid #ccc; - margin: 20px -20px 10px -20px; - padding: 3px 0 3px 10px; -} - -div.body h1 { margin-top: 0; font-size: 200%; } -div.body h2 { font-size: 160%; } -div.body h3 { font-size: 140%; } -div.body h4 { font-size: 120%; } -div.body h5 { font-size: 110%; } -div.body h6 { font-size: 100%; } - -a.headerlink { - color: #c60f0f; - font-size: 0.8em; - padding: 0 4px 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - background-color: #c60f0f; - color: white; -} - -div.body p, div.body dd, div.body li { - text-align: left; - line-height: 130%; -} - -div.admonition p.admonition-title + p { - display: inline; -} - -div.admonition p { - margin-bottom: 5px; -} - -div.admonition pre { - margin-bottom: 5px; -} - -div.admonition ul, div.admonition ol { - margin-bottom: 5px; -} - -div.note { - background-color: #eee; - border: 1px solid #ccc; -} - -div.seealso { - background-color: #ffc; - border: 1px solid #ff6; -} - -div.topic { - background-color: #eee; -} - -div.warning { - background-color: #ffe4e4; - border: 1px solid #f66; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre { - padding: 5px; - background-color: #eeffcc; - color: #333333; - line-height: 120%; - border: 1px solid #ac9; - border-left: none; - border-right: none; -} - -tt { - background-color: #ecf0f3; - padding: 0 1px 0 1px; - font-size: 0.95em; -} - -.warning tt { - background: #efc2c2; -} - -.note tt { - background: #d6d6d6; -} diff --git a/doc/source/_static/header-line.gif b/doc/source/_static/header-line.gif deleted file mode 100644 index 3601730e03488b7b5f92dc992d23ad753357c167..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48 zcmZ?wbhEHbWMg1uXkcVG`smgF|Nj+#vM@3*Ff!;c00Bsbfr-7RpY8O^Kn4bD08FwB Aga7~l diff --git a/doc/source/_static/header_bg.jpg b/doc/source/_static/header_bg.jpg deleted file mode 100644 index f788c41c26481728fa4329c17c87bde36001adc1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3738 zcmd5-YdDna8vedHnM0NtYi6>>At7O=uyTsZup5R_40A9)aXQa}U(l^=gSg=J*&3mKp$aM0r>UIFDe9Zy(vs} zWf)kqO2Y_n0$>ZQ0D&hY4tWjpY?Ii5?V)h*kc0fz?%ZIj3|{;F8E5l%d0)&*Hx~ulvc_*73u8%R zsVMV~ne!JY);&pWott~QIZYJFTXliYc2};JEU{X7W6;ZPfz;)U;U4#mEuK@K*=SC3BR-m&x9(Nna@>b@%FS34|P^jtsXRb5>z9gtPp;_MI2F3o*k z>csA-?CX4b;~4P-*L$+Mmb|51F)eD*wCc`Jt(9}C${Zo=!Uin=u_yMC^;`X!x$##4 z+~}dkT`NF@Uhw0r+6g_)?e!h8IX+OE^C96>UOsv0GPMD6(kr#ljhXRnA=O>Qj@%iT zqBF7aQ*}BG)h@6r0%#azk!r9yrN6>9dq~>KadV$~cGG?Hjk>~it^5rd#zS4KE*p+4 z;;B)%oBK8PNTs=A)a-z`n?3zJ%+h{`=>ijk4sYKr*>`eN1H`~Lo|Tm!o6qN{S* zeNl=NcpGzD55)XnLC|>g)~w={=c#4*x^;mk4Zo_FOFlffP@!?1`c+TogTVR4kp9-q z`d5cMBzNxk6qjPRK9*WY3uHS=bnm_QJvSMBBS_A#3i=ywsg6^|9rfruW0MhdGwHDO z?1gJRMQVecKE^gV{%uo(b)zl^Hd&vmnwFh88h*-?FJ;y=Hdqvt!K|s<$>xlzR=G4{ zZgGOCF43IXS?62B)w*N&dXt%U8X^Bjx}^%Yf>VFpFoKSGP%k?ems;&&J)|Dx(qtQD zu2tS)<_Qz4#LhBKYkl@Og}G)^5+F4P($Fk>)}{uMVv|;Sz2i4$XJ_WTw*;n>3N805rnXhbC52SC={E3rXRlrs|I6f;o|Cn%eje59{axu9sivy4oYmg=j|fLt3<3 zFce84aNb8GbK;y>RbBu71YBcYKL3@M3N25yoE%BtG z^K!`WTQ|fb-Ysa7T)mEw&4_b)PWYgc!)3W)H+neR9o^f|AXdgY1`gN+pvgzbbk`M z*Ts6${7M`2)9XIPy^MoXTiiP2GTp_OtgWMshnH)M&ZSO0)cet!oWo_0_&hV(0?Qdb zdo(sw{I#{hI`SWPM`N=U^#+MgN-*rZ#J7Cm7Jj89`5ehd_{z&9->Jc7$F(X4)&|`K z5rEgd;@dhi-IzJnSVpMd!Gf_G-QW+ zjVMrIas1)g%)GJ;(=oaK};O^)NYdS1`XR?K_;I7qj zhii5}x^he{U3M+GF+WpYws#=Pt#S9xB_X5QE7W+_rQdwMhukJnQj}5cnCz_sIJ#r0 zJa5drkRPI$X(4YdpCswJe#5aN4Jjw3V3Nzt&`lcKBI~#;!>jq7j8y# zvHrFg_#P376A45^hp-KU*P=R;DVdPK*w7D@Gw+`XsSpm^L-VkCooZF61sPAnnjsT# zND4C{>G#P10F_&txEoE!rX%Iy*L}Kna=Q%fDLJ_rF*LujRITZ)$g!?UYLkCXOoz-S z_p`Hny*Rh--l)aYQC&-2dd%;%VKGC1<1DJm_n~`nk4^yS`}&P zM}5bOypW0hwtvrwnE>}g1Mq+B>09qPp1b$hn6kC_iqF`tX#G-t7D$n}Ky9t}sUqiI zOe@odQ?JueZ+sg`-zoQ}J4if6vv1c9x{BDme+F6z{8esU^Kio zK_oPy9}@nlGywSOZy9`^- zzBg>C9|rgWF{pcCogEV@;d}VHrgeBl=5Dr*th4V!1`Z9Zrz9le1zHC#sM3{j#G2R?WMhl6b_yyoEAxX>Zixl$16`+^d$ihNtuIBUafyiCEv#oksNL<4= z*oDXsc7-(ww^9-b-6_|bITySG1N2C-7p0L4+V@R%j=4@ygc=89bmSNy38$S=ZiDyP z0SrqrVA;zi8kYBZ2@Mx(2Lx~-*bc@d1#4R($RJv$9ZTfx_t7Kc|HIHnd&@I386P?& z?d6Vd(48n${cTNFFCoSIUj#O{mmt%M&xCIFmR9Y3f{2UnF4e9@uFZOaYiY|CLdbDa z%xS9x4SHi7Fr-1?CnDqRK?)n&$TTBW5J?O&o{TnNCnLw*{QmT7{c}flSbp9&xi*zF z1TdUn&_!$_WxQbMKGkgsl}B%+N5ZV%Hy6_zJ>dejD89yCBMw9(d}z2fWjYH_nV6!F zqe_rI2H5Pi0^~S6)jjnu%lqZN*eQq6!||a24+edpSH_{C8Ew^g8dw2qdrH!@*E7K* z)00Bb8uUsai%v6Oa^L@3E02r|EG%EdV>q;=#2Q9Wjv3l?dAur$4bzyOl3M6 z1hf%&o*#2R&xnS1z4&R`Uq%`Ut0_P{BOwt;FuDb$1")); - }); - return $(returning); - }, - linkUser: function() { - var returning = []; - var regexp = /[\@]+([A-Za-z0-9-_]+)/gi; - this.each(function() { - returning.push(this.replace(regexp,"@$1")); - }); - return $(returning); - }, - linkHash: function() { - var returning = []; - var regexp = / [\#]+([A-Za-z0-9-_]+)/gi; - this.each(function() { - returning.push(this.replace(regexp, ' #$1')); - }); - return $(returning); - }, - capAwesome: function() { - var returning = []; - this.each(function() { - returning.push(this.replace(/\b(awesome)\b/gi, '$1')); - }); - return $(returning); - }, - capEpic: function() { - var returning = []; - this.each(function() { - returning.push(this.replace(/\b(epic)\b/gi, '$1')); - }); - return $(returning); - }, - makeHeart: function() { - var returning = []; - this.each(function() { - returning.push(this.replace(/(<)+[3]/gi, "")); - }); - return $(returning); - } - }); - - function relative_time(time_value) { - var parsed_date = Date.parse(time_value); - var relative_to = (arguments.length > 1) ? arguments[1] : new Date(); - var delta = parseInt((relative_to.getTime() - parsed_date) / 1000); - var pluralize = function (singular, n) { - return '' + n + ' ' + singular + (n == 1 ? '' : 's'); - }; - if(delta < 60) { - return 'less than a minute ago'; - } else if(delta < (45*60)) { - return 'about ' + pluralize("minute", parseInt(delta / 60)) + ' ago'; - } else if(delta < (24*60*60)) { - return 'about ' + pluralize("hour", parseInt(delta / 3600)) + ' ago'; - } else { - return 'about ' + pluralize("day", parseInt(delta / 86400)) + ' ago'; - } - } - - function build_url() { - var proto = ('https:' == document.location.protocol ? 'https:' : 'http:'); - if (s.list) { - return proto+"//api.twitter.com/1/"+s.username[0]+"/lists/"+s.list+"/statuses.json?per_page="+s.count+"&callback=?"; - } else if (s.query == null && s.username.length == 1) { - return proto+'//twitter.com/status/user_timeline/'+s.username[0]+'.json?count='+s.count+'&callback=?'; - } else { - var query = (s.query || 'from:'+s.username.join('%20OR%20from:')); - return proto+'//search.twitter.com/search.json?&q='+query+'&rpp='+s.count+'&callback=?'; - } - } - - return this.each(function(){ - var list = $('
    ').appendTo(this); - var intro = '

    '+s.intro_text+'

    '; - var outro = '

    '+s.outro_text+'

    '; - var loading = $('

    '+s.loading_text+'

    '); - - if(typeof(s.username) == "string"){ - s.username = [s.username]; - } - - if (s.loading_text) $(this).append(loading); - $.getJSON(build_url(), function(data){ - if (s.loading_text) loading.remove(); - if (s.intro_text) list.before(intro); - $.each((data.results || data), function(i,item){ - // auto join text based on verb tense and content - if (s.join_text == "auto") { - if (item.text.match(/^(@([A-Za-z0-9-_]+)) .*/i)) { - var join_text = s.auto_join_text_reply; - } else if (item.text.match(/(^\w+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&\?\/.=]+) .*/i)) { - var join_text = s.auto_join_text_url; - } else if (item.text.match(/^((\w+ed)|just) .*/im)) { - var join_text = s.auto_join_text_ed; - } else if (item.text.match(/^(\w*ing) .*/i)) { - var join_text = s.auto_join_text_ing; - } else { - var join_text = s.auto_join_text_default; - } - } else { - var join_text = s.join_text; - }; - - var from_user = item.from_user || item.user.screen_name; - var profile_image_url = item.profile_image_url || item.user.profile_image_url; - var join_template = ' '+join_text+' '; - var join = ((s.join_text) ? join_template : ' '); - var avatar_template = ''+from_user+'\'s avatar'; - var avatar = (s.avatar_size ? avatar_template : ''); - var date = ''+relative_time(item.created_at)+''; - var text = '' +$([item.text]).linkUrl().linkUser().linkHash().makeHeart().capAwesome().capEpic()[0]+ ''; - - // until we create a template option, arrange the items below to alter a tweet's display. - list.append('
  • ' + avatar + date + join + text + '
  • '); - - list.children('li:first').addClass('tweet_first'); - list.children('li:odd').addClass('tweet_even'); - list.children('li:even').addClass('tweet_odd'); - }); - if (s.outro_text) list.after(outro); - }); - - }); - }; -})(jQuery); \ No newline at end of file diff --git a/doc/source/_static/nature.css b/doc/source/_static/nature.css deleted file mode 100644 index a98bd42..0000000 --- a/doc/source/_static/nature.css +++ /dev/null @@ -1,245 +0,0 @@ -/* - * nature.css_t - * ~~~~~~~~~~~~ - * - * Sphinx stylesheet -- nature theme. - * - * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -body { - font-family: Arial, sans-serif; - font-size: 100%; - background-color: #111; - color: #555; - margin: 0; - padding: 0; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 0 0 0 {{ theme_sidebarwidth|toint }}px; -} - -hr { - border: 1px solid #B1B4B6; -} - -div.document { - background-color: #eee; -} - -div.body { - background-color: #ffffff; - color: #3E4349; - padding: 0 30px 30px 30px; - font-size: 0.9em; -} - -div.footer { - color: #555; - width: 100%; - padding: 13px 0; - text-align: center; - font-size: 75%; -} - -div.footer a { - color: #444; - text-decoration: underline; -} - -div.related { - background-color: #6BA81E; - line-height: 32px; - color: #fff; - text-shadow: 0px 1px 0 #444; - font-size: 0.9em; -} - -div.related a { - color: #E2F3CC; -} - -div.sphinxsidebar { - font-size: 0.75em; - line-height: 1.5em; -} - -div.sphinxsidebarwrapper{ - padding: 20px 0; -} - -div.sphinxsidebar h3, -div.sphinxsidebar h4 { - font-family: Arial, sans-serif; - color: #222; - font-size: 1.2em; - font-weight: normal; - margin: 0; - padding: 5px 10px; - background-color: #ddd; - text-shadow: 1px 1px 0 white -} - -div.sphinxsidebar h4{ - font-size: 1.1em; -} - -div.sphinxsidebar h3 a { - color: #444; -} - - -div.sphinxsidebar p { - color: #888; - padding: 5px 20px; -} - -div.sphinxsidebar p.topless { -} - -div.sphinxsidebar ul { - margin: 10px 20px; - padding: 0; - color: #000; -} - -div.sphinxsidebar a { - color: #444; -} - -div.sphinxsidebar input { - border: 1px solid #ccc; - font-family: sans-serif; - font-size: 1em; -} - -div.sphinxsidebar input[type=text]{ - margin-left: 20px; -} - -/* -- body styles ----------------------------------------------------------- */ - -a { - color: #005B81; - text-decoration: none; -} - -a:hover { - color: #E32E00; - text-decoration: underline; -} - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: Arial, sans-serif; - background-color: #BED4EB; - font-weight: normal; - color: #212224; - margin: 30px 0px 10px 0px; - padding: 5px 0 5px 10px; - text-shadow: 0px 1px 0 white -} - -div.body h1 { border-top: 20px solid white; margin-top: 0; font-size: 200%; } -div.body h2 { font-size: 150%; background-color: #C8D5E3; } -div.body h3 { font-size: 120%; background-color: #D8DEE3; } -div.body h4 { font-size: 110%; background-color: #D8DEE3; } -div.body h5 { font-size: 100%; background-color: #D8DEE3; } -div.body h6 { font-size: 100%; background-color: #D8DEE3; } - -a.headerlink { - color: #c60f0f; - font-size: 0.8em; - padding: 0 4px 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - background-color: #c60f0f; - color: white; -} - -div.body p, div.body dd, div.body li { - line-height: 1.5em; -} - -div.admonition p.admonition-title + p { - display: inline; -} - -div.highlight{ - background-color: white; -} - -div.note { - background-color: #eee; - border: 1px solid #ccc; -} - -div.seealso { - background-color: #ffc; - border: 1px solid #ff6; -} - -div.topic { - background-color: #eee; -} - -div.warning { - background-color: #ffe4e4; - border: 1px solid #f66; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre { - padding: 10px; - background-color: White; - color: #222; - line-height: 1.2em; - border: 1px solid #C6C9CB; - font-size: 1.1em; - margin: 1.5em 0 1.5em 0; - -webkit-box-shadow: 1px 1px 1px #d8d8d8; - -moz-box-shadow: 1px 1px 1px #d8d8d8; -} - -tt { - background-color: #ecf0f3; - color: #222; - /* padding: 1px 2px; */ - font-size: 1.1em; - font-family: monospace; -} - -.viewcode-back { - font-family: Arial, sans-serif; -} - -div.viewcode-block:target { - background-color: #f4debf; - border-top: 1px solid #ac9; - border-bottom: 1px solid #ac9; -} diff --git a/doc/source/_static/openstack_logo.png b/doc/source/_static/openstack_logo.png deleted file mode 100644 index 146faec5cfe3773824f4caf39e4480e4974d10df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3670 zcmV-c4yo~pP)CW75Qp#l)U;+N6jaIz6Nf$t6dNV>^>ETzcpQ=%tMaf0k|rg72+IW`z$FyfE+D{1@tt$t5DmX)*;QV?c;%+5Z&egAgfXTQJq-mZkC z>pFAHu}U=Axde_?s!99ZfDg_+9TYzDa6N1R3adhx&2Mb7>9w`KpMNz!>U5t2XQ8lZ zu+!+H7(PRwF@jAkwvI;|8|=Z_dfzV`Kpi;I!e=|Ql+HAdEag?VZ^Ilw9XJj9N1#1a z?UFC!)X62`CRIe^9YCLKbJ` z&O@f0zt{Z1YDF1utg2$F+rzvrncys+g37Xsd8)idSW(=}t#~qF#qBo29*@^ZCs<$W zpa144=o4g0z63h_ttPfIpH-FyG^MAH+6B~r$(4qw+Uv{2d#h`$lq+i+#Tf%CAzDFUh!pzX(6nW{EASJAQkhm!+}aGpHc z;(+N`S*@tYmump1T37E}J;!$0#F>^M*mT_X1x~bvnp&qP9IHI#bj-0z8FR+=p+e#*w3ugV#wX``sR-CI1!YiQsfc@Om<;1MBw zlfqH9z4Q|m*C?URU1OG(`UYn>Q8<|I!mby#FlN5MMFE8;Pyh$skbR?ngFLt?%nWSkS-#W5umy>@^DyAERP~{E&`M%0(qi&((^ahqL}u^jT<2dcf)p< z%Fxc9J$nh_`>_oNYC?oy`rIDY46Yrw4si3Qn~oXV%dJ}IlUD-40>QipyGa_dV0Z%J ztcEXm5yxR0gySJ04{nnbm#vP=Hq&GI<8VxcZ34pRjt6m%pE2H|!+HBJQrdBdyKHJR z2O_}hp!5bXuwniQYTF>yI|=cjT+2l`9T3|H+l4%ryPxWQm(ODW#8Ctj_CplcO=)qj zD#d~V6BahR9NY1kE5rF)_j<|!Cqnpq0uOKhL%w z>y8OyeTM1?REXc{0|3b=#WPZneh80PxL=Ljau1~+CgtMgg-vccMDX-L z9^7An_;!lFAi`#G_1F*OdM|Z$EVQs0m0$?mY}(baOZ%Zpd62#Pyg!3Jd4d zD^8+lSir&T6Y9-p9L#Wz6$5nXLjdOl?7Lv!TeMr}F14ranauW9=L>ubu*x>Bcrgwp zjrT@{rL*2Fc}Ilwn07QvdJfMOO2=(1Px)6&ih7lg839!Bx&}lQER~T`^7_x@fXo({ zCZMeZYt*!VgMTg>PR)PBaIwubzRY%jjE`-s zG;B}>2!lD=QLOTfQOEZKIEz*;yTJ9(Af0zNv;IDq7#Fr#W{Ap+7Sq1N3TL21X|h2t z=Dk>^bGSsRX-u+cZ23mMB_Ioc0yNIfcfLWB>$hVU3W3>d&a?IM+bGRGt+t}aiv(eh z(D6Z9N>U2|Qxle(!UVTeEKE6W))3WI5z48Rs8d5v0GwmyC8iQiUJO8KS?QwHl2abL zNW+hadDdPc8z%MSOG$l&WR@!!&M{WLmrnS=-0G#&`a)chX>mN9W1>|yqve@lL8a`f zXRmn$B8P=dLxE!2rIi}a*gh%FI4j?C;b@L=WgypiTRf==n6DKr9mUExo6a@{wLM-I z9%V9{!;5G!<8fMYikfEbrGXRQN-9*24}kIIpP&dEg@fiLqAY5|jjv}$P3x0avZODU zdX`c|G>h`1f=3uEu)L9C)H5%frni#HZXcX`TD{iQ-e2qXxj_f%|WW;byDMc%7+uBy}Y?KLC?jp%yyyeBNkqQ-*osw2ex&97Q{#C7%CdSDMNIV zTdC(LEm?&qPcNOjM)h9Grs|M(gsuhV8@96?m4WkQ>j{bJIs)m^neL%ua!i+N8>Lh+ zKu#7rF~VOH@hb{zGXYwys!Um4Vkf+H8Hj6?^eI%kT%j+HA0K=6qdQ@nfR57Q`Jm9T zc)Yg9-`e~BRE!xoKZ z=mP|0Kihr}V1$5sHw$QekmoL)lQ;~@H$S)}s3xuwypiubB?1%OyBpwC08TH!=?BrQ zhOp`PTu;%u0}Q=XKGb7d$g8*;de8c1UI|Re2R;;Radh_D!FIZg+JP`oJg>5 z;&B7eVAomZe>j~hOOIVRO_Q7eSGz37hxmnsG!n%HX`C6gSqFcg(RLmikn%EPR*wel zrsc;>!vQ<>2ZW`lk`MbNLopFd#_9mh8iKPH;KbjC@xJU${pdxuTF{uO(eG#9t*>XP z_4Seh`r_#q$^xeiuy(=eSouv66cpS!t3n`|j`6xnmSs1q@;0!I)m<6eYHHGMRdB87 ziruozT=gn@yp`B9oGxD-b7PqhZum|oJCfLB38&8v51ijj-Pb`qvCr3FtJ0aFms2h3(n0-}3jJ~J$ zCzep7-MIZFbo$(m8zWm?SoRl__blLE+!fFBVVk1&XLg+vmVNcTk9O2+q?x#F0LZUN zu6oM~C)(7^0|az4nM}@aZf<@RkH0CR8<-Yn-fZe+Dbr#iJWSt#tnR4^h<@ePXWmeHIO4q^X zCbiy(=k3R1o1}0E+7x*OOe-qnIXG{#N_rqK*1NH}Qz6aumTR`YTgo5K=q=61;5@b- zrgUA_Qz=)(TPN!tCZE|{?B0*r9ov5Fcip6xQ2;Yqs*2_o7TFKGp0|~bcP@6+a(rz^ zXXmmyBfT}ucw_t(6s+f^t_)nc>RKW<-q_&J35vN+RPLsR?VAsQeHLyCR7AWvxFOVc zAg-xl=j*RipzaKWx3lAf?ei`PoM;bbAL>svH?JqQwjSulb9bghytRt%*5x-no>xlf zh7qj0LYRXVDU})?Btsy7^71*ujsEP_ACyd)P)*ULWBCXox@PUfwmQ#)Vl&oeIqpQY zHMgU+xe0EhQ)RmjdB3JHGdrsvJ9?A=WwOrn)J?BH{+D&O_@SKdrj2|8Z{hS1T(k>&Zlt;p=tqw*mVY1aLt=u^eAHkW>8cb#@q& z4-SLa@ii zCt7NGrLv)1Scy9ew-sOwwLYn2a6T#KzJgnbacm7Z20q6tcs~C!0DI+r(=$l+x{=W0A}~0&W)ll4*&oF07*qoM6N<$f~n6U7ytkO diff --git a/doc/source/_static/pygments.css b/doc/source/_static/pygments.css deleted file mode 100644 index d79caa1..0000000 --- a/doc/source/_static/pygments.css +++ /dev/null @@ -1,62 +0,0 @@ -.highlight .hll { background-color: #ffffcc } -.highlight { background: #eeffcc; } -.highlight .c { color: #408090; font-style: italic } /* Comment */ -.highlight .err { border: 1px solid #FF0000 } /* Error */ -.highlight .k { color: #007020; font-weight: bold } /* Keyword */ -.highlight .o { color: #666666 } /* Operator */ -.highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #007020 } /* Comment.Preproc */ -.highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ -.highlight .gd { color: #A00000 } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #FF0000 } /* Generic.Error */ -.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ -.highlight .gi { color: #00A000 } /* Generic.Inserted */ -.highlight .go { color: #333333 } /* Generic.Output */ -.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -.highlight .gt { color: #0044DD } /* Generic.Traceback */ -.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ -.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { color: #007020 } /* Keyword.Pseudo */ -.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #902000 } /* Keyword.Type */ -.highlight .m { color: #208050 } /* Literal.Number */ -.highlight .s { color: #4070a0 } /* Literal.String */ -.highlight .na { color: #4070a0 } /* Name.Attribute */ -.highlight .nb { color: #007020 } /* Name.Builtin */ -.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ -.highlight .no { color: #60add5 } /* Name.Constant */ -.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ -.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ -.highlight .ne { color: #007020 } /* Name.Exception */ -.highlight .nf { color: #06287e } /* Name.Function */ -.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ -.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ -.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ -.highlight .nv { color: #bb60d5 } /* Name.Variable */ -.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ -.highlight .mf { color: #208050 } /* Literal.Number.Float */ -.highlight .mh { color: #208050 } /* Literal.Number.Hex */ -.highlight .mi { color: #208050 } /* Literal.Number.Integer */ -.highlight .mo { color: #208050 } /* Literal.Number.Oct */ -.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ -.highlight .sc { color: #4070a0 } /* Literal.String.Char */ -.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ -.highlight .s2 { color: #4070a0 } /* Literal.String.Double */ -.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ -.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ -.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ -.highlight .sx { color: #c65d09 } /* Literal.String.Other */ -.highlight .sr { color: #235388 } /* Literal.String.Regex */ -.highlight .s1 { color: #4070a0 } /* Literal.String.Single */ -.highlight .ss { color: #517918 } /* Literal.String.Symbol */ -.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ -.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ -.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ -.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/doc/source/_static/tweaks.css b/doc/source/_static/tweaks.css deleted file mode 100644 index 3f3fb3f..0000000 --- a/doc/source/_static/tweaks.css +++ /dev/null @@ -1,94 +0,0 @@ -body { - background: #fff url(../_static/header_bg.jpg) top left no-repeat; -} - -#header { - width: 950px; - margin: 0 auto; - height: 102px; -} - -#header h1#logo { - background: url(../_static/openstack_logo.png) top left no-repeat; - display: block; - float: left; - text-indent: -9999px; - width: 175px; - height: 55px; -} - -#navigation { - background: url(../_static/header-line.gif) repeat-x 0 bottom; - display: block; - float: left; - margin: 27px 0 0 25px; - padding: 0; -} - -#navigation li{ - float: left; - display: block; - margin-right: 25px; -} - -#navigation li a { - display: block; - font-weight: normal; - text-decoration: none; - background-position: 50% 0; - padding: 20px 0 5px; - color: #353535; - font-size: 14px; -} - -#navigation li a.current, #navigation li a.section { - border-bottom: 3px solid #cf2f19; - color: #cf2f19; -} - -div.related { - background-color: #cde2f8; - border: 1px solid #b0d3f8; -} - -div.related a { - color: #4078ba; - text-shadow: none; -} - -div.sphinxsidebarwrapper { - padding-top: 0; -} - -pre { - color: #555; -} - -div.documentwrapper h1, div.documentwrapper h2, div.documentwrapper h3, div.documentwrapper h4, div.documentwrapper h5, div.documentwrapper h6 { - font-family: 'PT Sans', sans-serif !important; - color: #264D69; - border-bottom: 1px dotted #C5E2EA; - padding: 0; - background: none; - padding-bottom: 5px; -} - -div.documentwrapper h3 { - color: #CF2F19; -} - -a.headerlink { - color: #fff !important; - margin-left: 5px; - background: #CF2F19 !important; -} - -div.body { - margin-top: -25px; - margin-left: 230px; -} - -div.document { - width: 960px; - margin: 0 auto; -} \ No newline at end of file diff --git a/doc/source/_theme/layout.html b/doc/source/_theme/layout.html deleted file mode 100644 index 750b782..0000000 --- a/doc/source/_theme/layout.html +++ /dev/null @@ -1,83 +0,0 @@ -{% extends "basic/layout.html" %} -{% set css_files = css_files + ['_static/tweaks.css'] %} -{% set script_files = script_files + ['_static/jquery.tweet.js'] %} - -{%- macro sidebar() %} - {%- if not embedded %}{% if not theme_nosidebar|tobool %} -
    -
    - {%- block sidebarlogo %} - {%- if logo %} - - {%- endif %} - {%- endblock %} - {%- block sidebartoc %} - {%- if display_toc %} -

    {{ _('Table Of Contents') }}

    - {{ toc }} - {%- endif %} - {%- endblock %} - {%- block sidebarrel %} - {%- if prev %} -

    {{ _('Previous topic') }}

    -

    {{ prev.title }}

    - {%- endif %} - {%- if next %} -

    {{ _('Next topic') }}

    -

    {{ next.title }}

    - {%- endif %} - {%- endblock %} - {%- block sidebarsourcelink %} - {%- if show_source and has_source and sourcename %} -

    {{ _('This Page') }}

    - - {%- endif %} - {%- endblock %} - {%- if customsidebar %} - {% include customsidebar %} - {%- endif %} - {%- block sidebarsearch %} - {%- if pagename != "search" %} - - - {%- endif %} - {%- endblock %} -
    -
    - {%- endif %}{% endif %} -{%- endmacro %} - -{% block relbar1 %}{% endblock relbar1 %} - -{% block header %} - -{% endblock %} \ No newline at end of file diff --git a/doc/source/_theme/theme.conf b/doc/source/_theme/theme.conf deleted file mode 100644 index 1cc4004..0000000 --- a/doc/source/_theme/theme.conf +++ /dev/null @@ -1,4 +0,0 @@ -[theme] -inherit = basic -stylesheet = nature.css -pygments_style = tango diff --git a/doc/source/cli/v2/index.rst b/doc/source/cli/v2/index.rst deleted file mode 100644 index 9850a9e..0000000 --- a/doc/source/cli/v2/index.rst +++ /dev/null @@ -1,8 +0,0 @@ -CLI Usage with version 2 API -=========================== - -.. toctree:: - :maxdepth: 1 - - plans - roles diff --git a/doc/source/cli/v2/plans.rst b/doc/source/cli/v2/plans.rst deleted file mode 100644 index 9756821..0000000 --- a/doc/source/cli/v2/plans.rst +++ /dev/null @@ -1,229 +0,0 @@ -Plans commands with version 2 API -================================= - -List All Plans --------------- -*tuskar plan-list [-h]* - -Usage example: - -:: - - tuskar plan-list - -This will show table of all Plans. - -Example: - -:: - - +--------------------------------------+-------------+---------------------------+---------------------+ - | uuid | name | description | roles | - +--------------------------------------+-------------+---------------------------+---------------------+ - | 53268a27-afc8-4b21-839f-90227dd7a001 | dev-cloud-3 | Development testing cloud | controller, compute | - +--------------------------------------+-------------+---------------------------+---------------------+ - | a117fa66-1445-44c7-8ad1-7663d2607aca | test1 | None | | - +--------------------------------------+-------------+---------------------------+---------------------+ - | c367b394-7179-4c44-85ed-bf84baaf9fee | dev-cloud-2 | Development testing cloud | | - +--------------------------------------+-------------+---------------------------+---------------------+ - -Field 'roles' contains list of names of Roles assigned to the Plan. - -Retrieve a Single Plan ----------------------- -*tuskar plan-show [-h] [--verbose] [--only-empty-parameters] * - -Usage example: - -:: - - tuskar plan-show c367b394-7179-4c44-85ed-bf84baaf9fee - -This command will show an overview of the Plan. - -Example: - -:: - - +-------------+------------------------------------------------------------------------------------------+ - | Property | Value | - +-------------+------------------------------------------------------------------------------------------+ - | created_at | 2014-09-26T13:36:28.804272 | - | description | Development testing cloud | - | name | dev-cloud-2 | - | parameters | ... | - | roles | description=OpenStack hypervisor node. Can be wrapped in a ResourceGroup for scaling. | - | | name=compute | - | | uuid=b7b1583c-5c80-481f-a25b-708ed4a39734 | - | | version=1 | - | | | - | | description=OpenStack control plane node. Can be wrapped in a ResourceGroup for scaling. | - | | name=controller | - | | uuid=df9edfac-e009-4df1-ac7f-8931d37f4be6 | - | | version=1 | - | updated_at | None | - | uuid | c367b394-7179-4c44-85ed-bf84baaf9fee | - +-------------+------------------------------------------------------------------------------------------+ - -Adding the --verbose flag will display all parameters, instead of just role counts. - -Adding the --only-empty-parameters flag will display only parameters, which have empty or None value. When all parameters have some value, no parameters will be displayed. - -Note: Parameters are displayed similarly as Roles, ie. set of properties with values. Each Parameter/Role separated by empty line from previous. - -Create a New Plan ------------------ -*tuskar plan-create [-h] [-d ] name* - -Usage example: - -:: - - tuskar plan-create -d 'Description of new plan' new-plan-name - -Output will be the same as for showing detail of a Plan. -Note that parameters and roles are not set for newly created Plan. - -:: - - +-------------+--------------------------------------+ - | Property | Value | - +-------------+--------------------------------------+ - | created_at | 2014-09-27T00:10:33.958239 | - | description | Description of new plan | - | name | new-plan-name | - | parameters | | - | roles | | - | updated_at | None | - | uuid | 839fcbbf-7aa0-4801-8ccb-d020da654dd6 | - +-------------+--------------------------------------+ - -Delete an Existing Plan ------------------------ -*tuskar plan-delete [-h] * - -Usage example: - -:: - - tuskar plan-delete 839fcbbf-7aa0-4801-8ccb-d020da654dd6 - -When successfully deleted, you will get message like this: - -:: - - Deleted Plan "new-plan-name". - -Adding a Role to a Plan ------------------------ -*tuskar plan-add-role [-h] -r plan_uuid* - -Usage example: - -:: - - tuskar plan-add-role -r df9edfac-e009-4df1-ac7f-8931d37f4be6 c367b394-7179-4c44-85ed-bf84baaf9fee - -This will assign Role specified by UUID to Plan. -Output of this command is the same as for plan-show. - -Removing a Role from a Plan ---------------------------- -*tuskar plan-remove-role [-h] -r plan_uuid* - -Usage example: - -:: - - tuskar plan-remove-role -r df9edfac-e009-4df1-ac7f-8931d37f4be6 c367b394-7179-4c44-85ed-bf84baaf9fee - -This will unassign Role from a Plan. This will not delete the Role from Tuskar. -Output of this command is the same as for plan-show. - -Show Plan’s scale ------------------ -*tuskar plan-show-scale plan_uuid* - -Usage example: - -:: - - tuskar plan-show-scale c367b394-7179-4c44-85ed-bf84baaf9fee - -Output of this command is a table containing role names with versions and their counts. - -Scaling a Plan --------------- -*tuskar plan-scale --count= plan_uuid* - -Usage example: - -:: - - tuskar plan-scale compute-1 --count=2 c367b394-7179-4c44-85ed-bf84baaf9fee - -This will scale given Plan’s role with specified count of nodes. -Output of this command is a short summary of changed values. - -Show Plan’s Flavors assigned to Roles -------------------------------------- -*tuskar plan-show-flavors plan_uuid* - -Usage example: - -:: - - tuskar plan-show-flavors c367b394-7179-4c44-85ed-bf84baaf9fee - -Output of this command is a table containing roles and assigned flavors. - -Assign Flavors to Roles in a Plan ---------------------------------- -*tuskar plan-flavor --flavor= plan_uuid* - -Usage example: - -:: - - tuskar plan-flavor compute-1 --flavor=baremetal c367b394-7179-4c44-85ed-bf84baaf9fee - -This will update role-flavor assignment in a Plan. -Output of this command is a short summary of changed values. - -Changing a Plan’s Configuration Values --------------------------------------- -*tuskar plan-update [-h] [-P ] plan_uuid* - -Usage example: - -:: - - tuskar plan-update -P compute-1::CeilometerPassword=secret-password -P compute-1::CeilometerMeteringSecret=secret-secret 53268a27-afc8-4b21-839f-90227dd7a001 - -This command accepts multiple name=value pairs for parameters to be updated. -Above example will look for parameter named 'compute-1::CeilometerPassword' and update its value to 'secret-password' -and will do similar update for 'compute-1::CeilometerMeteringSecret' parameter. - -This command can be used only for updating existing parameters. It is not possible to create new parameter this way. - -Retrieve a Plan’s Template Files --------------------------------- -*tuskar plan-templates [-h] -O plan_uuid* - -Usage example: - -:: - - tuskar plan-templates -O templates 53268a27-afc8-4b21-839f-90227dd7a001 - -This command will retrieve contents of templates of the Plan and save them as files into specified directory. --O/--output-dir is mandatory and application will create it if it does not exist. -Output is list of files with templates. - -:: - - Following templates has been written: - templates/plan.yaml - templates/environment.yaml - templates/provider-controller-1.yaml - templates/provider-compute-1.yaml diff --git a/doc/source/cli/v2/roles.rst b/doc/source/cli/v2/roles.rst deleted file mode 100644 index fbdd309..0000000 --- a/doc/source/cli/v2/roles.rst +++ /dev/null @@ -1,27 +0,0 @@ -Roles commands with version 2 API -================================= - - -Retrieving Possible Roles -------------------------- - -*tuskar role-list [-h]* - -Usage example: - -:: - - tuskar role-list - -This will show table of all Roles: - -Example: - -:: - - +--------------------------------------+------------+---------+------------------------------------------------------------------------------+ - | uuid | name | version | description | - +--------------------------------------+------------+---------+------------------------------------------------------------------------------+ - | b7b1583c-5c80-481f-a25b-708ed4a39734 | compute | 1 | OpenStack hypervisor node. Can be wrapped in a ResourceGroup for scaling. | - | df9edfac-e009-4df1-ac7f-8931d37f4be6 | controller | 1 | OpenStack control plane node. Can be wrapped in a ResourceGroup for scaling. | - +--------------------------------------+------------+---------+------------------------------------------------------------------------------+ diff --git a/doc/source/conf.py b/doc/source/conf.py deleted file mode 100644 index f321bc2..0000000 --- a/doc/source/conf.py +++ /dev/null @@ -1,264 +0,0 @@ -# -*- 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-tuskarclient 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 sys - -# 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('.')) -import os - -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 = "tuskarclient" - if ver: - pkg = "%s.%s" % (pkg, ver) - refdir = os.path.join(refdir, ver) - if not os.path.exists(refdir): - os.makedirs(refdir) - idxpath = os.path.join(refdir, "index.rst") - with open(idxpath, "w") as idx: - idx.write(("%(title)s\n" - "%(signs)s\n" - "\n" - ".. toctree::\n" - " :maxdepth: 1\n" - "\n") % {"title": title, "signs": "=" * len(title)}) - for name in names: - idx.write(" %s\n" % name) - rstpath = os.path.join(refdir, "%s.rst" % name) - with open(rstpath, "w") as rst: - rst.write(("%(title)s\n" - "%(signs)s\n" - "\n" - ".. automodule:: %(pkg)s.%(name)s\n" - " :members:\n" - " :undoc-members:\n" - " :show-inheritance:\n" - " :noindex:\n") - % {"title": name.capitalize(), - "signs": "=" * len(name), - "pkg": pkg, "name": name}) - -gen_ref("", "Client Reference", ["client", "exc"]) - - -# -- 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'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = 'python-tuskarclient' -copyright = u'OpenStack Foundation' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '0.1' -# The full version, including alpha/beta/rc tags. -release = '0.1' - -# 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 documents that shouldn't be included in the build. -#unused_docs = [] - -# List of directories, relative to source directory, that shouldn't be searched -# for source files. -exclude_trees = [] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# Grouping the document tree for man pages. -# List of tuples 'sourcefile', 'target', u'title', u'Authors name', 'manual' - -man_pages = [ - ('man/tuskar', 'tuskar', 'OpenStack Tuskar command line client', - ['OpenStack Contributors'], 1), -] - -# -- Options for HTML output -------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. Major themes that come with -# Sphinx are currently 'default' and 'sphinxdoc'. -html_theme = '_theme' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -html_theme_path = ["."] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# 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_use_modindex = 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, 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 = '' - -# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = '' - -# Output file base name for HTML help builder. -htmlhelp_basename = '%sdoc' % project - - -# -- Options for LaTeX output ------------------------------------------------- - -# The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' - -# The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]) -# . -latex_documents = [ - ( - 'index', - '%s.tex' % project, - '%s Documentation' % project, - '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 - -# Additional stuff for the LaTeX preamble. -#latex_preamble = '' - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_use_modindex = True diff --git a/doc/source/index.rst b/doc/source/index.rst deleted file mode 100644 index d48fe3e..0000000 --- a/doc/source/index.rst +++ /dev/null @@ -1,51 +0,0 @@ -Python bindings to the OpenStack Tuskar API -=========================================== - -This is a client for OpenStack Tuskar API. There's a Python API -(the :mod:`tuskarclient` module), and a command-line script -(installed as :program:`tuskar`). - -Python API -========== - -You can use the API like so:: - - >>> from tuskarclient.client import Client - >>> tuskar = Client('1', endpoint=tuskar_url) - -Reference ---------- - -.. toctree:: - :maxdepth: 1 - - ref/index - ref/v1/index - ref/v2/index - -Command-line Tool -================= - -.. toctree:: - :maxdepth: 1 - - cli/v2/index - -Man Pages -========= - -.. toctree:: - :maxdepth: 1 - - man/tuskar - -Contributing -============ - -Code is hosted `on OpenStack git`_. Submit bugs to the Tuskar project on -`Launchpad`_. Submit code to the openstack/python-tuskarclient project -using `Gerrit`_. - -.. _on OpenStack git: http://git.openstack.org/cgit/openstack/python-tuskarclient -.. _Launchpad: https://launchpad.net/python-tuskarclient -.. _Gerrit: http://docs.openstack.org/infra/manual/developers.html#development-workflow diff --git a/doc/source/man/tuskar.rst b/doc/source/man/tuskar.rst deleted file mode 100644 index c99592f..0000000 --- a/doc/source/man/tuskar.rst +++ /dev/null @@ -1,56 +0,0 @@ -====== -Tuskar -====== - -.. program:: tuskar - -SYNOPSIS -======== - - `tuskar` [options] [command-options] - - `tuskar help` - - `tuskar help` - - -DESCRIPTION -=========== - -`tuskar` is a command line client for controlling OpenStack Tuskar. - - - -OPTIONS -======= - -To get a list of available commands and options run:: - - tuskar help - -To get usage and options of a command run:: - - tuskar help - - -EXAMPLES -======== - -Get information about overcloud-create command:: - - tuskar help overcloud-create - -List available overcloud-list:: - - tuskar overcloud-list - -List available Overcloud Roles:: - - tuskar overcloud-roles-list - - -BUGS -==== - -Tuskar client is hosted in Launchpad so you can view current bugs at -https://bugs.launchpad.net/python-tuskarclient/. diff --git a/openstack-common.conf b/openstack-common.conf deleted file mode 100644 index 119ded4..0000000 --- a/openstack-common.conf +++ /dev/null @@ -1,8 +0,0 @@ -[DEFAULT] - -# The list of modules to copy from openstack-common -module=apiclient -module=cliutils - -# The base module to hold the copy of openstack.common -base=tuskarclient diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 33144f3..0000000 --- a/requirements.txt +++ /dev/null @@ -1,14 +0,0 @@ -# The order of packages is significant, because pip processes them in the order -# of appearance. Changing the order has an impact on the overall integration -# process, which may cause wedges in the gate later. -pbr>=1.6 - -Babel>=1.3 -cliff>=1.14.0 # Apache-2.0 -iso8601>=0.1.9 -PrettyTable<0.8,>=0.7 -python-keystoneclient>=1.6.0 -requests>=2.5.2 -python-openstackclient>=1.5.0 -simplejson>=2.2.0 -six>=1.9.0 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 4ca226c..0000000 --- a/setup.cfg +++ /dev/null @@ -1,51 +0,0 @@ -[metadata] -name = python-tuskarclient -summary = Client library for OpenStack Management API -description-file = - README.rst -license = Apache License, Version 2.0 -author = Jiri Stransky -author-email = jistr@redhat.com -home-page = https://github.com/openstack/python-tuskarclient -classifier = - Environment :: Console - Environment :: OpenStack - Intended Audience :: Developers - Intended Audience :: Information Technology - License :: OSI Approved :: Apache Software License - Operating System :: OS Independent - Programming Language :: Python - Programming Language :: Python :: 2 - Programming Language :: Python :: 2.7 - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.3 - -[files] -packages = - tuskarclient - -[entry_points] -console_scripts = - tuskar = tuskarclient.shell:main - -openstack.cli.extension = - management = tuskarclient.osc.plugin - -openstack.management.v2 = - management_plan_create = tuskarclient.osc.v2.plan:CreateManagementPlan - management_plan_delete = tuskarclient.osc.v2.plan:DeleteManagementPlan - management_plan_list = tuskarclient.osc.v2.plan:ListManagementPlans - management_plan_set = tuskarclient.osc.v2.plan:SetManagementPlan - management_plan_show = tuskarclient.osc.v2.plan:ShowManagementPlan - management_plan_add_role = tuskarclient.osc.v2.plan:AddManagementPlanRole - management_plan_remove_role = tuskarclient.osc.v2.plan:RemoveManagementPlanRole - management_plan_download = tuskarclient.osc.v2.plan:DownloadManagementPlan - management_role_list = tuskarclient.osc.v2.role:ListRoles - -[build_sphinx] -source-dir = doc/source -build-dir = doc/build -all_files = 1 - -[upload_sphinx] -upload-dir = doc/build/html diff --git a/setup.py b/setup.py deleted file mode 100644 index 782bb21..0000000 --- a/setup.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT -import setuptools - -# In python < 2.7.4, a lazy loading of package `pbr` will break -# setuptools if some other modules registered functions in `atexit`. -# solution from: http://bugs.python.org/issue15881#msg170215 -try: - import multiprocessing # noqa -except ImportError: - pass - -setuptools.setup( - setup_requires=['pbr>=1.8'], - pbr=True) diff --git a/test-requirements.txt b/test-requirements.txt deleted file mode 100644 index 8276a8b..0000000 --- a/test-requirements.txt +++ /dev/null @@ -1,14 +0,0 @@ -# The order of packages is significant, because pip processes them in the order -# of appearance. Changing the order has an impact on the overall integration -# process, which may cause wedges in the gate later. -# Hacking already pins down pep8, pyflakes and flake8 -hacking<0.11,>=0.10.2 - -coverage>=3.6 -discover -fixtures>=1.3.1 -mock>=1.2 -sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 -sphinxcontrib-pecanwsme>=0.8 -testrepository>=0.0.18 -testtools>=1.4.0 diff --git a/tools/tuskar_with_venv.sh b/tools/tuskar_with_venv.sh deleted file mode 100755 index f4d0808..0000000 --- a/tools/tuskar_with_venv.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -TOOLS=`dirname $0` -VENV=$TOOLS/../.venv -source $VENV/bin/activate && python -m tuskarclient.shell $@ diff --git a/tox.ini b/tox.ini deleted file mode 100644 index a491e67..0000000 --- a/tox.ini +++ /dev/null @@ -1,39 +0,0 @@ -[tox] -minversion = 1.6 -skipsdist = True -envlist = py27,py33,py34,pep8,cover - -[testenv] -usedevelop = True -install_command = pip install {opts} {packages} -setenv = VIRTUAL_ENV={envdir} - -deps = -r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt -commands = python setup.py testr --testr-args='{posargs}' - -[testenv:pep8] -commands = flake8 - -[testenv:venv] -commands = {posargs} - -[testenv:cover] -commands = coverage erase - python setup.py testr --coverage --testr-args='{posargs}' - coverage report -m --omit='tuskarclient/openstack/*' - -[tox:jenkins] -downloadcache = ~/cache/pip - -# H405 multi line docstring summary not separated with an empty line -[flake8] -ignore = H405 -show-source = True -exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build - -[hacking] -import_exceptions = - gettext.gettext, - six.StringIO, - tuskarclient.openstack.common.gettextutils._ diff --git a/tuskarclient/__init__.py b/tuskarclient/__init__.py deleted file mode 100644 index 6280215..0000000 --- a/tuskarclient/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# 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-tuskarclient').version_string() diff --git a/tuskarclient/client.py b/tuskarclient/client.py deleted file mode 100644 index abd9362..0000000 --- a/tuskarclient/client.py +++ /dev/null @@ -1,63 +0,0 @@ -# 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 tuskarclient.common import auth -from tuskarclient.openstack.common.apiclient import client as apiclient - -VERSION_MAP = { - '2': 'tuskarclient.v2.client.Client' -} - - -def get_client(api_version, **kwargs): - """Get an authtenticated client, based on the credentials - in the keyword args. - - :param api_version: the API version to use (only '2' is valid) - :param kwargs: keyword args containing credentials, either: - * os_auth_token: pre-existing token to re-use - * tuskar_url: tuskar API endpoint - or: - * os_username: name of user - * os_password: user's password - * os_auth_url: endpoint to authenticate against - * os_tenant_{name|id}: name or ID of tenant - """ - # Try call for client with token and endpoint. - # If it returns None, call for client with credentials - cli_kwargs = { - 'username': kwargs.get('os_username'), - 'password': kwargs.get('os_password'), - 'tenant_name': kwargs.get('os_tenant_name'), - 'token': kwargs.get('os_auth_token'), - 'auth_url': kwargs.get('os_auth_url'), - 'endpoint': kwargs.get('tuskar_url'), - 'cacert': kwargs.get('os_cacert'), - 'cert': kwargs.get('os_cert'), - 'key': kwargs.get('os_key'), - } - client = Client(api_version, **cli_kwargs) - # If we have a client, return it - if client: - return client - # otherwise raise error - else: - raise ValueError("Need correct set of parameters") - - -def Client(version, **kwargs): - client_class = apiclient.BaseClient.get_class('tuskarclient', - version, - VERSION_MAP) - keystone_auth = auth.KeystoneAuthPlugin(**kwargs) - http_client = apiclient.HTTPClient(keystone_auth) - return client_class(http_client) diff --git a/tuskarclient/common/__init__.py b/tuskarclient/common/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tuskarclient/common/auth.py b/tuskarclient/common/auth.py deleted file mode 100644 index cd47a4c..0000000 --- a/tuskarclient/common/auth.py +++ /dev/null @@ -1,82 +0,0 @@ -# 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 keystoneclient.v2_0 import client as ksclient - -from tuskarclient.openstack.common.apiclient import auth -from tuskarclient.openstack.common.apiclient import exceptions - - -class KeystoneAuthPlugin(auth.BaseAuthPlugin): - opt_names = [ - "username", - "password", - "tenant_id", - "tenant_name", - "token", - "auth_url", - "endpoint", - "cacert", - "cert", - "key", - ] - - def _do_authenticate(self, httpclient): - if self.opts.get('token') is None: - ks_kwargs = { - 'username': self.opts.get('username'), - 'password': self.opts.get('password'), - 'tenant_id': self.opts.get('tenant_id'), - 'tenant_name': self.opts.get('tenant_name'), - 'auth_url': self.opts.get('auth_url'), - 'cacert': self.opts.get('cacert'), - 'cert': self.opts.get('cert'), - 'key': self.opts.get('key'), - } - - self._ksclient = ksclient.Client(**ks_kwargs) - - def token_and_endpoint(self, endpoint_type, service_type): - token = endpoint = None - - if self.opts.get('token') and self.opts.get('endpoint'): - token = self.opts.get('token') - endpoint = self.opts.get('endpoint') - elif hasattr(self, '_ksclient'): - token = self._ksclient.auth_token - endpoint = (self.opts.get('endpoint') or - self._ksclient.service_catalog.url_for( - service_type=service_type or 'management', - endpoint_type=endpoint_type)) - - return (token, endpoint) - - def sufficient_options(self): - """Check if all required options are present. - - :raises: AuthPluginOptionsMissing - """ - if self.opts.get('token'): - lookup_table = ["token", "endpoint"] - else: - lookup_table = [ - "username", - "password", - "auth_url" - ] - tenant_opts = ["tenant_id", "tenant_name"] - if not any([self.opts.get(opt) for opt in tenant_opts]): - raise exceptions.AuthPluginOptionsMissing( - ' or '.join(tenant_opts)) - - missing = [opt for opt in lookup_table if not self.opts.get(opt)] - if missing: - raise exceptions.AuthPluginOptionsMissing(missing) diff --git a/tuskarclient/common/formatting.py b/tuskarclient/common/formatting.py deleted file mode 100644 index 807e81b..0000000 --- a/tuskarclient/common/formatting.py +++ /dev/null @@ -1,132 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from __future__ import print_function - -import pprint -import sys -import textwrap - -import prettytable -import six - - -def value_formatter(value, width=70): - # Most values can be pretty printed for a reasonable output - if not isinstance(value, six.string_types): - return pprint.pformat(value, width=width) - - # pprint doesn't touch strings, so we do them manually - - # First join lines that are not indented: - joined = [] - parts = [] - for l in value.splitlines(): - if l[0] in u' \t\r\n': - # break here - joined.append(' '.join(parts)) - parts = [] - parts.append(l.rstrip()) - if parts: - joined.append(' '.join(parts)) - - result = [] - for line in joined: - result.append(textwrap.fill(line, width=width)) - return u"\n".join(result) - - -def attributes_formatter(attributes): - """Given a simple dict format the keyvalue pairs with one on each line. - """ - return u"".join(u"{0}={1}\n".format(k, value_formatter(v)) for k, v in - sorted(attributes.items())) - - -def parameters_v2_formatter(parameters): - """Given a list of dicts format parameters output.""" - return u"\n".join(attributes_formatter(parameter) - for parameter in parameters) - - -def list_plan_roles_formatter(roles): - """Given a list of Roles format roles' names into row.""" - return u", ".join(role.name for role in roles) - - -def print_list(objs, fields, formatters={}, custom_labels={}, sortby=0, - outfile=sys.stdout): - '''Prints a list of objects. - - :param objs: list of objects to print - :param fields: list of attributes of the objects to print; - attributes beginning with '!' have a special meaning - they - should be used with custom field labels and formatters only, - and the formatter receives the whole object - :param formatters: dict of functions that perform pre-print - formatting of attributes (keys are strings from `fields` - parameter, values are functions that take one parameter - the - attribute) - :param custom_labels: dict of label overrides for fields (keys are - strings from `fields` parameter, values are custom labels - - headers of the table) - ''' - field_labels = [custom_labels.get(f, f) for f in fields] - pt = prettytable.PrettyTable([f for f in field_labels], - caching=False, print_empty=False) - pt.align = 'l' - - for o in objs: - row = [] - for field in fields: - if field[0] == '!': # custom field - if field in formatters: - row.append(formatters[field](o)) - else: - raise KeyError( - 'Custom field "%s" needs a formatter.' % field) - else: # attribute-based field - if hasattr(o, field) and field in formatters: - row.append(formatters[field](getattr(o, field))) - else: - row.append(getattr(o, field, '')) - pt.add_row(row) - print(pt.get_string(sortby=field_labels[sortby]), file=outfile) - - -def print_dict(d, formatters={}, custom_labels={}, outfile=sys.stdout): - '''Prints a dict to the provided file or file-like object. - - :param d: dict to print - :param formatters: dict of functions that perform pre-print - formatting of dict values (keys are keys from `d` parameter, - values are functions that take one parameter - the dict value - to format). A wild card formatter can be provided as '*' which - will be applied to all fields without a dedicated formatter. - :param custom_labels: dict of label overrides for keys (keys are - keys from `d` parameter, values are custom labels) - ''' - pt = prettytable.PrettyTable(['Property', 'Value'], - caching=False, print_empty=False) - pt.align = 'l' - - global_formatter = formatters.get('*') - - for field in d.keys(): - label = custom_labels.get(field, field) - if field in formatters: - pt.add_row([label, formatters[field](d[field])]) - elif global_formatter: - pt.add_row([label, global_formatter(d[field])]) - else: - pt.add_row([label, d[field]]) - print(pt.get_string(sortby='Property'), file=outfile) diff --git a/tuskarclient/common/utils.py b/tuskarclient/common/utils.py deleted file mode 100644 index b4b4c5e..0000000 --- a/tuskarclient/common/utils.py +++ /dev/null @@ -1,168 +0,0 @@ -# Copyright 2012 OpenStack LLC. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from __future__ import print_function - -import sys -import uuid - -from oslo_utils import importutils - -from tuskarclient.i18n import _ -from tuskarclient.openstack.common.apiclient import exceptions as exc -from tuskarclient.openstack.common import cliutils - -# Using common methods from oslo cliutils -arg = cliutils.arg -env = cliutils.env - - -def define_commands_from_module(subparsers, command_module): - """Find all methods beginning with 'do_' in a module, and add them - as commands into a subparsers collection. - """ - for method_name in (a for a in dir(command_module) if a.startswith('do_')): - # Commands should be hypen-separated instead of underscores. - command = method_name[3:].replace('_', '-') - callback = getattr(command_module, method_name) - define_command(subparsers, command, callback) - - -def define_command(subparsers, command, callback): - """Define a command in the subparsers collection. - - :param subparsers: subparsers collection where the command will go - :param command: command name - :param callback: function that will be used to process the command - """ - desc = callback.__doc__ or '' - help = desc.strip().split('\n')[0] - arguments = getattr(callback, 'arguments', []) - - subparser = subparsers.add_parser(command, help=help, description=desc) - for (args, kwargs) in arguments: - subparser.add_argument(*args, **kwargs) - subparser.set_defaults(func=callback) - - -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 the entity by name - resource = getattr(manager, 'resource_class', None) - attr = resource.NAME_ATTR if resource else 'name' - - listing = manager.list() - matches = [obj for obj in listing if getattr(obj, attr) == name_or_id] - - num_matches = len(matches) - if num_matches == 0: - msg = "No %s with name '%s' exists." % ( - manager.resource_class.__name__.lower(), name_or_id) - raise exc.CommandError(msg) - elif num_matches > 1: - msg = "Multiple instances of %s with name '%s' exist." % ( - manager.resource_class.__name__.lower(), name_or_id) - raise exc.CommandError(msg) - - return matches[0] - - -def import_versioned_module(version, submodule=None): - module = 'tuskarclient.v%s' % version - if submodule: - module = '.'.join((module, submodule)) - return importutils.import_module(module) - - -def format_key_value(params): - """Parse a list of k=v strings into an iterator of (k,v). - - :raises: CommandError - """ - if not params: - raise StopIteration - - for param in params: - try: - (name, value) = param.split(('='), 1) - except ValueError: - msg = _('Malformed parameter({0}). Use the key=value format.') - raise exc.CommandError(msg.format(param)) - yield name, value - - -def format_key_value_args(params): - """Reformat CLI attributes into the structure expected by the API. - - The format expected by the API for attributes is a dictionary consisting - of only string keys and values. - - :raises: ValidationError - """ - attributes = {} - - for key, value in format_key_value(params): - - if key in attributes: - raise exc.ValidationError( - _("The attribute name {0} can't be given twice.").format(key)) - - attributes[key] = value - - return attributes - - -def parameters_args_to_patch(parameters): - """Create a list of patch dicts to update the parameters in the API.""" - - return [{'name': pair[0], 'value': pair[1]} - for pair in sorted(format_key_value_args(parameters).items())] - - -def args_to_patch(flavors, roles, parameter): - """Create a list of dicts to update the given parameter in the API.""" - - role_flavors_dict = format_key_value_args(flavors) - - roles = dict(("{0}-{1}".format(r.name, r.version), r) for r in roles) - patch = [] - - for role_name, flavor in sorted(role_flavors_dict.items()): - - if role_name in roles: - patch.append({ - 'name': "{0}::{1}".format(role_name, parameter), - 'value': flavor - }) - else: - print("ERROR: no roles were found in the Plan with the name {0}". - format(role_name), file=sys.stderr) - continue - - return patch diff --git a/tuskarclient/i18n.py b/tuskarclient/i18n.py deleted file mode 100644 index 5aa1c18..0000000 --- a/tuskarclient/i18n.py +++ /dev/null @@ -1,35 +0,0 @@ -# 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 for tuskarclient. - -See http://docs.openstack.org/developer/oslo.i18n/usage.html . - -""" - -import oslo_i18n - - -_translators = oslo_i18n.TranslatorFactory(domain='tuskarclient') - -# The primary translation function using the well-known name "_" -_ = _translators.primary - -# Translators for log levels. -# -# The abbreviated names are meant to reflect the usual use of a short -# name like '_'. The "L" is for "log" and the other letter comes from -# the level. -_LI = _translators.log_info -_LW = _translators.log_warning -_LE = _translators.log_error -_LC = _translators.log_critical diff --git a/tuskarclient/openstack/__init__.py b/tuskarclient/openstack/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tuskarclient/openstack/common/__init__.py b/tuskarclient/openstack/common/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tuskarclient/openstack/common/_i18n.py b/tuskarclient/openstack/common/_i18n.py deleted file mode 100644 index c570579..0000000 --- a/tuskarclient/openstack/common/_i18n.py +++ /dev/null @@ -1,45 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""oslo.i18n integration module. - -See http://docs.openstack.org/developer/oslo.i18n/usage.html - -""" - -try: - 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='tuskarclient') - - # The primary translation function using the well-known name "_" - _ = _translators.primary - - # Translators for log levels. - # - # The abbreviated names are meant to reflect the usual use of a short - # name like '_'. The "L" is for "log" and the other letter comes from - # the level. - _LI = _translators.log_info - _LW = _translators.log_warning - _LE = _translators.log_error - _LC = _translators.log_critical -except ImportError: - # NOTE(dims): Support for cases where a project wants to use - # code from oslo-incubator, but is not ready to be internationalized - # (like tempest) - _ = _LI = _LW = _LE = _LC = lambda x: x diff --git a/tuskarclient/openstack/common/apiclient/__init__.py b/tuskarclient/openstack/common/apiclient/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tuskarclient/openstack/common/apiclient/auth.py b/tuskarclient/openstack/common/apiclient/auth.py deleted file mode 100644 index 70a995f..0000000 --- a/tuskarclient/openstack/common/apiclient/auth.py +++ /dev/null @@ -1,234 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# Copyright 2013 Spanish National Research Council. -# 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. - -# E0202: An attribute inherited from %s hide this method -# pylint: disable=E0202 - -######################################################################## -# -# THIS MODULE IS DEPRECATED -# -# Please refer to -# https://etherpad.openstack.org/p/kilo-tuskarclient-library-proposals for -# the discussion leading to this deprecation. -# -# We recommend checking out the python-openstacksdk project -# (https://launchpad.net/python-openstacksdk) instead. -# -######################################################################## - -import abc -import argparse -import os - -import six -from stevedore import extension - -from tuskarclient.openstack.common.apiclient import exceptions - - -_discovered_plugins = {} - - -def discover_auth_systems(): - """Discover the available auth-systems. - - This won't take into account the old style auth-systems. - """ - global _discovered_plugins - _discovered_plugins = {} - - def add_plugin(ext): - _discovered_plugins[ext.name] = ext.plugin - - ep_namespace = "tuskarclient.openstack.common.apiclient.auth" - mgr = extension.ExtensionManager(ep_namespace) - mgr.map(add_plugin) - - -def load_auth_system_opts(parser): - """Load options needed by the available auth-systems into a parser. - - This function will try to populate the parser with options from the - available plugins. - """ - group = parser.add_argument_group("Common auth options") - BaseAuthPlugin.add_common_opts(group) - for name, auth_plugin in six.iteritems(_discovered_plugins): - group = parser.add_argument_group( - "Auth-system '%s' options" % name, - conflict_handler="resolve") - auth_plugin.add_opts(group) - - -def load_plugin(auth_system): - try: - plugin_class = _discovered_plugins[auth_system] - except KeyError: - raise exceptions.AuthSystemNotFound(auth_system) - return plugin_class(auth_system=auth_system) - - -def load_plugin_from_args(args): - """Load required plugin and populate it with options. - - Try to guess auth system if it is not specified. Systems are tried in - alphabetical order. - - :type args: argparse.Namespace - :raises: AuthPluginOptionsMissing - """ - auth_system = args.os_auth_system - if auth_system: - plugin = load_plugin(auth_system) - plugin.parse_opts(args) - plugin.sufficient_options() - return plugin - - for plugin_auth_system in sorted(six.iterkeys(_discovered_plugins)): - plugin_class = _discovered_plugins[plugin_auth_system] - plugin = plugin_class() - plugin.parse_opts(args) - try: - plugin.sufficient_options() - except exceptions.AuthPluginOptionsMissing: - continue - return plugin - raise exceptions.AuthPluginOptionsMissing(["auth_system"]) - - -@six.add_metaclass(abc.ABCMeta) -class BaseAuthPlugin(object): - """Base class for authentication plugins. - - An authentication plugin needs to override at least the authenticate - method to be a valid plugin. - """ - - auth_system = None - opt_names = [] - common_opt_names = [ - "auth_system", - "username", - "password", - "tenant_name", - "token", - "auth_url", - ] - - def __init__(self, auth_system=None, **kwargs): - self.auth_system = auth_system or self.auth_system - self.opts = dict((name, kwargs.get(name)) - for name in self.opt_names) - - @staticmethod - def _parser_add_opt(parser, opt): - """Add an option to parser in two variants. - - :param opt: option name (with underscores) - """ - dashed_opt = opt.replace("_", "-") - env_var = "OS_%s" % opt.upper() - arg_default = os.environ.get(env_var, "") - arg_help = "Defaults to env[%s]." % env_var - parser.add_argument( - "--os-%s" % dashed_opt, - metavar="<%s>" % dashed_opt, - default=arg_default, - help=arg_help) - parser.add_argument( - "--os_%s" % opt, - metavar="<%s>" % dashed_opt, - help=argparse.SUPPRESS) - - @classmethod - def add_opts(cls, parser): - """Populate the parser with the options for this plugin. - """ - for opt in cls.opt_names: - # use `BaseAuthPlugin.common_opt_names` since it is never - # changed in child classes - if opt not in BaseAuthPlugin.common_opt_names: - cls._parser_add_opt(parser, opt) - - @classmethod - def add_common_opts(cls, parser): - """Add options that are common for several plugins. - """ - for opt in cls.common_opt_names: - cls._parser_add_opt(parser, opt) - - @staticmethod - def get_opt(opt_name, args): - """Return option name and value. - - :param opt_name: name of the option, e.g., "username" - :param args: parsed arguments - """ - return (opt_name, getattr(args, "os_%s" % opt_name, None)) - - def parse_opts(self, args): - """Parse the actual auth-system options if any. - - This method is expected to populate the attribute `self.opts` with a - dict containing the options and values needed to make authentication. - """ - self.opts.update(dict(self.get_opt(opt_name, args) - for opt_name in self.opt_names)) - - def authenticate(self, http_client): - """Authenticate using plugin defined method. - - The method usually analyses `self.opts` and performs - a request to authentication server. - - :param http_client: client object that needs authentication - :type http_client: HTTPClient - :raises: AuthorizationFailure - """ - self.sufficient_options() - self._do_authenticate(http_client) - - @abc.abstractmethod - def _do_authenticate(self, http_client): - """Protected method for authentication. - """ - - def sufficient_options(self): - """Check if all required options are present. - - :raises: AuthPluginOptionsMissing - """ - missing = [opt - for opt in self.opt_names - if not self.opts.get(opt)] - if missing: - raise exceptions.AuthPluginOptionsMissing(missing) - - @abc.abstractmethod - def token_and_endpoint(self, endpoint_type, service_type): - """Return token and endpoint. - - :param service_type: Service type of the endpoint - :type service_type: string - :param endpoint_type: Type of endpoint. - Possible values: public or publicURL, - internal or internalURL, - admin or adminURL - :type endpoint_type: string - :returns: tuple of token and endpoint strings - :raises: EndpointException - """ diff --git a/tuskarclient/openstack/common/apiclient/base.py b/tuskarclient/openstack/common/apiclient/base.py deleted file mode 100644 index 69f2b81..0000000 --- a/tuskarclient/openstack/common/apiclient/base.py +++ /dev/null @@ -1,539 +0,0 @@ -# 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. -""" - -######################################################################## -# -# THIS MODULE IS DEPRECATED -# -# Please refer to -# https://etherpad.openstack.org/p/kilo-tuskarclient-library-proposals for -# the discussion leading to this deprecation. -# -# We recommend checking out the python-openstacksdk project -# (https://launchpad.net/python-openstacksdk) instead. -# -######################################################################## - - -# E1102: %s is not callable -# pylint: disable=E1102 - -import abc -import copy - -from oslo_utils import strutils -import six -from six.moves.urllib import parse - -from tuskarclient.openstack.common._i18n import _ -from tuskarclient.openstack.common.apiclient import 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, response_key=None): - """Delete an object. - - :param url: a partial URL, e.g., '/servers/my-server' - """ - resp = self.client.delete(url) - # DELETE 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) - - -@six.add_metaclass(abc.ABCMeta) -class ManagerWithFind(BaseManager): - """Manager with additional `find()`/`findall()` methods.""" - - @abc.abstractmethod - def list(self): - pass - - def find(self, **kwargs): - """Find a single item with attributes matching ``**kwargs``. - - This isn't very efficient: it loads the entire list then filters on - the Python side. - """ - matches = self.findall(**kwargs) - num_matches = len(matches) - if num_matches == 0: - msg = _("No %(name)s matching %(args)s.") % { - 'name': self.resource_class.__name__, - 'args': kwargs - } - raise exceptions.NotFound(msg) - elif num_matches > 1: - raise exceptions.NoUniqueMatch() - else: - return matches[0] - - def findall(self, **kwargs): - """Find all items with attributes matching ``**kwargs``. - - This isn't very efficient: it loads the entire list then filters on - the Python side. - """ - found = [] - searches = kwargs.items() - - for obj in self.list(): - try: - if all(getattr(obj, attr) == value - for (attr, value) in searches): - found.append(obj) - except AttributeError: - continue - - return found - - -class CrudManager(BaseManager): - """Base manager class for manipulating entities. - - Children of this class are expected to define a `collection_key` and `key`. - - - `collection_key`: Usually a plural noun by convention (e.g. `entities`); - used to refer collections in both URL's (e.g. `/v3/entities`) and JSON - objects containing a list of member resources (e.g. `{'entities': [{}, - {}, {}]}`). - - `key`: Usually a singular noun by convention (e.g. `entity`); used to - refer to an individual member of the collection. - - """ - collection_key = None - key = None - - def build_url(self, base_url=None, **kwargs): - """Builds a resource URL for the given kwargs. - - Given an example collection where `collection_key = 'entities'` and - `key = 'entity'`, the following URL's could be generated. - - By default, the URL will represent a collection of entities, e.g.:: - - /entities - - If kwargs contains an `entity_id`, then the URL will represent a - specific member, e.g.:: - - /entities/{entity_id} - - :param base_url: if provided, the generated URL will be appended to it - """ - url = base_url if base_url is not None else '' - - url += '/%s' % self.collection_key - - # do we have a specific entity? - entity_id = kwargs.get('%s_id' % self.key) - if entity_id is not None: - url += '/%s' % entity_id - - return url - - def _filter_kwargs(self, kwargs): - """Drop null values and handle ids.""" - for key, ref in six.iteritems(kwargs.copy()): - if ref is None: - kwargs.pop(key) - else: - if isinstance(ref, Resource): - kwargs.pop(key) - kwargs['%s_id' % key] = getid(ref) - return kwargs - - def create(self, **kwargs): - kwargs = self._filter_kwargs(kwargs) - return self._post( - self.build_url(**kwargs), - {self.key: kwargs}, - self.key) - - def get(self, **kwargs): - kwargs = self._filter_kwargs(kwargs) - return self._get( - self.build_url(**kwargs), - self.key) - - def head(self, **kwargs): - kwargs = self._filter_kwargs(kwargs) - return self._head(self.build_url(**kwargs)) - - def list(self, base_url=None, **kwargs): - """List the collection. - - :param base_url: if provided, the generated URL will be appended to it - """ - kwargs = self._filter_kwargs(kwargs) - - return self._list( - '%(base_url)s%(query)s' % { - 'base_url': self.build_url(base_url=base_url, **kwargs), - 'query': '?%s' % 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(404, msg) - elif num > 1: - raise exceptions.NoUniqueMatch - else: - return rl[0] - - -class Extension(HookableMixin): - """Extension descriptor.""" - - SUPPORTED_HOOKS = ('__pre_parse_args__', '__post_parse_args__') - manager_class = None - - def __init__(self, name, module): - super(Extension, self).__init__() - self.name = name - self.module = module - self._parse_extension_module() - - def _parse_extension_module(self): - self.manager_class = None - for attr_name, attr_value in self.module.__dict__.items(): - if attr_name in self.SUPPORTED_HOOKS: - self.add_hook(attr_name, attr_value) - else: - try: - if issubclass(attr_value, BaseManager): - self.manager_class = attr_value - except TypeError: - pass - - def __repr__(self): - return "" % self.name - - -class Resource(object): - """Base class for OpenStack resources (tenant, user, etc.). - - This is pretty much just a bag for attributes. - """ - - HUMAN_ID = False - NAME_ATTR = 'name' - - def __init__(self, manager, info, loaded=False): - """Populate and bind to a manager. - - :param manager: BaseManager object - :param info: dictionary representing resource attributes - :param loaded: prevent lazy-loading if set to True - """ - self.manager = manager - self._info = info - self._add_details(info) - self._loaded = loaded - - def __repr__(self): - reprkeys = sorted(k - for k in self.__dict__.keys() - if k[0] != '_' and k != 'manager') - info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) - return "<%s %s>" % (self.__class__.__name__, info) - - @property - def human_id(self): - """Human-readable ID which can be used for bash completion. - """ - if self.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 six.iteritems(info): - try: - setattr(self, k, v) - self._info[k] = v - except AttributeError: - # In this case we already defined the attribute on the class - pass - - def __getattr__(self, k): - if k not in self.__dict__: - # NOTE(bcwaldon): disallow lazy-loading if already loaded once - if not self.is_loaded(): - self.get() - return self.__getattr__(k) - - raise AttributeError(k) - else: - return self.__dict__[k] - - def get(self): - """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 - if hasattr(self, 'id') and hasattr(other, 'id'): - return self.id == other.id - return self._info == other._info - - def is_loaded(self): - return self._loaded - - def set_loaded(self, val): - self._loaded = val - - def to_dict(self): - return copy.deepcopy(self._info) diff --git a/tuskarclient/openstack/common/apiclient/client.py b/tuskarclient/openstack/common/apiclient/client.py deleted file mode 100644 index 9f05cea..0000000 --- a/tuskarclient/openstack/common/apiclient/client.py +++ /dev/null @@ -1,388 +0,0 @@ -# Copyright 2010 Jacob Kaplan-Moss -# Copyright 2011 OpenStack Foundation -# Copyright 2011 Piston Cloud Computing, Inc. -# Copyright 2013 Alessio Ababilov -# Copyright 2013 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. - -""" -OpenStack Client interface. Handles the REST calls and responses. -""" - -# E0202: An attribute inherited from %s hide this method -# pylint: disable=E0202 - -import hashlib -import logging -import time - -try: - import simplejson as json -except ImportError: - import json - -from oslo_utils import encodeutils -from oslo_utils import importutils -import requests - -from tuskarclient.openstack.common._i18n import _ -from tuskarclient.openstack.common.apiclient import exceptions - -_logger = logging.getLogger(__name__) -SENSITIVE_HEADERS = ('X-Auth-Token', 'X-Subject-Token',) - - -class HTTPClient(object): - """This client handles sending HTTP requests to OpenStack servers. - - Features: - - - share authentication information between several clients to different - services (e.g., for compute and image clients); - - reissue authentication request for expired tokens; - - encode/decode JSON bodies; - - raise exceptions on HTTP errors; - - pluggable authentication; - - store authentication information in a keyring; - - store time spent for requests; - - register clients for particular services, so one can use - `http_client.identity` or `http_client.compute`; - - log requests and responses in a format that is easy to copy-and-paste - into terminal and send the same request with curl. - """ - - user_agent = "tuskarclient.openstack.common.apiclient" - - def __init__(self, - auth_plugin, - region_name=None, - endpoint_type="publicURL", - original_ip=None, - verify=True, - cert=None, - timeout=None, - timings=False, - keyring_saver=None, - debug=False, - user_agent=None, - http=None): - self.auth_plugin = auth_plugin - - self.endpoint_type = endpoint_type - self.region_name = region_name - - self.original_ip = original_ip - self.timeout = timeout - self.verify = verify - self.cert = cert - - self.keyring_saver = keyring_saver - self.debug = debug - self.user_agent = user_agent or self.user_agent - - self.times = [] # [("item", starttime, endtime), ...] - self.timings = timings - - # requests within the same session can reuse TCP connections from pool - self.http = http or requests.Session() - - self.cached_token = None - 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 _http_log_req(self, method, url, kwargs): - if not self.debug: - return - - string_parts = [ - "curl -g -i", - "-X '%s'" % method, - "'%s'" % url, - ] - - for element in kwargs['headers']: - header = ("-H '%s: %s'" % - self._safe_header(element, kwargs['headers'][element])) - string_parts.append(header) - - _logger.debug("REQ: %s" % " ".join(string_parts)) - if 'data' in kwargs: - _logger.debug("REQ BODY: %s\n" % (kwargs['data'])) - - def _http_log_resp(self, resp): - if not self.debug: - return - _logger.debug( - "RESP: [%s] %s\n", - resp.status_code, - resp.headers) - if resp._content_consumed: - _logger.debug( - "RESP BODY: %s\n", - resp.text) - - def serialize(self, kwargs): - if kwargs.get('json') is not None: - kwargs['headers']['Content-Type'] = 'application/json' - kwargs['data'] = json.dumps(kwargs['json']) - try: - del kwargs['json'] - except KeyError: - pass - - def get_timings(self): - return self.times - - def reset_timings(self): - self.times = [] - - def request(self, method, url, **kwargs): - """Send an http request with the specified characteristics. - - Wrapper around `requests.Session.request` to handle tasks such as - setting headers, JSON encoding/decoding, and error handling. - - :param method: method of HTTP request - :param url: URL of HTTP request - :param kwargs: any other parameter that can be passed to - requests.Session.request (such as `headers`) or `json` - that will be encoded as JSON and used as `data` argument - """ - kwargs.setdefault("headers", {}) - kwargs["headers"]["User-Agent"] = self.user_agent - if self.original_ip: - kwargs["headers"]["Forwarded"] = "for=%s;by=%s" % ( - self.original_ip, self.user_agent) - if self.timeout is not None: - kwargs.setdefault("timeout", self.timeout) - kwargs.setdefault("verify", self.verify) - if self.cert is not None: - kwargs.setdefault("cert", self.cert) - self.serialize(kwargs) - - self._http_log_req(method, url, kwargs) - if self.timings: - start_time = time.time() - resp = self.http.request(method, url, **kwargs) - if self.timings: - self.times.append(("%s %s" % (method, url), - start_time, time.time())) - self._http_log_resp(resp) - - self.last_request_id = resp.headers.get('x-openstack-request-id') - - if resp.status_code >= 400: - _logger.debug( - "Request returned failure status: %s", - resp.status_code) - raise exceptions.from_response(resp, method, url) - - return resp - - @staticmethod - def concat_url(endpoint, url): - """Concatenate endpoint and final URL. - - E.g., "http://keystone/v2.0/" and "/tokens" are concatenated to - "http://keystone/v2.0/tokens". - - :param endpoint: the base URL - :param url: the final URL - """ - return "%s/%s" % (endpoint.rstrip("/"), url.strip("/")) - - def client_request(self, client, method, url, **kwargs): - """Send an http request using `client`'s endpoint and specified `url`. - - If request was rejected as unauthorized (possibly because the token is - expired), issue one authorization attempt and send the request once - again. - - :param client: instance of BaseClient descendant - :param method: method of HTTP request - :param url: URL of HTTP request - :param kwargs: any other parameter that can be passed to - `HTTPClient.request` - """ - - filter_args = { - "endpoint_type": client.endpoint_type or self.endpoint_type, - "service_type": client.service_type, - } - token, endpoint = (self.cached_token, client.cached_endpoint) - just_authenticated = False - if not (token and endpoint): - try: - token, endpoint = self.auth_plugin.token_and_endpoint( - **filter_args) - except exceptions.EndpointException: - pass - if not (token and endpoint): - self.authenticate() - just_authenticated = True - token, endpoint = self.auth_plugin.token_and_endpoint( - **filter_args) - if not (token and endpoint): - raise exceptions.AuthorizationFailure( - _("Cannot find endpoint or token for request")) - - old_token_endpoint = (token, endpoint) - kwargs.setdefault("headers", {})["X-Auth-Token"] = token - self.cached_token = token - client.cached_endpoint = endpoint - # Perform the request once. If we get Unauthorized, then it - # might be because the auth token expired, so try to - # re-authenticate and try again. If it still fails, bail. - try: - return self.request( - method, self.concat_url(endpoint, url), **kwargs) - except exceptions.Unauthorized as unauth_ex: - if just_authenticated: - raise - self.cached_token = None - client.cached_endpoint = None - if self.auth_plugin.opts.get('token'): - self.auth_plugin.opts['token'] = None - if self.auth_plugin.opts.get('endpoint'): - self.auth_plugin.opts['endpoint'] = None - self.authenticate() - try: - token, endpoint = self.auth_plugin.token_and_endpoint( - **filter_args) - except exceptions.EndpointException: - raise unauth_ex - if (not (token and endpoint) or - old_token_endpoint == (token, endpoint)): - raise unauth_ex - self.cached_token = token - client.cached_endpoint = endpoint - kwargs["headers"]["X-Auth-Token"] = token - return self.request( - method, self.concat_url(endpoint, url), **kwargs) - - def add_client(self, base_client_instance): - """Add a new instance of :class:`BaseClient` descendant. - - `self` will store a reference to `base_client_instance`. - - Example: - - >>> def test_clients(): - ... from keystoneclient.auth import keystone - ... from openstack.common.apiclient import client - ... auth = keystone.KeystoneAuthPlugin( - ... username="user", password="pass", tenant_name="tenant", - ... auth_url="http://auth:5000/v2.0") - ... openstack_client = client.HTTPClient(auth) - ... # create nova client - ... from novaclient.v1_1 import client - ... client.Client(openstack_client) - ... # create keystone client - ... from keystoneclient.v2_0 import client - ... client.Client(openstack_client) - ... # use them - ... openstack_client.identity.tenants.list() - ... openstack_client.compute.servers.list() - """ - service_type = base_client_instance.service_type - if service_type and not hasattr(self, service_type): - setattr(self, service_type, base_client_instance) - - def authenticate(self): - self.auth_plugin.authenticate(self) - # Store the authentication results in the keyring for later requests - if self.keyring_saver: - self.keyring_saver.save(self) - - -class BaseClient(object): - """Top-level object to access the OpenStack API. - - This client uses :class:`HTTPClient` to send requests. :class:`HTTPClient` - will handle a bunch of issues such as authentication. - """ - - service_type = None - endpoint_type = None # "publicURL" will be used - cached_endpoint = None - - def __init__(self, http_client, extensions=None): - self.http_client = http_client - http_client.add_client(self) - - # Add in any extensions... - if extensions: - for extension in extensions: - if extension.manager_class: - setattr(self, extension.name, - extension.manager_class(self)) - - def client_request(self, method, url, **kwargs): - return self.http_client.client_request( - self, method, url, **kwargs) - - @property - def last_request_id(self): - return self.http_client.last_request_id - - 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.client_request("DELETE", url, **kwargs) - - def patch(self, url, **kwargs): - return self.client_request("PATCH", url, **kwargs) - - @staticmethod - def get_class(api_name, version, version_map): - """Returns the client class for the requested API version - - :param api_name: the name of the API, e.g. 'compute', 'image', etc - :param version: the requested API version - :param version_map: a dict of client classes keyed by version - :rtype: a client class for the requested API version - """ - try: - client_path = version_map[str(version)] - except (KeyError, ValueError): - msg = _("Invalid %(api_name)s client version '%(version)s'. " - "Must be one of: %(version_map)s") % { - 'api_name': api_name, - 'version': version, - 'version_map': ', '.join(version_map.keys())} - raise exceptions.UnsupportedVersion(msg) - - return importutils.import_class(client_path) diff --git a/tuskarclient/openstack/common/apiclient/exceptions.py b/tuskarclient/openstack/common/apiclient/exceptions.py deleted file mode 100644 index 77615a7..0000000 --- a/tuskarclient/openstack/common/apiclient/exceptions.py +++ /dev/null @@ -1,479 +0,0 @@ -# Copyright 2010 Jacob Kaplan-Moss -# Copyright 2011 Nebula, Inc. -# Copyright 2013 Alessio Ababilov -# Copyright 2013 OpenStack Foundation -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Exception definitions. -""" - -######################################################################## -# -# THIS MODULE IS DEPRECATED -# -# Please refer to -# https://etherpad.openstack.org/p/kilo-tuskarclient-library-proposals for -# the discussion leading to this deprecation. -# -# We recommend checking out the python-openstacksdk project -# (https://launchpad.net/python-openstacksdk) instead. -# -######################################################################## - -import inspect -import sys - -import six - -from tuskarclient.openstack.common._i18n import _ - - -class ClientException(Exception): - """The base exception class for all exceptions this library raises. - """ - pass - - -class ValidationError(ClientException): - """Error in validation on API client side.""" - pass - - -class UnsupportedVersion(ClientException): - """User is trying to use an unsupported version of the API.""" - pass - - -class CommandError(ClientException): - """Error in CLI tool.""" - pass - - -class AuthorizationFailure(ClientException): - """Cannot authorize API client.""" - pass - - -class ConnectionError(ClientException): - """Cannot connect to API service.""" - pass - - -class ConnectionRefused(ConnectionError): - """Connection refused while trying to connect to API service.""" - pass - - -class AuthPluginOptionsMissing(AuthorizationFailure): - """Auth plugin misses some options.""" - def __init__(self, opt_names): - super(AuthPluginOptionsMissing, self).__init__( - _("Authentication failed. Missing options: %s") % - ", ".join(opt_names)) - self.opt_names = opt_names - - -class AuthSystemNotFound(AuthorizationFailure): - """User has specified an AuthSystem that is not installed.""" - def __init__(self, auth_system): - super(AuthSystemNotFound, self).__init__( - _("AuthSystemNotFound: %r") % auth_system) - self.auth_system = auth_system - - -class NoUniqueMatch(ClientException): - """Multiple entities found instead of one.""" - pass - - -class EndpointException(ClientException): - """Something is rotten in Service Catalog.""" - pass - - -class EndpointNotFound(EndpointException): - """Could not find requested endpoint in Service Catalog.""" - pass - - -class AmbiguousEndpoints(EndpointException): - """Found more than one matching endpoint in Service Catalog.""" - def __init__(self, endpoints=None): - super(AmbiguousEndpoints, self).__init__( - _("AmbiguousEndpoints: %r") % endpoints) - self.endpoints = endpoints - - -class HttpError(ClientException): - """The base exception class for all HTTP exceptions. - """ - http_status = 0 - message = _("HTTP Error") - - def __init__(self, message=None, details=None, - response=None, request_id=None, - url=None, method=None, http_status=None): - self.http_status = http_status or self.http_status - self.message = message or self.message - self.details = details - self.request_id = request_id - self.response = response - self.url = url - self.method = method - formatted_string = "%s (HTTP %s)" % (self.message, self.http_status) - if request_id: - formatted_string += " (Request-ID: %s)" % request_id - super(HttpError, self).__init__(formatted_string) - - -class HTTPRedirection(HttpError): - """HTTP Redirection.""" - message = _("HTTP Redirection") - - -class HTTPClientError(HttpError): - """Client-side HTTP error. - - Exception for cases in which the client seems to have erred. - """ - message = _("HTTP Client Error") - - -class HttpServerError(HttpError): - """Server-side HTTP error. - - Exception for cases in which the server is aware that it has - erred or is incapable of performing the request. - """ - message = _("HTTP Server Error") - - -class MultipleChoices(HTTPRedirection): - """HTTP 300 - Multiple Choices. - - Indicates multiple options for the resource that the client may follow. - """ - - http_status = 300 - message = _("Multiple Choices") - - -class BadRequest(HTTPClientError): - """HTTP 400 - Bad Request. - - The request cannot be fulfilled due to bad syntax. - """ - http_status = 400 - message = _("Bad Request") - - -class Unauthorized(HTTPClientError): - """HTTP 401 - Unauthorized. - - Similar to 403 Forbidden, but specifically for use when authentication - is required and has failed or has not yet been provided. - """ - http_status = 401 - message = _("Unauthorized") - - -class PaymentRequired(HTTPClientError): - """HTTP 402 - Payment Required. - - Reserved for future use. - """ - http_status = 402 - message = _("Payment Required") - - -class Forbidden(HTTPClientError): - """HTTP 403 - Forbidden. - - The request was a valid request, but the server is refusing to respond - to it. - """ - http_status = 403 - message = _("Forbidden") - - -class NotFound(HTTPClientError): - """HTTP 404 - Not Found. - - The requested resource could not be found but may be available again - in the future. - """ - http_status = 404 - message = _("Not Found") - - -class MethodNotAllowed(HTTPClientError): - """HTTP 405 - Method Not Allowed. - - A request was made of a resource using a request method not supported - by that resource. - """ - http_status = 405 - message = _("Method Not Allowed") - - -class NotAcceptable(HTTPClientError): - """HTTP 406 - Not Acceptable. - - The requested resource is only capable of generating content not - acceptable according to the Accept headers sent in the request. - """ - http_status = 406 - message = _("Not Acceptable") - - -class ProxyAuthenticationRequired(HTTPClientError): - """HTTP 407 - Proxy Authentication Required. - - The client must first authenticate itself with the proxy. - """ - http_status = 407 - message = _("Proxy Authentication Required") - - -class RequestTimeout(HTTPClientError): - """HTTP 408 - Request Timeout. - - The server timed out waiting for the request. - """ - http_status = 408 - message = _("Request Timeout") - - -class Conflict(HTTPClientError): - """HTTP 409 - Conflict. - - Indicates that the request could not be processed because of conflict - in the request, such as an edit conflict. - """ - http_status = 409 - message = _("Conflict") - - -class Gone(HTTPClientError): - """HTTP 410 - Gone. - - Indicates that the resource requested is no longer available and will - not be available again. - """ - http_status = 410 - message = _("Gone") - - -class LengthRequired(HTTPClientError): - """HTTP 411 - Length Required. - - The request did not specify the length of its content, which is - required by the requested resource. - """ - http_status = 411 - message = _("Length Required") - - -class PreconditionFailed(HTTPClientError): - """HTTP 412 - Precondition Failed. - - The server does not meet one of the preconditions that the requester - put on the request. - """ - http_status = 412 - message = _("Precondition Failed") - - -class RequestEntityTooLarge(HTTPClientError): - """HTTP 413 - Request Entity Too Large. - - The request is larger than the server is willing or able to process. - """ - http_status = 413 - message = _("Request Entity Too Large") - - def __init__(self, *args, **kwargs): - try: - self.retry_after = int(kwargs.pop('retry_after')) - except (KeyError, ValueError): - self.retry_after = 0 - - super(RequestEntityTooLarge, self).__init__(*args, **kwargs) - - -class RequestUriTooLong(HTTPClientError): - """HTTP 414 - Request-URI Too Long. - - The URI provided was too long for the server to process. - """ - http_status = 414 - message = _("Request-URI Too Long") - - -class UnsupportedMediaType(HTTPClientError): - """HTTP 415 - Unsupported Media Type. - - The request entity has a media type which the server or resource does - not support. - """ - http_status = 415 - message = _("Unsupported Media Type") - - -class RequestedRangeNotSatisfiable(HTTPClientError): - """HTTP 416 - Requested Range Not Satisfiable. - - The client has asked for a portion of the file, but the server cannot - supply that portion. - """ - http_status = 416 - message = _("Requested Range Not Satisfiable") - - -class ExpectationFailed(HTTPClientError): - """HTTP 417 - Expectation Failed. - - The server cannot meet the requirements of the Expect request-header field. - """ - http_status = 417 - message = _("Expectation Failed") - - -class UnprocessableEntity(HTTPClientError): - """HTTP 422 - Unprocessable Entity. - - The request was well-formed but was unable to be followed due to semantic - errors. - """ - http_status = 422 - message = _("Unprocessable Entity") - - -class InternalServerError(HttpServerError): - """HTTP 500 - Internal Server Error. - - A generic error message, given when no more specific message is suitable. - """ - http_status = 500 - message = _("Internal Server Error") - - -# NotImplemented is a python keyword. -class HttpNotImplemented(HttpServerError): - """HTTP 501 - Not Implemented. - - The server either does not recognize the request method, or it lacks - the ability to fulfill the request. - """ - http_status = 501 - message = _("Not Implemented") - - -class BadGateway(HttpServerError): - """HTTP 502 - Bad Gateway. - - The server was acting as a gateway or proxy and received an invalid - response from the upstream server. - """ - http_status = 502 - message = _("Bad Gateway") - - -class ServiceUnavailable(HttpServerError): - """HTTP 503 - Service Unavailable. - - The server is currently unavailable. - """ - http_status = 503 - message = _("Service Unavailable") - - -class GatewayTimeout(HttpServerError): - """HTTP 504 - Gateway Timeout. - - The server was acting as a gateway or proxy and did not receive a timely - response from the upstream server. - """ - http_status = 504 - message = _("Gateway Timeout") - - -class HttpVersionNotSupported(HttpServerError): - """HTTP 505 - HttpVersion Not Supported. - - The server does not support the HTTP protocol version used in the request. - """ - http_status = 505 - message = _("HTTP Version Not Supported") - - -# _code_map contains all the classes that have http_status attribute. -_code_map = dict( - (getattr(obj, 'http_status', None), obj) - for name, obj in six.iteritems(vars(sys.modules[__name__])) - if inspect.isclass(obj) and getattr(obj, 'http_status', False) -) - - -def from_response(response, method, url): - """Returns an instance of :class:`HttpError` or subclass based on response. - - :param response: instance of `requests.Response` class - :param method: HTTP method used for request - :param url: URL used for request - """ - - req_id = response.headers.get("x-openstack-request-id") - # NOTE(hdd) true for older versions of nova and cinder - if not req_id: - req_id = response.headers.get("x-compute-request-id") - kwargs = { - "http_status": response.status_code, - "response": response, - "method": method, - "url": url, - "request_id": req_id, - } - if "retry-after" in response.headers: - kwargs["retry_after"] = response.headers["retry-after"] - - content_type = response.headers.get("Content-Type", "") - if content_type.startswith("application/json"): - try: - body = response.json() - except ValueError: - pass - else: - if isinstance(body, dict): - error = body.get(list(body)[0]) - if isinstance(error, dict): - kwargs["message"] = (error.get("message") or - error.get("faultstring")) - kwargs["details"] = (error.get("details") or - six.text_type(body)) - elif content_type.startswith("text/"): - kwargs["details"] = response.text - - try: - cls = _code_map[response.status_code] - except KeyError: - if 500 <= response.status_code < 600: - cls = HttpServerError - elif 400 <= response.status_code < 500: - cls = HTTPClientError - else: - cls = HttpError - return cls(**kwargs) diff --git a/tuskarclient/openstack/common/apiclient/fake_client.py b/tuskarclient/openstack/common/apiclient/fake_client.py deleted file mode 100644 index d43117a..0000000 --- a/tuskarclient/openstack/common/apiclient/fake_client.py +++ /dev/null @@ -1,190 +0,0 @@ -# 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. - -""" -A fake server that "responds" to API methods with pre-canned responses. - -All of these responses come from the spec, so if for some reason the spec's -wrong the tests might raise AssertionError. I've indicated in comments the -places where actual behavior differs from the spec. -""" - -######################################################################## -# -# THIS MODULE IS DEPRECATED -# -# Please refer to -# https://etherpad.openstack.org/p/kilo-tuskarclient-library-proposals for -# the discussion leading to this deprecation. -# -# We recommend checking out the python-openstacksdk project -# (https://launchpad.net/python-openstacksdk) instead. -# -######################################################################## - -# W0102: Dangerous default value %s as argument -# pylint: disable=W0102 - -import json - -import requests -import six -from six.moves.urllib import parse - -from tuskarclient.openstack.common.apiclient import client - - -def assert_has_keys(dct, required=None, optional=None): - required = required or [] - optional = optional or [] - for k in required: - try: - assert k in dct - except AssertionError: - extra_keys = set(dct.keys()).difference(set(required + optional)) - raise AssertionError("found unexpected keys: %s" % - list(extra_keys)) - - -class TestResponse(requests.Response): - """Wrap requests.Response and provide a convenient initialization. - """ - - def __init__(self, data): - super(TestResponse, self).__init__() - self._content_consumed = True - if isinstance(data, dict): - self.status_code = data.get('status_code', 200) - # Fake the text attribute to streamline Response creation - text = data.get('text', "") - if isinstance(text, (dict, list)): - self._content = json.dumps(text) - default_headers = { - "Content-Type": "application/json", - } - else: - self._content = text - default_headers = {} - if six.PY3 and isinstance(self._content, six.string_types): - self._content = self._content.encode('utf-8', 'strict') - self.headers = data.get('headers') or default_headers - else: - self.status_code = data - - def __eq__(self, other): - return (self.status_code == other.status_code and - self.headers == other.headers and - self._content == other._content) - - -class FakeHTTPClient(client.HTTPClient): - - def __init__(self, *args, **kwargs): - self.callstack = [] - self.fixtures = kwargs.pop("fixtures", None) or {} - if not args and "auth_plugin" not in kwargs: - args = (None, ) - super(FakeHTTPClient, self).__init__(*args, **kwargs) - - def assert_called(self, method, url, body=None, pos=-1): - """Assert than an API method was just called. - """ - expected = (method, url) - called = self.callstack[pos][0:2] - assert self.callstack, \ - "Expected %s %s but no calls were made." % expected - - assert expected == called, 'Expected %s %s; got %s %s' % \ - (expected + called) - - if body is not None: - if self.callstack[pos][3] != body: - raise AssertionError('%r != %r' % - (self.callstack[pos][3], body)) - - def assert_called_anytime(self, method, url, body=None): - """Assert than an API method was called anytime in the test. - """ - expected = (method, url) - - assert self.callstack, \ - "Expected %s %s but no calls were made." % expected - - found = False - entry = None - for entry in self.callstack: - if expected == entry[0:2]: - found = True - break - - assert found, 'Expected %s %s; got %s' % \ - (method, url, self.callstack) - if body is not None: - assert entry[3] == body, "%s != %s" % (entry[3], body) - - self.callstack = [] - - def clear_callstack(self): - self.callstack = [] - - def authenticate(self): - pass - - def client_request(self, client, method, url, **kwargs): - # Check that certain things are called correctly - if method in ["GET", "DELETE"]: - assert "json" not in kwargs - - # Note the call - self.callstack.append( - (method, - url, - kwargs.get("headers") or {}, - kwargs.get("json") or kwargs.get("data"))) - try: - fixture = self.fixtures[url][method] - except KeyError: - pass - else: - return TestResponse({"headers": fixture[0], - "text": fixture[1]}) - - # Call the method - args = parse.parse_qsl(parse.urlparse(url)[4]) - kwargs.update(args) - munged_url = url.rsplit('?', 1)[0] - munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_') - munged_url = munged_url.replace('-', '_') - - callback = "%s_%s" % (method.lower(), munged_url) - - if not hasattr(self, callback): - raise AssertionError('Called unknown API method: %s %s, ' - 'expected fakes method name: %s' % - (method, url, callback)) - - resp = getattr(self, callback)(**kwargs) - if len(resp) == 3: - status, headers, body = resp - else: - status, body = resp - headers = {} - self.last_request_id = headers.get('x-openstack-request-id', - 'req-test') - return TestResponse({ - "status_code": status, - "text": body, - "headers": headers, - }) diff --git a/tuskarclient/openstack/common/apiclient/utils.py b/tuskarclient/openstack/common/apiclient/utils.py deleted file mode 100644 index 062e004..0000000 --- a/tuskarclient/openstack/common/apiclient/utils.py +++ /dev/null @@ -1,100 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -######################################################################## -# -# THIS MODULE IS DEPRECATED -# -# Please refer to -# https://etherpad.openstack.org/p/kilo-tuskarclient-library-proposals for -# the discussion leading to this deprecation. -# -# We recommend checking out the python-openstacksdk project -# (https://launchpad.net/python-openstacksdk) instead. -# -######################################################################## - -from oslo_utils import encodeutils -import six - -from tuskarclient.openstack.common._i18n import _ -from tuskarclient.openstack.common.apiclient import exceptions -from tuskarclient.openstack.common import uuidutils - - -def find_resource(manager, name_or_id, **find_args): - """Look for resource in a given manager. - - Used as a helper for the _find_* methods. - Example: - - .. code-block:: python - - def _find_hypervisor(cs, hypervisor): - #Get a hypervisor by name or ID. - return cliutils.find_resource(cs.hypervisors, hypervisor) - """ - # first try to get entity as integer id - try: - return manager.get(int(name_or_id)) - except (TypeError, ValueError, exceptions.NotFound): - pass - - # now try to get entity as uuid - try: - if six.PY2: - tmp_id = encodeutils.safe_encode(name_or_id) - else: - tmp_id = encodeutils.safe_decode(name_or_id) - - if uuidutils.is_uuid_like(tmp_id): - return manager.get(tmp_id) - except (TypeError, ValueError, exceptions.NotFound): - pass - - # for str id which is not uuid - if getattr(manager, 'is_alphanum_id_allowed', False): - try: - return manager.get(name_or_id) - except exceptions.NotFound: - pass - - try: - try: - return manager.find(human_id=name_or_id, **find_args) - except exceptions.NotFound: - pass - - # finally try to find entity by name - try: - resource = getattr(manager, 'resource_class', None) - name_attr = resource.NAME_ATTR if resource else 'name' - kwargs = {name_attr: name_or_id} - kwargs.update(find_args) - return manager.find(**kwargs) - except exceptions.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 exceptions.CommandError(msg) - except exceptions.NoUniqueMatch: - msg = _("Multiple %(name)s matches found for " - "'%(name_or_id)s', use an ID to be more specific.") % \ - { - "name": manager.resource_class.__name__.lower(), - "name_or_id": name_or_id - } - raise exceptions.CommandError(msg) diff --git a/tuskarclient/openstack/common/cliutils.py b/tuskarclient/openstack/common/cliutils.py deleted file mode 100644 index ae4e75b..0000000 --- a/tuskarclient/openstack/common/cliutils.py +++ /dev/null @@ -1,271 +0,0 @@ -# Copyright 2012 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. - -# W0603: Using the global statement -# W0621: Redefining name %s from outer scope -# pylint: disable=W0603,W0621 - -from __future__ import print_function - -import getpass -import inspect -import os -import sys -import textwrap - -from oslo_utils import encodeutils -from oslo_utils import strutils -import prettytable -import six -from six import moves - -from tuskarclient.openstack.common._i18n import _ - - -class MissingArgs(Exception): - """Supplied arguments are not sufficient for calling a function.""" - def __init__(self, missing): - self.missing = missing - msg = _("Missing arguments: %s") % ", ".join(missing) - super(MissingArgs, self).__init__(msg) - - -def validate_args(fn, *args, **kwargs): - """Check that the supplied args are sufficient for calling a function. - - >>> validate_args(lambda a: None) - Traceback (most recent call last): - ... - MissingArgs: Missing argument(s): a - >>> validate_args(lambda a, b, c, d: None, 0, c=1) - Traceback (most recent call last): - ... - MissingArgs: Missing argument(s): b, d - - :param fn: the function to check - :param arg: the positional arguments supplied - :param kwargs: the keyword arguments supplied - """ - argspec = inspect.getargspec(fn) - - num_defaults = len(argspec.defaults or []) - required_args = argspec.args[:len(argspec.args) - num_defaults] - - def isbound(method): - return getattr(method, '__self__', None) is not None - - if isbound(fn): - required_args.pop(0) - - missing = [arg for arg in required_args if arg not in kwargs] - missing = missing[len(args):] - if missing: - raise MissingArgs(missing) - - -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 unauthenticated(func): - """Adds 'unauthenticated' attribute to decorated function. - - Usage: - - >>> @unauthenticated - ... def mymethod(f): - ... pass - """ - func.unauthenticated = True - return func - - -def isunauthenticated(func): - """Checks if the function does not require authentication. - - Mark such functions with the `@unauthenticated` decorator. - - :returns: bool - """ - return getattr(func, 'unauthenticated', False) - - -def print_list(objs, fields, formatters=None, sortby_index=0, - mixed_case_fields=None, field_labels=None): - """Print a list or objects as a table, one row per object. - - :param objs: iterable of :class:`Resource` - :param fields: attributes that correspond to columns, in order - :param formatters: `dict` of callables for field formatting - :param sortby_index: index of the field for sorting table rows - :param mixed_case_fields: fields corresponding to object attributes that - have mixed case names (e.g., 'serverId') - :param field_labels: Labels to use in the heading of the table, default to - fields. - """ - formatters = formatters or {} - mixed_case_fields = mixed_case_fields or [] - field_labels = field_labels or fields - if len(field_labels) != len(fields): - raise ValueError(_("Field labels list %(labels)s has different number " - "of elements than fields list %(fields)s"), - {'labels': field_labels, 'fields': fields}) - - if sortby_index is None: - kwargs = {} - else: - kwargs = {'sortby': field_labels[sortby_index]} - pt = prettytable.PrettyTable(field_labels) - pt.align = 'l' - - for o in objs: - row = [] - for field in fields: - if field in formatters: - row.append(formatters[field](o)) - else: - if field in mixed_case_fields: - field_name = field.replace(' ', '_') - else: - field_name = field.lower().replace(' ', '_') - data = getattr(o, field_name, '') - row.append(data) - pt.add_row(row) - - if six.PY3: - print(encodeutils.safe_encode(pt.get_string(**kwargs)).decode()) - else: - print(encodeutils.safe_encode(pt.get_string(**kwargs))) - - -def print_dict(dct, dict_property="Property", wrap=0): - """Print a `dict` as a table of two columns. - - :param dct: `dict` to print - :param dict_property: name of the first column - :param wrap: wrapping for the second column - """ - pt = prettytable.PrettyTable([dict_property, 'Value']) - pt.align = 'l' - for k, v in six.iteritems(dct): - # convert dict to str to check length - if isinstance(v, dict): - v = six.text_type(v) - if wrap > 0: - v = textwrap.fill(six.text_type(v), wrap) - # if value has a newline, add in multiple rows - # e.g. fault with stacktrace - if v and isinstance(v, six.string_types) and r'\n' in v: - lines = v.strip().split(r'\n') - col1 = k - for line in lines: - pt.add_row([col1, line]) - col1 = '' - else: - pt.add_row([k, v]) - - if six.PY3: - print(encodeutils.safe_encode(pt.get_string()).decode()) - else: - print(encodeutils.safe_encode(pt.get_string())) - - -def get_password(max_password_prompts=3): - """Read password from TTY.""" - verify = strutils.bool_from_string(env("OS_VERIFY_PASSWORD")) - pw = None - if hasattr(sys.stdin, "isatty") and sys.stdin.isatty(): - # Check for Ctrl-D - try: - for __ in moves.range(max_password_prompts): - pw1 = getpass.getpass("OS Password: ") - if verify: - pw2 = getpass.getpass("Please verify: ") - else: - pw2 = pw1 - if pw1 == pw2 and pw1: - pw = pw1 - break - except EOFError: - pass - return pw - - -def service_type(stype): - """Adds 'service_type' attribute to decorated function. - - Usage: - - .. code-block:: python - - @service_type('volume') - def mymethod(f): - ... - """ - def inner(f): - f.service_type = stype - return f - return inner - - -def get_service_type(f): - """Retrieves service type from function.""" - return getattr(f, 'service_type', None) - - -def pretty_choice_list(l): - return ', '.join("'%s'" % i for i in l) - - -def exit(msg=''): - if msg: - print (msg, file=sys.stderr) - sys.exit(1) diff --git a/tuskarclient/openstack/common/uuidutils.py b/tuskarclient/openstack/common/uuidutils.py deleted file mode 100644 index 69a78b9..0000000 --- a/tuskarclient/openstack/common/uuidutils.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright (c) 2012 Intel Corporation. -# 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. - -""" -UUID related utilities and helper functions. -""" - -import uuid - - -def generate_uuid(): - return str(uuid.uuid4()) - - -def is_uuid_like(val): - """Returns validation of a value as a UUID. - - For our purposes, a UUID is a canonical form string: - aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa - - """ - try: - return str(uuid.UUID(val)).lower() == val.lower() - except (TypeError, ValueError, AttributeError): - return False diff --git a/tuskarclient/osc/__init__.py b/tuskarclient/osc/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tuskarclient/osc/plugin.py b/tuskarclient/osc/plugin.py deleted file mode 100644 index a7f90dd..0000000 --- a/tuskarclient/osc/plugin.py +++ /dev/null @@ -1,61 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import logging - -from openstackclient.common import utils - -from tuskarclient import client as tuskar_client - -LOG = logging.getLogger(__name__) - -DEFAULT_MANAGEMENT_API_VERSION = '2' -API_VERSION_OPTION = 'os_management_api_version' -API_NAME = 'management' -API_VERSIONS = { - '2': 'tuskarclient.v2.client.Client', -} - - -def make_client(instance): - """Returns a management service client.""" - - endpoint = instance.get_endpoint_for_service_type( - API_NAME, - region_name=instance._region_name, - ) - - token = instance.auth.get_token(instance.session) - - client = tuskar_client.get_client( - instance._api_version[API_NAME], - tuskar_url=endpoint, - os_auth_token=token, - username=instance._username, - password=instance._password, - ) - - return client - - -def build_option_parser(parser): - """Hook to add global options.""" - parser.add_argument( - '--os-management-api-version', - metavar='', - default=utils.env( - 'OS_MANAGEMENT_API_VERSION', - default=DEFAULT_MANAGEMENT_API_VERSION), - help='Management API version, default=' + - DEFAULT_MANAGEMENT_API_VERSION + - ' (Env: OS_MANAGEMENT_API_VERSION)') - return parser diff --git a/tuskarclient/osc/v2/__init__.py b/tuskarclient/osc/v2/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tuskarclient/osc/v2/plan.py b/tuskarclient/osc/v2/plan.py deleted file mode 100644 index 76046eb..0000000 --- a/tuskarclient/osc/v2/plan.py +++ /dev/null @@ -1,344 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from __future__ import print_function - -import logging -import os -import shutil -import sys - -from cliff import command -from cliff import lister -from cliff import show - -from tuskarclient.common import formatting -from tuskarclient.common import utils -from tuskarclient.openstack.common.apiclient import exceptions as exc - - -class CreateManagementPlan(show.ShowOne): - """Create a Management Plan.""" - - log = logging.getLogger(__name__ + '.CreateManagementPlan') - - def get_parser(self, prog_name): - parser = super(CreateManagementPlan, self).get_parser(prog_name) - - parser.add_argument( - 'name', - help="Name of the plan being created." - ) - - parser.add_argument( - '-d', '--description', - help='A textual description of the plan.') - - return parser - - def take_action(self, parsed_args): - self.log.debug("take_action(%s)" % parsed_args) - - client = self.app.client_manager.management - name = parsed_args.name - - try: - plan = client.plans.create( - name=name, - description=parsed_args.description - ) - except exc.Conflict: - raise exc.CommandError( - 'Plan with name "%s" already exists.' % name) - - return self.dict2columns(plan.to_dict()) - - -class DeleteManagementPlan(command.Command): - """Delete a Management Plan.""" - - log = logging.getLogger(__name__ + '.DeleteManagementPlan') - - def get_parser(self, prog_name): - parser = super(DeleteManagementPlan, self).get_parser(prog_name) - - parser.add_argument( - 'plan_uuid', - help="The UUID of the plan being deleted." - ) - - return parser - - def take_action(self, parsed_args): - self.log.debug("take_action(%s)" % parsed_args) - - client = self.app.client_manager.management - - client.plans.delete(parsed_args.plan_uuid) - - -class ListManagementPlans(lister.Lister): - """List the Management Plans.""" - - log = logging.getLogger(__name__ + '.ListManagementPlans') - - def get_parser(self, prog_name): - parser = super(ListManagementPlans, self).get_parser(prog_name) - return parser - - def take_action(self, parsed_args): - self.log.debug("take_action(%s)" % parsed_args) - - client = self.app.client_manager.management - - plans = client.plans.list() - - return ( - ('uuid', 'name', 'description', 'roles'), - ((p.uuid, p.name, p.description, - ', '.join(r.name for r in p.roles)) - for p in plans) - ) - - -class SetManagementPlan(show.ShowOne): - """Update a Management Plans properties.""" - - log = logging.getLogger(__name__ + '.SetManagementPlan') - - def get_parser(self, prog_name): - parser = super(SetManagementPlan, self).get_parser(prog_name) - - parser.add_argument( - 'plan_uuid', - help="The UUID of the plan being updated." - ) - - parser.add_argument( - '-P', '--parameter', dest='parameters', metavar='', - help=('Set a parameter in the Plan. This can be specified ' - 'multiple times.'), - action='append' - ) - - parser.add_argument( - '-F', '--flavor', dest='flavors', metavar='', - help=('Set the flavor for a role in the Plan. This can be ' - 'specified multiple times.'), - action='append' - ) - - parser.add_argument( - '-S', '--scale', dest='scales', metavar='', - help=('Set the Scale count for a role in the Plan. This can be ' - 'specified multiple times.'), - action='append' - ) - - return parser - - def take_action(self, parsed_args): - self.log.debug("take_action(%s)" % parsed_args) - - client = self.app.client_manager.management - - plan = client.plans.get(parsed_args.plan_uuid) - roles = plan.roles - - patch = [] - - patch.extend(utils.parameters_args_to_patch(parsed_args.parameters)) - patch.extend(utils.args_to_patch(parsed_args.flavors, roles, "Flavor")) - patch.extend(utils.args_to_patch(parsed_args.scales, roles, "count")) - - if len(patch) > 0: - plan = client.plans.patch(parsed_args.plan_uuid, patch) - else: - print(("WARNING: No valid arguments passed. No update operation " - "has been performed."), file=sys.stderr) - - return self.dict2columns(plan.to_dict()) - - -class ShowManagementPlan(show.ShowOne): - """Show a Management Plan.""" - - log = logging.getLogger(__name__ + '.ShowManagementPlan') - - def get_parser(self, prog_name): - parser = super(ShowManagementPlan, self).get_parser(prog_name) - - parser.add_argument( - 'plan_uuid', - help="The UUID of the plan to show." - ) - - parser.add_argument( - '--long', default=False, action="store_true", - help="Display full plan details" - ) - - return parser - - def take_action(self, parsed_args): - self.log.debug("take_action(%s)" % parsed_args) - - client = self.app.client_manager.management - plan = client.plans.get(parsed_args.plan_uuid) - plan_dict = plan.to_dict() - - if not parsed_args.long: - if 'parameters' in plan_dict: - plan_dict['parameters'] = ("Parameter output suppressed. Use " - "--long to display them.") - plan_dict['roles'] = ', '.join([r.name for r in plan.roles]) - - return self.dict2columns(plan_dict) - - -class AddManagementPlanRole(show.ShowOne): - """Add a Role to a Management Plan.""" - - log = logging.getLogger(__name__ + '.AddManagementPlanRole') - - def get_parser(self, prog_name): - parser = super(AddManagementPlanRole, self).get_parser(prog_name) - - parser.add_argument( - 'plan_uuid', - help="The UUID of the plan." - ) - - parser.add_argument( - 'role_uuid', - help="The UUID of the Role being added to the Plan." - ) - - return parser - - def take_action(self, parsed_args): - self.log.debug("take_action(%s)" % parsed_args) - - client = self.app.client_manager.management - - plan = client.plans.add_role( - parsed_args.plan_uuid, - parsed_args.role_uuid - ) - - return self.dict2columns(filtered_plan_dict(plan.to_dict())) - - -class RemoveManagementPlanRole(show.ShowOne): - """Remove a Role from a Management Plan.""" - - log = logging.getLogger(__name__ + '.RemoveManagementPlanRole') - - def get_parser(self, prog_name): - parser = super(RemoveManagementPlanRole, self).get_parser(prog_name) - - parser.add_argument( - 'plan_uuid', - help="The UUID of the plan." - ) - - parser.add_argument( - 'role_uuid', - help="The UUID of the Role being removed from the Plan." - ) - - return parser - - def take_action(self, parsed_args): - self.log.debug("take_action(%s)" % parsed_args) - - client = self.app.client_manager.management - - plan = client.plans.remove_role( - parsed_args.plan_uuid, - parsed_args.role_uuid - ) - - return self.dict2columns(filtered_plan_dict(plan.to_dict())) - - -class DownloadManagementPlan(command.Command): - """Download a Management Plan.""" - - log = logging.getLogger(__name__ + '.DownloadManagementPlan') - - def get_parser(self, prog_name): - parser = super(DownloadManagementPlan, self).get_parser(prog_name) - - parser.add_argument( - 'plan_uuid', - help="The UUID of the plan to download." - ) - - parser.add_argument( - '-O', '--output-dir', metavar='', - required=True, - help=('Directory to write template files into. It will be created ' - 'if it does not exist and any existing files in the ' - 'directory will be removed.') - ) - - return parser - - def take_action(self, parsed_args): - self.log.debug("take_action(%s)" % parsed_args) - - client = self.app.client_manager.management - - output_dir = parsed_args.output_dir - - if os.path.isdir(output_dir): - shutil.rmtree(output_dir) - - os.mkdir(output_dir) - - # retrieve templates - templates = client.plans.templates(parsed_args.plan_uuid) - - # write file for each key-value in templates - print("The following templates will be written:") - for template_name, template_content in templates.items(): - - # It's possible to organize the role templates and their dependent - # files into directories, in which case the template_name will - # carry the directory information. If that's the case, first - # create the directory structure (if it hasn't already been - # created by another file in the templates list). - template_dir = os.path.dirname(template_name) - output_template_dir = os.path.join(output_dir, template_dir) - if template_dir and not os.path.exists(output_template_dir): - os.makedirs(output_template_dir) - - filename = os.path.join(output_dir, template_name) - with open(filename, 'w+') as template_file: - template_file.write(template_content) - print(filename) - - -def filtered_plan_dict(plan_dict): - if 'parameters' in plan_dict and 'roles' in plan_dict: - plan_dict['parameters'] = [param for param in - plan_dict['parameters'] - if param['name'].endswith('::count')] - - plan_dict['parameters'] = formatting.parameters_v2_formatter( - plan_dict['parameters']) - - plan_dict['roles'] = formatting.parameters_v2_formatter( - plan_dict['roles']) - - return plan_dict diff --git a/tuskarclient/osc/v2/role.py b/tuskarclient/osc/v2/role.py deleted file mode 100644 index 7f79a3c..0000000 --- a/tuskarclient/osc/v2/role.py +++ /dev/null @@ -1,38 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import logging - -from cliff import lister - - -class ListRoles(lister.Lister): - """List Roles.""" - - log = logging.getLogger(__name__ + '.ListRoles') - - def get_parser(self, prog_name): - parser = super(ListRoles, self).get_parser(prog_name) - return parser - - def take_action(self, parsed_args): - self.log.debug("take_action(%s)" % parsed_args) - - client = self.app.client_manager.management - - roles = client.roles.list() - - return ( - ('uuid', 'name', 'version', 'description'), - ((r.uuid, r.name, r.version, r.description.strip()) - for r in roles) - ) diff --git a/tuskarclient/shell.py b/tuskarclient/shell.py deleted file mode 100755 index 23d5181..0000000 --- a/tuskarclient/shell.py +++ /dev/null @@ -1,257 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Command-line interface to the Tuskar API. -""" - -from __future__ import print_function - -import argparse -import logging -import logging.handlers -import sys - -from keystoneclient import session as kssession -import six - -import tuskarclient -from tuskarclient import client -import tuskarclient.common.utils as utils -from tuskarclient.openstack.common.apiclient import exceptions as exc - -logger = logging.getLogger(__name__) - - -class TuskarShell(object): - - def __init__(self, raw_args, - argument_parser_class=argparse.ArgumentParser): - self.raw_args = raw_args - self.argument_parser_class = argument_parser_class - - self.partial_args = None - self.parser = None - self.subparsers = None - self._prepare_parsers() - - def _prepare_parsers(self): - nonversioned_parser = self._nonversioned_parser() - self.partial_args = ( - nonversioned_parser.parse_known_args(self.raw_args)[0]) - self.parser, self.subparsers = ( - self._parser(self.partial_args.tuskar_api_version)) - - def run(self): - '''Run the CLI. Parse arguments and do the respective action.''' - - # run self.do_help() if we have no raw_args at all or just -h/--help - if (not self.raw_args - or self.raw_args in (['-h'], ['--help'])): - self.do_help(self.partial_args) - return 0 - - args = self.parser.parse_args(self.raw_args) - - self._setup_logging(args.debug) - - # run self.do_help() if we have help subcommand or -h/--help option - if args.func == self.do_help or args.help: - self.do_help(args) - return 0 - - self._ensure_auth_info(args) - - tuskar_client = client.get_client( - self.partial_args.tuskar_api_version, **args.__dict__) - args.func(tuskar_client, args) - - def _ensure_auth_info(self, args): - '''Ensure that authentication information is provided. Two variants - of authentication are supported: - - provide username, password, tenant and auth url - - provide token and tuskar url (or auth url instead of tuskar url) - ''' - if not args.os_auth_token: - if not args.os_username: - raise exc.CommandError("You must provide username via either " - "--os-username or env[OS_USERNAME]") - - if not args.os_password: - raise exc.CommandError("You must provide password via either " - "--os-password or env[OS_PASSWORD]") - - if not args.os_tenant_id and not args.os_tenant_name: - raise exc.CommandError("You must provide tenant via either " - "--os-tenant-name or --os-tenant-id or " - "env[OS_TENANT_NAME] or " - "env[OS_TENANT_ID]") - - if not args.os_auth_url: - raise exc.CommandError("You must provide auth URL via either " - "--os-auth-url or env[OS_AUTH_URL]") - else: - if not args.tuskar_url and not args.os_auth_url: - raise exc.CommandError("You must provide either " - "--tuskar-url or --os-auth-url or " - "env[TUSKAR_URL] or env[OS_AUTH_URL]") - - def _parser(self, version): - '''Create a top level argument parser. - - :param version: version of Tuskar API (and corresponding CLI - commands) to use - :return: main parser and subparsers - :rtype: (Parser, Subparsers) - ''' - parser = self._nonversioned_parser() - subparsers = parser.add_subparsers(metavar='') - versioned_shell = utils.import_versioned_module(version, 'shell') - versioned_shell.enhance_parser(parser, subparsers) - utils.define_commands_from_module(subparsers, self) - return parser, subparsers - - def _nonversioned_parser(self): - '''Create a basic parser that doesn't contain version-specific - subcommands. This one is mainly useful for parsing which API - version should be used for the versioned full blown parser and - defining common version-agnostic options. - ''' - parser = self.argument_parser_class( - prog='tuskar', - description='OpenStack Management CLI', - add_help=False, - ) - - parser.add_argument('-h', '--help', - action='store_true', - help="Print this help message and exit.", - ) - - parser.add_argument('--version', - action='version', - version=tuskarclient.__version__, - help="Shows the client version and exits.") - - parser.add_argument('-d', '--debug', - default=bool(utils.env('TUSKARCLIENT_DEBUG')), - action='store_true', - help='Defaults to env[TUSKARCLIENT_DEBUG].') - - parser.add_argument('--os-username', - default=utils.env('OS_USERNAME'), - help='Defaults to env[OS_USERNAME]', - ) - - parser.add_argument('--os_username', - help=argparse.SUPPRESS, - ) - - parser.add_argument('--os-password', - default=utils.env('OS_PASSWORD'), - help='Defaults to env[OS_PASSWORD]', - ) - - parser.add_argument('--os_password', - help=argparse.SUPPRESS, - ) - - parser.add_argument('--os-tenant-id', - default=utils.env('OS_TENANT_ID'), - help='Defaults to env[OS_TENANT_ID]', - ) - - parser.add_argument('--os_tenant_id', - help=argparse.SUPPRESS, - ) - - parser.add_argument('--os-tenant-name', - default=utils.env('OS_TENANT_NAME'), - help='Defaults to env[OS_TENANT_NAME]', - ) - - parser.add_argument('--os_tenant_name', - help=argparse.SUPPRESS, - ) - - parser.add_argument('--os-auth-url', - default=utils.env('OS_AUTH_URL'), - help='Defaults to env[OS_AUTH_URL]', - ) - - parser.add_argument('--os_auth_url', - help=argparse.SUPPRESS, - ) - - parser.add_argument('--os-auth-token', - default=utils.env('OS_AUTH_TOKEN', - default=None), - help='Defaults to env[OS_AUTH_TOKEN]') - - parser.add_argument('--os_auth_token', - help=argparse.SUPPRESS) - - parser.add_argument('--tuskar-url', - default=utils.env('TUSKAR_URL'), - help='Defaults to env[TUSKAR_URL]') - - parser.add_argument('--tuskar_url', - help=argparse.SUPPRESS) - - parser.add_argument('--tuskar-api-version', - default=utils.env('TUSKAR_API_VERSION', - default='2'), - help='Defaults to env[TUSKAR_API_VERSION] ' - 'or 2') - - parser.add_argument('--tuskar_api_version', - help=argparse.SUPPRESS) - - kssession.Session.register_cli_options(parser) - - return parser - - @utils.arg( - 'command', metavar='', nargs='?', - help='Display help for ') - def do_help(self, args): - """Display help about this program or one of its subcommands.""" - if getattr(args, 'command', None): - if args.command in self.subparsers.choices: - # print help for subcommand - self.subparsers.choices[args.command].print_help() - else: - raise exc.CommandError("'%s' is not a valid subcommand" % - args.command) - else: - # print general help - self.parser.print_help() - - def _setup_logging(self, debug): - log_lvl = logging.DEBUG if debug else logging.WARNING - logging.basicConfig( - format="%(levelname)s (%(module)s) %(message)s", - level=log_lvl) - - -def main(): - try: - TuskarShell(sys.argv[1:]).run() - except exc.CommandError as e: - print(six.text_type(e), file=sys.stderr) - except Exception as e: - logger.exception("Exiting due to an error:") - sys.exit(1) - - -if __name__ == '__main__': - main() diff --git a/tuskarclient/tests/__init__.py b/tuskarclient/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tuskarclient/tests/common/__init__.py b/tuskarclient/tests/common/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tuskarclient/tests/common/test_auth.py b/tuskarclient/tests/common/test_auth.py deleted file mode 100644 index d47af38..0000000 --- a/tuskarclient/tests/common/test_auth.py +++ /dev/null @@ -1,123 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import mock - -from tuskarclient.common import auth -from tuskarclient.openstack.common.apiclient import client -from tuskarclient.openstack.common.apiclient import exceptions as exc -from tuskarclient.tests import utils as test_utils - - -@mock.patch.object(auth.ksclient, 'Client') -class KeystoneAuthPluginTest(test_utils.TestCase): - def setUp(self): - super(KeystoneAuthPluginTest, self).setUp() - plugin = auth.KeystoneAuthPlugin( - username="fake-username", - password="fake-password", - tenant_id="fake-tenant-id", - tenant_name="fake-tenant-name", - auth_url="http://auth", - endpoint="http://tuskar") - self.cs = client.HTTPClient(auth_plugin=plugin) - - def test_authenticate(self, mock_ksclient): - self.cs.authenticate() - mock_ksclient.assert_called_with( - username="fake-username", - password="fake-password", - tenant_id="fake-tenant-id", - tenant_name="fake-tenant-name", - auth_url="http://auth", - cacert=None, - cert=None, - key=None) - - def test_authenticate_with_ssl(self, mock_ksclient): - plugin = auth.KeystoneAuthPlugin( - username="fake-username", - password="fake-password", - tenant_id="fake-tenant-id", - tenant_name="fake-tenant-name", - auth_url="http://auth", - endpoint="http://tuskar", - cacert="/fake/cacert.pem", - cert="/fake/cert.pem", - key="/fake/key.pem") - self.cs = client.HTTPClient(auth_plugin=plugin) - self.cs.authenticate() - mock_ksclient.assert_called_with( - username="fake-username", - password="fake-password", - tenant_id="fake-tenant-id", - tenant_name="fake-tenant-name", - auth_url="http://auth", - cacert="/fake/cacert.pem", - cert="/fake/cert.pem", - key="/fake/key.pem") - - def test_token_and_endpoint(self, mock_ksclient): - self.cs.authenticate() - (token, endpoint) = self.cs.auth_plugin.token_and_endpoint( - "fake-endpoint-type", "fake-service-type") - self.assertIsInstance(token, mock.MagicMock) - self.assertEqual("http://tuskar", endpoint) - - def test_token_and_endpoint_before_auth(self, mock_ksclient): - (token, endpoint) = self.cs.auth_plugin.token_and_endpoint( - "fake-endpoint-type", "fake-service-type") - self.assertIsNone(token, None) - self.assertIsNone(endpoint, None) - - -@mock.patch.object(auth.ksclient, 'Client') -class KeystoneAuthPluginTokenTest(test_utils.TestCase): - def test_token_and_endpoint(self, mock_ksclient): - plugin = auth.KeystoneAuthPlugin( - token="fake-token", - endpoint="http://tuskar") - cs = client.HTTPClient(auth_plugin=plugin) - - cs.authenticate() - (token, endpoint) = cs.auth_plugin.token_and_endpoint( - "fake-endpoint-type", "fake-service-type") - self.assertEqual('fake-token', token) - self.assertEqual('http://tuskar', endpoint) - - -class KeystoneAuthPluginOptionsTest(test_utils.TestCase): - def setUp(self): - super(KeystoneAuthPluginOptionsTest, self).setUp() - self.kwargs = { - 'username': "fake-username", - 'password': "fake-password", - 'tenant_id': "fake-tenant-id", - 'tenant_name': "fake-tenant-name", - 'auth_url': "http://auth", - 'endpoint': "http://tuskar" - } - - def test_it_raises_error_without_tenant_id_and_name(self): - kwargs = self.kwargs.copy() - del kwargs['tenant_id'] - del kwargs['tenant_name'] - auth_plugin = auth.KeystoneAuthPlugin(**kwargs) - self.assertRaises(exc.AuthPluginOptionsMissing, - auth_plugin.sufficient_options) - - def test_it_raises_error_withtout_username(self): - kwargs = self.kwargs.copy() - del kwargs['username'] - auth_plugin = auth.KeystoneAuthPlugin(**kwargs) - self.assertRaises(exc.AuthPluginOptionsMissing, - auth_plugin.sufficient_options) diff --git a/tuskarclient/tests/common/test_formatting.py b/tuskarclient/tests/common/test_formatting.py deleted file mode 100644 index 1739441..0000000 --- a/tuskarclient/tests/common/test_formatting.py +++ /dev/null @@ -1,99 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import mock -import six - -import tuskarclient.common.formatting as fmt -import tuskarclient.tests.utils as tutils -from tuskarclient.v2 import plans - - -class PrintTest(tutils.TestCase): - - def setUp(self): - super(PrintTest, self).setUp() - self.outfile = six.StringIO() - - def test_print_dict(self): - dict_ = {'k': 'v', 'key': 'value'} - formatters = {'key': lambda v: 'custom ' + v} - custom_labels = {'k': 'custom_key'} - - fmt.print_dict(dict_, formatters, custom_labels, - outfile=self.outfile) - self.assertEqual( - ('+------------+--------------+\n' - '| Property | Value |\n' - '+------------+--------------+\n' - '| custom_key | v |\n' - '| key | custom value |\n' - '+------------+--------------+\n'), - self.outfile.getvalue() - ) - - def test_print_list(self): - fields = ['thing', 'color', '!artistic_name'] - formatters = { - '!artistic_name': lambda obj: '{0} {1}'.format(obj.color, - obj.thing), - 'color': lambda c: c.split(' ')[1], - } - custom_labels = {'thing': 'name', '!artistic_name': 'artistic name'} - - fmt.print_list(self.objects(), fields, formatters, custom_labels, - outfile=self.outfile) - self.assertEqual( - ('+------+-------+-----------------+\n' - '| name | color | artistic name |\n' - '+------+-------+-----------------+\n' - '| moon | green | dark green moon |\n' - '| sun | blue | bright blue sun |\n' - '+------+-------+-----------------+\n'), - self.outfile.getvalue() - ) - - def test_print_list_custom_field_without_formatter(self): - fields = ['!artistic_name'] - - self.assertRaises(KeyError, fmt.print_list, self.objects(), fields) - - def objects(self): - return [ - mock.Mock(thing='sun', color='bright blue'), - mock.Mock(thing='moon', color='dark green'), - ] - - -class FormattersTest(tutils.TestCase): - - def test_attributes_formatter(self): - """Test the attributes formatter displays the attributes correctly.""" - - attributes = { - 'password': 'pass', - 'mysql_host': 'http://somewhere', - 'a thing': 'a value' - } - self.assertEqual( - ("a thing=a value\nmysql_host=http://somewhere\npassword=pass\n"), - fmt.attributes_formatter(attributes), - ) - - def test_list_plan_roles_formatter(self): - roles = plans.Plan(None, - {'roles': [{'name': 'foo_role'}, - {'name': 'bar_role'}]}).roles - self.assertEqual( - "foo_role, bar_role", - fmt.list_plan_roles_formatter(roles) - ) diff --git a/tuskarclient/tests/common/test_utils.py b/tuskarclient/tests/common/test_utils.py deleted file mode 100644 index a0044c2..0000000 --- a/tuskarclient/tests/common/test_utils.py +++ /dev/null @@ -1,154 +0,0 @@ -# Copyright 2013 OpenStack LLC. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - - -import mock - -from tuskarclient.common import utils -from tuskarclient.openstack.common.apiclient import exceptions as exc -from tuskarclient.tests import utils as test_utils - - -class DefineCommandsTest(test_utils.TestCase): - - def test_define_commands_from_module(self): - subparsers = mock.Mock() - subparser = mock.MagicMock() - subparsers.add_parser.return_value = subparser - dummy_module = self.dummy_command_module() - - utils.define_commands_from_module(subparsers, dummy_module) - subparsers.add_parser.assert_called_with( - 'dummy-list', help="Docstring.", description="Docstring.") - subparser.add_argument.assert_called_with( - '-a', metavar='', help="Add a number.") - subparser.set_defaults.assert_called_with( - func=dummy_module.do_dummy_list) - - def dummy_command_module(self): - @utils.arg('-a', metavar='', help="Add a number.") - def do_dummy_list(): - '''Docstring.''' - return 42 - - dummy = mock.Mock() - dummy.do_dummy_list = do_dummy_list - dummy.other_method = mock.Mock('other_method', return_value=43) - return dummy - - -class FindResourceTest(test_utils.TestCase): - - def setUp(self): - super(FindResourceTest, self).setUp() - - self.overcloud1 = mock.Mock() - self.overcloud1.id = '1' - self.overcloud1.name = 'My Overcloud 1' - - self.overcloud2 = mock.Mock() - self.overcloud2.id = '2' - self.overcloud2.name = 'My Overcloud 2' - - self.overcloud3 = mock.Mock() - self.overcloud3.id = '3' - self.overcloud3.name = 'My Overcloud 2' - - self.manager = mock.Mock() - self.manager.resource_class = None - self.manager.get.return_value = self.overcloud1 - self.manager.resource_class = mock.Mock(__name__='Overcloud', - NAME_ATTR='name') - self.manager.list.return_value = [ - self.overcloud1, - self.overcloud2, - self.overcloud3] - - def test_with_id(self): - overcloud = utils.find_resource(self.manager, '1') - self.manager.get.assert_called_with(1) - self.assertEqual(self.overcloud1, overcloud) - - def test_with_name(self): - overcloud = utils.find_resource(self.manager, 'My Overcloud 1') - self.assertEqual(self.overcloud1, overcloud) - - def test_no_match(self): - self.assertRaises(exc.CommandError, - utils.find_resource, - self.manager, - 'My Overcloud 3') - - def test_multiple_matches(self): - self.assertRaises(exc.CommandError, - utils.find_resource, - self.manager, - 'My Overcloud 2') - - -class ParseCLIArgsTest(test_utils.TestCase): - - def setUp(self): - super(ParseCLIArgsTest, self).setUp() - - self.mock_role1 = mock.Mock() - self.mock_role1.configure_mock(name="role1", version=1) - - self.mock_role2 = mock.Mock() - self.mock_role2.configure_mock(name="role2", version=2) - - self.roles = [self.mock_role1, self.mock_role2] - - def test_parameters_args_to_patch(self): - - args = [ - "parameter1=value1", - "parameter2=value2", - ] - - result = utils.parameters_args_to_patch(args) - - self.assertEqual([ - {'name': 'parameter1', 'value': 'value1'}, - {'name': 'parameter2', 'value': 'value2'}, - ], result) - - def test_flavors_args_to_patch(self): - - args = [ - "role1-1=flavor1", - "role2-2=flavor2", - ] - - result = utils.args_to_patch(args, self.roles, "Flavor") - - self.assertEqual([ - {'name': 'role1-1::Flavor', 'value': 'flavor1'}, - {'name': 'role2-2::Flavor', 'value': 'flavor2'} - ], result) - - def test_scale_args_to_patch(self): - - args = [ - "role1-1=1", - "role2-2=2", - ] - - result = utils.args_to_patch(args, self.roles, "count") - - self.assertEqual([ - {'name': 'role1-1::count', 'value': '1'}, - {'name': 'role2-2::count', 'value': '2'} - ], result) diff --git a/tuskarclient/tests/integration/__init__.py b/tuskarclient/tests/integration/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tuskarclient/tests/integration/test_help_command.py b/tuskarclient/tests/integration/test_help_command.py deleted file mode 100644 index 3502717..0000000 --- a/tuskarclient/tests/integration/test_help_command.py +++ /dev/null @@ -1,101 +0,0 @@ -# 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 tuskarclient.tests.utils as tutils - - -class HelpCommandTest(tutils.CommandTestCase): - pass - -tests = [ - # help - { - 'commands': ['help'], # commands to test "tuskar help" - # helps find failed tests in code - needs "test_" prefix - 'test_identifiers': ['test_help'], - 'out_includes': [ # what should be in output - 'usage:', - 'positional arguments:', - 'optional arguments:', - ], - 'out_excludes': [ # what should not be in output - 'foo bar baz', - ], - 'err_string': '', # how error output should look like - 'return_code': 0, - }, - { - 'commands': ['help -h', 'help --help', 'help help'], - 'test_identifiers': ['test_help_dash_h', - 'test_help_dashdash_help', - 'test_help_help'], - 'out_includes': [ - 'usage:', - 'positional arguments:', - 'optional arguments:', - 'Display help for ', - ], - 'out_excludes': [ - 'flavor-list', - '--os-username OS_USERNAME', - ], - 'err_string': '', - 'return_code': 0, - }, - -] - - -def create_test_method(command, expected_values): - def test_command_method(self): - self.assertThat( - self.run_tuskar("--tuskar-api-version 2 %s" % command), - tutils.CommandOutputMatches( - out_str=expected_values.get('out_string'), - out_inc=expected_values.get('out_includes'), - out_exc=expected_values.get('out_excludes'), - err_str=expected_values.get('err_string'), - err_inc=expected_values.get('err_includes'), - err_exc=expected_values.get('err_excludes'), - )) - return test_command_method - -# Create a method for each command found in the above tests. The tests will be -# constructed on the HelpCommandTest class with the name given in the test -# identifiers. This way the developer can search the above structure for the -# identifier and find the actual data used in the test. -duplicated = [] - -for test in tests: - commands = test.get('commands') - for index, command in enumerate(commands): - test_command_method = create_test_method(command, test) - test_command_method.__name__ = test.get('test_identifiers')[index] - - if hasattr(HelpCommandTest, test_command_method.__name__): - duplicated.append(test_command_method.__name__) - - setattr(HelpCommandTest, - test_command_method.__name__, - test_command_method) - - -# Finally add a meta test to verify that no test identifiers were used twice -# which would result in only the last test being added. -def _meta_verify_test_builder(self): - self.assertEqual( - duplicated, [], "Expected no test identifiers to be " - "duplicated but found {0}".format(len(duplicated)) - ) - -setattr(HelpCommandTest, 'test_test_builder_for_duplicates', - _meta_verify_test_builder) diff --git a/tuskarclient/tests/osc/__init__.py b/tuskarclient/tests/osc/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tuskarclient/tests/osc/test_plugin.py b/tuskarclient/tests/osc/test_plugin.py deleted file mode 100644 index 04b969b..0000000 --- a/tuskarclient/tests/osc/test_plugin.py +++ /dev/null @@ -1,42 +0,0 @@ -# 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 unittest - -import mock - -from tuskarclient.osc import plugin - - -class TestManagementPlugin(unittest.TestCase): - - @mock.patch("tuskarclient.client.get_client") - def test_make_client(self, mock_get_client): - - mock_instance = mock.Mock() - mock_instance._api_version = {'management': 2} - mock_instance.get_endpoint_for_service_type.return_value = "ENDPOINT" - mock_instance.auth.get_token.return_value = "TOKEN" - - plugin.make_client(mock_instance) - - mock_instance.get_endpoint_for_service_type.assert_called_with( - 'management', region_name=mock_instance._region_name) - mock_instance.auth.get_token.assert_called_with(mock_instance.session) - - mock_get_client.assert_called_with( - 2, - username=mock_instance._username, - password=mock_instance._password, - tuskar_url="ENDPOINT", - os_auth_token="TOKEN" - ) diff --git a/tuskarclient/tests/osc/v2/__init__.py b/tuskarclient/tests/osc/v2/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tuskarclient/tests/osc/v2/fakes.py b/tuskarclient/tests/osc/v2/fakes.py deleted file mode 100644 index 47bfa4d..0000000 --- a/tuskarclient/tests/osc/v2/fakes.py +++ /dev/null @@ -1,44 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import mock -from openstackclient.tests import utils - - -def _create_mock(**kwargs): - mock_plan = mock.Mock() - mock_plan.configure_mock(**kwargs) - mock_plan.to_dict.return_value = kwargs - return mock_plan - - -mock_roles = [ - _create_mock(uuid="UUID1", name="Role 1 Name", version=1, - description="Mock Role 1"), - _create_mock(uuid="UUID2", name="Role 2 Name", version=2, - description="Mock Role 2"), -] - -mock_plans = [ - _create_mock(uuid="UUID1", name="Plan 1 Name", description="Plan 1", - roles=mock_roles), - _create_mock(uuid="UUID2", name="Plan 2 Name", description="Plan 2", - roles=[]) -] - - -class TestManagement(utils.TestCommand): - - def setUp(self): - super(TestManagement, self).setUp() - - self.app.client_manager.management = mock.Mock() diff --git a/tuskarclient/tests/osc/v2/test_plans.py b/tuskarclient/tests/osc/v2/test_plans.py deleted file mode 100644 index 767bcec..0000000 --- a/tuskarclient/tests/osc/v2/test_plans.py +++ /dev/null @@ -1,340 +0,0 @@ -# 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 tempfile - -from tuskarclient.osc.v2 import plan -from tuskarclient.tests.osc.v2 import fakes - - -class TestPlans(fakes.TestManagement): - - def setUp(self): - super(TestPlans, self).setUp() - - self.management_mock = self.app.client_manager.management - self.management_mock.reset_mock() - - -class TestCreateManagementPlan(TestPlans): - - def setUp(self): - super(TestCreateManagementPlan, self).setUp() - self.cmd = plan.CreateManagementPlan(self.app, None) - - def test_create_plan(self): - arglist = ["Plan 2 Name", '-d', 'Plan 2'] - verifylist = [ - ('name', "Plan 2 Name"), - ('description', "Plan 2"), - ] - - self.management_mock.plans.create.return_value = fakes.mock_plans[1] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.assertEqual([ - ('description', 'name', 'roles', 'uuid'), - ('Plan 2', 'Plan 2 Name', [], 'UUID2') - ], list(result) - ) - - def test_create_plan_no_description(self): - arglist = ["Plan1Name", ] - verifylist = [ - ('name', "Plan1Name"), - ('description', None), - ] - - self.management_mock.plans.create.return_value = fakes.mock_plans[0] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.assertEqual([ - ('description', 'name', 'roles', 'uuid'), - ('Plan 1', 'Plan 1 Name', fakes.mock_roles, 'UUID1') - ], list(result)) - - -class TestDeleteManagementPlan(TestPlans): - - def setUp(self): - super(TestDeleteManagementPlan, self).setUp() - self.cmd = plan.DeleteManagementPlan(self.app, None) - - def test_delete_plan(self): - arglist = ['UUID1', ] - verifylist = [ - ('plan_uuid', "UUID1"), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.cmd.take_action(parsed_args) - - self.management_mock.plans.delete.assert_called_with('UUID1') - - -class TestListManagementPlan(TestPlans): - - def setUp(self): - super(TestListManagementPlan, self).setUp() - self.cmd = plan.ListManagementPlans(self.app, None) - - def test_list_plans(self): - arglist = [] - verifylist = [] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.management_mock.plans.list.return_value = fakes.mock_plans - - titles, rows = self.cmd.take_action(parsed_args) - - self.assertEqual(titles, ('uuid', 'name', 'description', 'roles')) - self.assertEqual([ - ('UUID1', 'Plan 1 Name', 'Plan 1', 'Role 1 Name, Role 2 Name'), - ('UUID2', 'Plan 2 Name', 'Plan 2', '') - ], list(rows)) - - -class TestSetManagementPlan(TestPlans): - - def setUp(self): - super(TestSetManagementPlan, self).setUp() - self.cmd = plan.SetManagementPlan(self.app, None) - - def test_update_plan_nothing(self): - arglist = ['UUID1', ] - verifylist = [ - ('plan_uuid', "UUID1"), - ('parameters', None), - ('flavors', None), - ('scales', None), - ] - - self.management_mock.plans.get.return_value = fakes.mock_plans[1] - self.management_mock.plans.patch.return_value = fakes.mock_plans[1] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.cmd.take_action(parsed_args) - - self.management_mock.plans.patch.assert_not_called() - - def test_update_plan_parameters(self): - arglist = ['UUID1', '-P', 'A=1', '-P', 'B=2'] - verifylist = [ - ('plan_uuid', "UUID1"), - ('parameters', ['A=1', 'B=2']), - ('flavors', None), - ('scales', None), - ] - - self.management_mock.plans.get.return_value = fakes.mock_plans[1] - self.management_mock.plans.patch.return_value = fakes.mock_plans[1] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.assertEqual([ - ('description', 'name', 'roles', 'uuid'), - ('Plan 2', 'Plan 2 Name', [], 'UUID2') - ], list(result)) - - self.management_mock.plans.patch.assert_called_with('UUID1', [ - {'value': '1', 'name': 'A'}, - {'value': '2', 'name': 'B'} - ]) - - def test_update_plan_flavors(self): - arglist = ['UUID1', '-F', 'Role 1 Name-1=strawberry', - '-F', 'Role 2 Name-2=cherry'] - verifylist = [ - ('plan_uuid', "UUID1"), - ('parameters', None), - ('flavors', ['Role 1 Name-1=strawberry', 'Role 2 Name-2=cherry']), - ('scales', None), - ] - - self.management_mock.plans.get.return_value = fakes.mock_plans[0] - self.management_mock.plans.patch.return_value = fakes.mock_plans[1] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.assertEqual([ - ('description', 'name', 'roles', 'uuid'), - ('Plan 2', 'Plan 2 Name', [], 'UUID2') - ], list(result)) - - self.management_mock.plans.patch.assert_called_with('UUID1', [ - {'value': 'strawberry', 'name': 'Role 1 Name-1::Flavor'}, - {'value': 'cherry', 'name': 'Role 2 Name-2::Flavor'} - ]) - - def test_update_plan_scale(self): - arglist = ['UUID1', '-S', 'Role 1 Name-1=2', '-S', 'Role 2 Name-2=3'] - verifylist = [ - ('plan_uuid', "UUID1"), - ('parameters', None), - ('flavors', None), - ('scales', ['Role 1 Name-1=2', 'Role 2 Name-2=3']), - ] - - self.management_mock.plans.get.return_value = fakes.mock_plans[0] - self.management_mock.plans.patch.return_value = fakes.mock_plans[1] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.assertEqual([ - ('description', 'name', 'roles', 'uuid'), - ('Plan 2', 'Plan 2 Name', [], 'UUID2') - ], list(result)) - - self.management_mock.plans.patch.assert_called_with('UUID1', [ - {'value': '2', 'name': 'Role 1 Name-1::count'}, - {'value': '3', 'name': 'Role 2 Name-2::count'} - ]) - - -class TestShowManagementPlan(TestPlans): - - def setUp(self): - super(TestShowManagementPlan, self).setUp() - self.cmd = plan.ShowManagementPlan(self.app, None) - - def test_show_plan(self): - arglist = ['UUID2', ] - verifylist = [ - ('long', False), - ] - - self.management_mock.plans.get.return_value = fakes.mock_plans[0] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.assertEqual([ - ('description', 'name', 'roles', 'uuid'), - ('Plan 1', 'Plan 1 Name', 'Role 1 Name, Role 2 Name', 'UUID1') - ], list(result)) - - def test_show_plan_verbose(self): - arglist = ['UUID1', '--long'] - verifylist = [ - ('long', True), - ] - - self.management_mock.plans.get.return_value = fakes.mock_plans[1] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.assertEqual([ - ('description', 'name', 'roles', 'uuid'), - ('Plan 2', 'Plan 2 Name', [], 'UUID2') - ], list(result)) - - -class TestAddManagementPlanRole(TestPlans): - - def setUp(self): - super(TestAddManagementPlanRole, self).setUp() - self.cmd = plan.AddManagementPlanRole(self.app, None) - - def test_add_plan_role(self): - arglist = ['UUID1', 'UUID2'] - verifylist = [ - ('plan_uuid', 'UUID1'), - ('role_uuid', 'UUID2'), - ] - - self.management_mock.plans.add_role.return_value = fakes.mock_plans[0] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.assertEqual([ - ('description', 'name', 'roles', 'uuid'), - ('Plan 1', 'Plan 1 Name', fakes.mock_roles, 'UUID1') - ], list(result)) - - -class TestRemoveManagementPlanRole(TestPlans): - - def setUp(self): - super(TestRemoveManagementPlanRole, self).setUp() - self.cmd = plan.RemoveManagementPlanRole(self.app, None) - - def test_remove_plan_role(self): - arglist = ['UUID1', 'UUID2'] - verifylist = [ - ('plan_uuid', 'UUID1'), - ('role_uuid', 'UUID2'), - ] - - self.management_mock.plans.remove_role.return_value = ( - fakes.mock_plans[0]) - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.assertEqual([ - ('description', 'name', 'roles', 'uuid'), - ('Plan 1', 'Plan 1 Name', fakes.mock_roles, 'UUID1') - ], list(result)) - - -class TestDownloadManagementPlan(TestPlans): - - def setUp(self): - super(TestDownloadManagementPlan, self).setUp() - self.cmd = plan.DownloadManagementPlan(self.app, None) - - def test_download_plan_templates(self): - - temp_dir = tempfile.mkdtemp() - - arglist = ['UUID1', '-O', temp_dir] - verifylist = [ - ('plan_uuid', 'UUID1'), - ('output_dir', temp_dir), - ] - - mock_result = { - 'template-1-name': 'template 1 content', - 'template-2-name': 'template 2 content', - } - - self.management_mock.plans.templates.return_value = mock_result - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.cmd.take_action(parsed_args) - - for template_name in mock_result: - full_path = os.path.join(temp_dir, template_name) - self.assertTrue(os.path.exists(full_path)) diff --git a/tuskarclient/tests/osc/v2/test_roles.py b/tuskarclient/tests/osc/v2/test_roles.py deleted file mode 100644 index 18a7262..0000000 --- a/tuskarclient/tests/osc/v2/test_roles.py +++ /dev/null @@ -1,46 +0,0 @@ -# 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 tuskarclient.osc.v2 import role -from tuskarclient.tests.osc.v2 import fakes - - -class TestRoles(fakes.TestManagement): - - def setUp(self): - super(TestRoles, self).setUp() - - self.management_mock = self.app.client_manager.management - self.management_mock.reset_mock() - - -class TestRoleList(TestRoles): - - def setUp(self): - super(TestRoleList, self).setUp() - self.cmd = role.ListRoles(self.app, None) - - def test_list_roles(self): - arglist = [] - verifylist = [] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.management_mock.roles.list.return_value = fakes.mock_roles - - titles, rows = self.cmd.take_action(parsed_args) - - self.assertEqual(titles, ('uuid', 'name', 'version', 'description')) - self.assertEqual([ - ('UUID1', 'Role 1 Name', 1, 'Mock Role 1'), - ('UUID2', 'Role 2 Name', 2, 'Mock Role 2') - ], list(rows)) diff --git a/tuskarclient/tests/test_client.py b/tuskarclient/tests/test_client.py deleted file mode 100644 index 403bd27..0000000 --- a/tuskarclient/tests/test_client.py +++ /dev/null @@ -1,90 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import mock - -from tuskarclient import client -from tuskarclient.common import auth -from tuskarclient.openstack.common.apiclient import client as apiclient -from tuskarclient.tests import utils as tutils - - -class ClientGetClientTest(tutils.TestCase): - def setUp(self): - super(ClientGetClientTest, self).setUp() - self.kwargs = { - 'os_username': 'os_username', - 'os_password': 'os_password', - 'os_tenant_name': 'os_tenant_name', - 'os_auth_token': 'os_auth_token', - 'os_auth_url': 'os_auth_url', - 'tuskar_url': 'tuskar_url', - 'os_cacert': 'os_cacert', - 'os_cert': 'os_cert', - 'os_key': 'os_key', - } - self.client_kwargs = { - 'username': 'os_username', - 'password': 'os_password', - 'tenant_name': 'os_tenant_name', - 'token': 'os_auth_token', - 'auth_url': 'os_auth_url', - 'endpoint': 'tuskar_url', - 'cacert': 'os_cacert', - 'cert': 'os_cert', - 'key': 'os_key', - } - self.api_version = 2 - - @mock.patch.object(client, 'Client') - def test_it_works(self, mocked_Client): - mocked_Client.return_value = 'client' - self.assertEqual(client.get_client(self.api_version, **self.kwargs), - 'client') - mocked_Client.assert_called_with(self.api_version, - **self.client_kwargs) - - @mock.patch.object(client, 'Client') - def test_it_raises_error_without_proper_params( - self, - mocked_Client): - - mocked_Client.return_value = None - kwargs = self.kwargs.copy() - del kwargs['os_password'] - self.assertRaises(ValueError, - client.get_client, self.api_version, **self.kwargs - ) - mocked_Client.assert_called_with(self.api_version, - **self.client_kwargs) - - -class ClientClientTest(tutils.TestCase): - def setUp(self): - super(ClientClientTest, self).setUp() - self.client_kwargs = { - 'username': 'os_username', - 'password': 'os_password', - 'tenant_name': 'os_tenant_name', - 'token': 'os_auth_token', - 'auth_url': 'os_auth_url', - 'endpoint': 'tuskar_url' - } - self.api_version = 2 - - @mock.patch.object(apiclient, 'HTTPClient') - @mock.patch.object(auth, 'KeystoneAuthPlugin') - def test_client_initialization(self, mocked_KeystoneAuthPlugin, - mocked_HTTPClient): - client.Client(self.api_version, **self.client_kwargs) - mocked_KeystoneAuthPlugin.assert_called_with(**self.client_kwargs) - mocked_HTTPClient.assert_called_with(mocked_KeystoneAuthPlugin()) diff --git a/tuskarclient/tests/test_shell.py b/tuskarclient/tests/test_shell.py deleted file mode 100644 index c48c6cc..0000000 --- a/tuskarclient/tests/test_shell.py +++ /dev/null @@ -1,71 +0,0 @@ -# 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 tuskarclient.openstack.common.apiclient import exceptions as exc -from tuskarclient import shell -import tuskarclient.tests.utils as tutils - - -class ShellTest(tutils.TestCase): - - args_attributes = [ - 'os_username', 'os_password', 'os_tenant_name', 'os_tenant_id', - 'os_auth_url', 'os_auth_token', 'tuskar_url', 'tuskar_api_version', - 'os_cacert', 'os_cert', 'os_key', - ] - - def setUp(self): - super(ShellTest, self).setUp() - self.s = shell.TuskarShell([]) - - def empty_args(self): - args = lambda: None # i'd use object(), but it can't have attributes - for attr in self.args_attributes: - setattr(args, attr, None) - - return args - - def test_ensure_auth_info_with_credentials(self): - ensure = self.s._ensure_auth_info - command_error = exc.CommandError - args = self.empty_args() - - args.os_username = 'user' - args.os_password = 'pass' - args.os_tenant_name = 'tenant' - self.assertRaises(command_error, ensure, args) - - args.os_auth_url = 'keystone' - ensure(args) # doesn't raise - - def test_ensure_auth_info_with_token(self): - ensure = self.s._ensure_auth_info - command_error = exc.CommandError - args = self.empty_args() - - args.os_auth_token = 'token' - self.assertRaises(command_error, ensure, args) - - args.tuskar_url = 'tuskar' - ensure(args) # doesn't raise - - def test_parser_v2(self): - v2_commands = [ - ] - parser, subparsers = self.s._parser(2) - tuskar_help = parser.format_help() - - for arg in map(lambda a: a.replace('_', '-'), self.args_attributes): - self.assertIn(arg, tuskar_help) - - for command in v2_commands: - self.assertIn(command, tuskar_help) diff --git a/tuskarclient/tests/utils.py b/tuskarclient/tests/utils.py deleted file mode 100644 index 8493824..0000000 --- a/tuskarclient/tests/utils.py +++ /dev/null @@ -1,327 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import argparse -from gettext import gettext as _ -import os -import sys - -import fixtures -from six import StringIO -import testtools - -from tuskarclient import shell - - -class TestCase(testtools.TestCase): - - def setUp(self): - super(TestCase, self).setUp() - if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or - os.environ.get('OS_STDOUT_CAPTURE') == '1'): - stdout = self.useFixture(fixtures.StringStream('stdout')).stream - self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout)) - if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or - os.environ.get('OS_STDERR_CAPTURE') == '1'): - stderr = self.useFixture(fixtures.StringStream('stderr')).stream - self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr)) - - -class HasManager(object): - - def __init__(self, cls_name, attr_name): - self.cls_name = cls_name - self.attr_name = attr_name - - def match(self, client): - if not hasattr(client, self.attr_name): - return ManagerClassMismatch(client, self.cls_name, self.attr_name) - - obj = getattr(client, self.attr_name) - if self.cls_name != obj.__class__.__name__: - return ManagerClassMismatch(client, self.cls_name, self.attr_name) - else: - return None - - -class ManagerClassMismatch(object): - - def __init__(self, client, cls_name, attr_name): - self.client = client - self.cls_name = cls_name - self.attr_name = attr_name - - def describe(self): - return "Class %r mismatch for attribute %r on %r" % ( - self.cls_name, self.attr_name, self.client) - - def get_details(self): - return {} - - -class IsMethodOn(object): - """Match if there is method with same name on object.""" - def __init__(self, obj): - self.obj = obj - - def __str__(self): - return 'IsMethodOn(%s)' % (self.obj) - - def match(self, method_name): - result = hasattr(self.obj, method_name) - if result: - return None - else: - return testtools.matchers.Mismatch("%s is not a method on %s" % - (method_name, self.obj)) - - -class CommandTestCase(TestCase): - def setUp(self): - super(CommandTestCase, self).setUp() - self.tuskar_bin = os.path.join( - os.path.dirname(os.path.realpath(sys.executable)), - 'tuskar') - - def run_tuskar(self, params=''): - args = params.split() - out = StringIO() - err = StringIO() - ArgumentParserForTests.OUT = out - ArgumentParserForTests.ERR = err - try: - shell.TuskarShell( - args, argument_parser_class=ArgumentParserForTests).run() - except TestExit: - pass - outvalue = out.getvalue() - errvalue = err.getvalue() - return [outvalue, errvalue] - - -class CommandOutputMatches(object): - def __init__(self, - out_str=None, out_inc=None, out_exc=None, - err_str=None, err_inc=None, err_exc=None, - return_code=None): - self.out_str = out_str - self.out_inc = out_inc or [] - self.out_exc = out_exc or [] - self.err_str = err_str - self.err_inc = err_inc or [] - self.err_exc = err_exc or [] - self.return_code = return_code - - def match(self, outputs): - out, err = outputs[0], outputs[1] - errors = [] - - # tests for exact output and error output match - errors.append(self.match_output(out, self.out_str, type='output')) - errors.append(self.match_output(err, self.err_str, type='error')) - - # tests for what output should include and what it should not - errors.append(self.match_includes(out, self.out_inc, type='output')) - errors.append(self.match_excludes(out, self.out_exc, type='output')) - - # tests for what error output should include and what it should not - errors.append(self.match_includes(err, self.err_inc, type='error')) - errors.append(self.match_excludes(err, self.err_exc, type='error')) - - # get first non None item or None if none is found and return it - return next((item for item in errors if item is not None), None) - - def match_return_code(self, return_code, expected_return_code): - if expected_return_code is not None: - if expected_return_code != return_code: - return CommandOutputReturnCodeMismatch( - return_code, expected_return_code) - - def match_output(self, output, expected_output, type='output'): - if expected_output is not None: - if expected_output != output: - return CommandOutputMismatch( - output, expected_output, type=type) - - def match_includes(self, output, includes, type='output'): - for part in includes: - if part not in output: - return CommandOutputMissingMismatch(output, part, type=type) - - def match_excludes(self, output, excludes, type='error'): - for part in excludes: - if part in output: - return CommandOutputExtraMismatch(output, part, type=type) - - -class CommandOutputMismatch(object): - def __init__(self, out, out_str, type='output'): - if type == 'error': - self.type = 'Error output' - else: - self.type = 'Output' - self.out = out - self.out_str = out_str - - def describe(self): - return "%s '%s' should be '%s'" % (self.type, self.out, self.out_str) - - def get_details(self): - return {} - - -class CommandOutputMissingMismatch(object): - def __init__(self, out, out_inc, type='output'): - if type == 'error': - self.type = 'Error output' - else: - self.type = 'Output' - self.out = out - self.out_inc = out_inc - - def describe(self): - return "%s '%s' should contain '%s'" % ( - self.type, self.out, self.out_inc) - - def get_details(self): - return {} - - -class CommandOutputExtraMismatch(object): - def __init__(self, out, out_exc, type='output'): - if type == 'error': - self.type = 'Error output' - else: - self.type = 'Output' - self.out = out - self.out_exc = out_exc - - def describe(self): - return "%s '%s' should not contain '%s'" % ( - self.type, self.out, self.out_exc) - - def get_details(self): - return {} - - -class CommandOutputReturnCodeMismatch(object): - def __init__(self, ret, ret_exp): - self.ret = ret - self.ret_exp = ret_exp - - def describe(self): - return "Return code is '%s' but expected '%s'" % ( - self.ret, self.ret_exp) - - def get_details(self): - return {} - - -class TestExit(Exception): - pass - - -class ArgumentParserForTests(argparse.ArgumentParser): - OUT = sys.stdout - ERR = sys.stderr - - def __init__(self, **kwargs): - self.out = ArgumentParserForTests.OUT - self.err = ArgumentParserForTests.ERR - - super(ArgumentParserForTests, self).__init__(**kwargs) - - def error(self, message): - self.print_usage(self.err) - self.exit(2, _('%(prog)s: error: %(message)s\n') % - {'prog': self.prog, 'message': message}) - - def exit(self, status=0, message=None): - if message: - self._print_message(message, self.err) - raise TestExit - - def print_usage(self, file=None): - if file is None: - file = self.out - self._print_message(self.format_usage(), file) - - def print_help(self, file=None): - if file is None: - file = self.out - self._print_message(self.format_help(), file) - - def print_version(self, file=None): - import warnings - warnings.warn( - 'The print_version method is deprecated -- the "version" ' - 'argument to ArgumentParser is no longer supported.', - DeprecationWarning) - self._print_message(self.format_version(), file) - - def _print_message(self, message, file=None): - if message: - if file is None: - file = self.err - file.write(message) - - -def create_test_dictionary_pair(default_keys, redundant_keys, missing_keys, - **kwargs): - """Creates a pair of dictionaries for testing - - This function creates two dictionaries from three sets of keys. - - The first returned dictionary contains keys from default_keys, - keys from redundant_keys but is missing keys from missing_keys. - All with value of key + '_value'. - - The second returned dictionary contains keys from default_keys - with value of key + '_value' except for keys from missing_keys. - These contains value None. - - These two dictionaries can be used in test cases when testing - if tested function filters out set of keys from kwargs - and passes it to other function. - - :param default_keys: set of keys expected to be passed on - :param redundant_keys: set of keys expected to be filtered out - :param missing_keys: set of keys missing from passed_dictionary - and expected to be set to None - :param kwargs: key translation pairs. original=new_one will create - original='original_value' in passed_dictionary and - new_one='original_value' in called_dictionary. - """ - passed_dictionary = {} - translations = kwargs - - for key in default_keys | redundant_keys: - if key not in missing_keys: - passed_dictionary[key] = key + '_value' - - called_dictionary = passed_dictionary.copy() - - for key in redundant_keys: - del called_dictionary[key] - - for key in missing_keys: - called_dictionary[key] = None - - for key in translations: - if key in called_dictionary: - # create new key with name from translations dict - # with original value - called_dictionary[translations[key]] = called_dictionary[key] - # delete original key - del called_dictionary[key] - - return passed_dictionary, called_dictionary diff --git a/tuskarclient/tests/v2/__init__.py b/tuskarclient/tests/v2/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tuskarclient/tests/v2/test_client.py b/tuskarclient/tests/v2/test_client.py deleted file mode 100644 index dd4ce1d..0000000 --- a/tuskarclient/tests/v2/test_client.py +++ /dev/null @@ -1,29 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -import mock - -from tuskarclient.tests import utils as tutils -from tuskarclient.v2 import client - - -class ClientTest(tutils.TestCase): - - def setUp(self): - super(ClientTest, self).setUp() - mock_http_client = mock.MagicMock() - self.client = client.Client(mock_http_client) - - def test_managers_present(self): - self.assertThat(self.client, tutils.HasManager('PlanManager', - 'plans')) - self.assertThat(self.client, tutils.HasManager('RoleManager', - 'roles')) diff --git a/tuskarclient/tests/v2/test_plans.py b/tuskarclient/tests/v2/test_plans.py deleted file mode 100644 index 0c76fa6..0000000 --- a/tuskarclient/tests/v2/test_plans.py +++ /dev/null @@ -1,147 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import mock - -import tuskarclient.tests.utils as tutils -from tuskarclient.v2 import plans -from tuskarclient.v2 import roles - - -class PlanManagerTest(tutils.TestCase): - - def setUp(self): - """Create a mock API object and bind to the PlanManager manager. - """ - super(PlanManagerTest, self).setUp() - self.api = mock.Mock() - self.pm = plans.PlanManager(self.api) - - def test_get(self): - """Test a standard GET operation to read/retrieve the plan.""" - self.assertThat('_get', tutils.IsMethodOn(self.pm)) - self.pm._get = mock.Mock(return_value='fake_plan') - - self.assertEqual(self.pm.get('fake_plan'), 'fake_plan') - self.pm._get.assert_called_with('/plans/fake_plan') - - def test_get_404(self): - """Test a 404 response to a standard GET.""" - self.assertThat('_get', tutils.IsMethodOn(self.pm)) - self.pm._get = mock.Mock(return_value=None) - - self.assertEqual(self.pm.get('fake_plan'), None) - self.pm._get.assert_called_with('/plans/fake_plan') - - def test_list(self): - """Test retrieving a list of plans via GET.""" - self.assertThat('_list', tutils.IsMethodOn(self.pm)) - self.pm._list = mock.Mock(return_value=['fake_plan']) - - self.assertEqual(self.pm.list(), ['fake_plan']) - self.pm._list.assert_called_with('/plans') - - def test_create(self): - """Test creating a new plan via POST.""" - self.assertThat('_post', tutils.IsMethodOn(self.pm)) - self.pm._post = mock.Mock(return_value=['fake_plan']) - - self.assertEqual( - self.pm.create(dummy='dummy plan data'), - ['fake_plan']) - - self.pm._post.assert_called_with( - '/plans', - {'dummy': 'dummy plan data'}) - - def test_patch(self): - """Test patching a plan.""" - self.assertThat('_patch', tutils.IsMethodOn(self.pm)) - self.pm._patch = mock.Mock(return_value=['fake_plan']) - - self.assertEqual( - self.pm.patch('42', [{'name': 'dummy', - 'value': 'dummy plan data'}]), - ['fake_plan']) - - self.pm._patch.assert_called_with( - '/plans/42', - [{'name': 'dummy', - 'value': 'dummy plan data'}]) - - def test_delete(self): - """Test deleting/removing an plan via DELETE.""" - self.assertThat('_delete', tutils.IsMethodOn(self.pm)) - self.pm._delete = mock.Mock(return_value=None) - - self.assertEqual(self.pm.delete(42), None) - self.pm._delete.assert_called_with('/plans/42') - - def test_roles_path_with_role_id(self): - """Test for building path for Role using UUID.""" - self.assertEqual(self.pm._roles_path('plan_42', 'role_abc'), - '/plans/plan_42/roles/role_abc') - - def test_roles_path_without_role_id(self): - """Test for building path for Role for POST requests.""" - self.assertEqual(self.pm._roles_path('plan_42'), - '/plans/plan_42/roles') - - def test_add_role(self): - """Test assigning Role to a Plan.""" - self.assertThat('_post', tutils.IsMethodOn(self.pm)) - self.pm._post = mock.Mock(return_value='dummy plan') - - self.assertEqual(self.pm.add_role('42', role_uuid='qwert12345'), - 'dummy plan') - self.pm._post.assert_called_with( - '/plans/42/roles', - {'uuid': 'qwert12345'}) - - def test_remove_role(self): - """Test assigning Role to a Plan.""" - self.assertThat('delete', tutils.IsMethodOn(self.api)) - api_delete_return_mock = mock.Mock() - self.api.delete = mock.Mock(return_value=api_delete_return_mock) - self.assertThat('resource_class', tutils.IsMethodOn(self.pm)) - self.pm.resource_class = mock.Mock(return_value='fake_plan') - - self.assertEqual(self.pm.remove_role('42', role_uuid='qwert12345'), - 'fake_plan') - self.api.delete.assert_called_with('/plans/42/roles/qwert12345') - self.pm.resource_class.assert_called_with( - self.pm, api_delete_return_mock.json()) - - def test_templates_path(self): - self.assertEqual(self.pm._templates_path('42'), - '/plans/42/templates') - - def test_templates(self): - """Test a GET operation to retrieve the plan's templates.""" - self.assertThat('_get', tutils.IsMethodOn(self.pm)) - self.pm._get = mock.MagicMock() - self.pm._get.return_value.to_dict.return_value = 'fake_templates_dict' - - self.assertEqual(self.pm.templates('fake_plan'), 'fake_templates_dict') - self.pm._get.assert_called_with('/plans/fake_plan/templates') - - def test_roles_subresource(self): - self.assertThat('_get', tutils.IsMethodOn(self.pm)) - self.pm._get = mock.Mock( - return_value=plans.Plan(None, - {'roles': [ - {'name': 'foo_role'}, - {'name': 'bar_role'} - ]})) - test_roles = self.pm.get('42').roles - self.assertTrue(isinstance(test_roles, list)) - self.assertTrue(isinstance(test_roles[0], roles.Role)) diff --git a/tuskarclient/tests/v2/test_plans_shell.py b/tuskarclient/tests/v2/test_plans_shell.py deleted file mode 100644 index 5212629..0000000 --- a/tuskarclient/tests/v2/test_plans_shell.py +++ /dev/null @@ -1,384 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import mock -import six - -import tuskarclient.tests.utils as tutils -from tuskarclient.v2 import plans_shell - - -def empty_args(): - args = mock.Mock(spec=[]) - for attr in ['uuid', 'name', 'description', 'parameters', - 'only_empty_parameters']: - setattr(args, attr, None) - return args - - -def mock_plan(): - plan = mock.Mock() - plan.uuid = '5' - plan.name = 'My Plan' - plan.parameters = [] - plan.parameters.append({'name': 'compute-1::count', 'value': '2'}) - plan.parameters.append({'name': 'compute-1::Flavor', 'value': 'baremetal'}) - plan.to_dict.return_value = { - 'uuid': 5, - 'name': 'My Plan', - 'parameters': plan.parameters, - } - return plan - - -class BasePlansShellTest(tutils.TestCase): - - def setUp(self): - super(BasePlansShellTest, self).setUp() - self.outfile = six.StringIO() - self.tuskar = mock.MagicMock() - self.shell = plans_shell - - -class PlansShellTest(BasePlansShellTest): - - @mock.patch('tuskarclient.common.formatting.print_list') - def test_plan_list(self, mock_print_list): - args = empty_args() - - self.shell.do_plan_list(self.tuskar, args, outfile=self.outfile) - # testing the other arguments would be just copy-paste - mock_print_list.assert_called_with( - self.tuskar.plans.list.return_value, mock.ANY, mock.ANY, - outfile=self.outfile - ) - - @mock.patch('tuskarclient.common.utils.find_resource') - @mock.patch('tuskarclient.v2.plans_shell.print_plan_summary') - def test_plan_show(self, mock_print_summary, mock_find_resource): - mock_find_resource.return_value = mock_plan() - args = empty_args() - args.plan = '5' - args.verbose = False - - self.shell.do_plan_show(self.tuskar, args, outfile=self.outfile) - mock_find_resource.assert_called_with(self.tuskar.plans, '5') - mock_print_summary.assert_called_with(mock_find_resource.return_value, - outfile=self.outfile) - - def test_filter_empty_parameters(self): - parameters = [{'name': 'setup param', 'value': '1'}, - {'name': 'empty-parameter', 'value': ''}, - {'name': 'empty-parameter', 'value': None}] - - filtered_parameters = self.shell.filter_empty_parameters(parameters) - - self.assertEqual([{'name': 'empty-parameter', 'value': ''}, - {'name': 'empty-parameter', 'value': None}], - filtered_parameters) - - @mock.patch('tuskarclient.common.utils.find_resource') - @mock.patch('tuskarclient.common.formatting.print_dict') - def test_plan_show_scale(self, mock_print_dict, mock_find_resource): - mock_find_resource.return_value = mock_plan() - args = empty_args() - args.plan = '5' - - self.shell.do_plan_show_scale(self.tuskar, args, outfile=self.outfile) - mock_find_resource.assert_called_with(self.tuskar.plans, '5') - mock_print_dict.assert_called_with({'compute-1': '2'}, - outfile=self.outfile) - - @mock.patch('tuskarclient.common.utils.find_resource') - @mock.patch('tuskarclient.common.formatting.print_dict') - def test_plan_show_flavors(self, mock_print_dict, mock_find_resource): - mock_find_resource.return_value = mock_plan() - args = empty_args() - args.plan = '5' - - self.shell.do_plan_show_flavors(self.tuskar, args, - outfile=self.outfile) - mock_find_resource.assert_called_with(self.tuskar.plans, '5') - mock_print_dict.assert_called_with({'compute-1': 'baremetal'}, - outfile=self.outfile) - - @mock.patch('tuskarclient.common.utils.find_resource') - def test_plan_delete(self, mock_find_resource): - mock_find_resource.return_value = mock_plan() - args = empty_args() - args.plan = '5' - - self.shell.do_plan_delete(self.tuskar, args, outfile=self.outfile) - self.tuskar.plans.delete.assert_called_with('5') - self.assertEqual('Deleted Plan "My Plan".\n', - self.outfile.getvalue()) - - @mock.patch('tuskarclient.v2.plans_shell.print_plan_summary') - def test_plan_create(self, mock_print_summary): - args = empty_args() - args.name = 'my_plan' - args.description = 'my plan description' - - self.shell.do_plan_create(self.tuskar, args, outfile=self.outfile) - self.tuskar.plans.create.assert_called_with( - name='my_plan', - description='my plan description' - ) - mock_print_summary.assert_called_with( - self.tuskar.plans.create.return_value, outfile=self.outfile) - - @mock.patch('tuskarclient.v2.plans_shell.print_plan_summary') - def test_add_role(self, mock_print_summary): - args = empty_args() - args.plan_uuid = '42' - args.role_uuid = 'role_uuid' - - self.shell.do_plan_add_role(self.tuskar, args, outfile=self.outfile) - self.tuskar.plans.add_role.assert_called_with('42', 'role_uuid') - - mock_print_summary.assert_called_with( - self.tuskar.plans.add_role.return_value, outfile=self.outfile) - - @mock.patch('tuskarclient.v2.plans_shell.print_plan_summary') - def test_remove_role(self, mock_print_summary): - args = empty_args() - args.plan_uuid = '42' - args.role_uuid = 'role_uuid' - - self.shell.do_plan_remove_role(self.tuskar, args, outfile=self.outfile) - self.tuskar.plans.remove_role.assert_called_with('42', 'role_uuid') - - mock_print_summary.assert_called_with( - self.tuskar.plans.remove_role.return_value, outfile=self.outfile) - - @mock.patch('tuskarclient.common.utils.find_resource') - def test_plan_scale(self, mock_find_resource): - mock_find_resource.return_value = mock_plan() - role = mock.Mock() - role.name = 'compute' - role.version = 1 - self.tuskar.roles.list.return_value = [role] - - args = empty_args() - args.plan_uuid = 'plan_uuid' - args.role_name = 'compute-1' - args.count = '9' - - parameters = [{'name': 'compute-1::count', 'value': '9'}] - - self.shell.do_plan_scale(self.tuskar, args, outfile=self.outfile) - self.assertEqual(self.tuskar.plans.patch.call_count, 1) - - self.assertEqual('plan_uuid', self.tuskar.plans.patch.call_args[0][0]) - self.assertEqual( - sorted(parameters, key=lambda k: k['name']), - sorted(self.tuskar.plans.patch.call_args[0][1], - key=lambda k: k['name'])) - - @mock.patch('tuskarclient.common.utils.find_resource') - def test_plan_flavor(self, mock_find_resource): - mock_find_resource.return_value = mock_plan() - role = mock.Mock() - role.name = 'compute' - role.version = 1 - self.tuskar.roles.list.return_value = [role] - - args = empty_args() - args.plan_uuid = 'plan_uuid' - args.role_name = 'compute-1' - args.flavor = 'baremetalssd' - - parameters = [{'name': 'compute-1::Flavor', 'value': 'baremetalssd'}] - - self.shell.do_plan_flavor(self.tuskar, args, outfile=self.outfile) - self.assertEqual(self.tuskar.plans.patch.call_count, 1) - - self.assertEqual('plan_uuid', self.tuskar.plans.patch.call_args[0][0]) - self.assertEqual( - sorted(parameters, key=lambda k: k['name']), - sorted(self.tuskar.plans.patch.call_args[0][1], - key=lambda k: k['name'])) - - @mock.patch('tuskarclient.v2.plans_shell.print_plan_summary') - def test_plan_patch(self, mock_print_summary): - args = empty_args() - args.plan_uuid = 'plan_uuid' - args.parameters = ['foo_name=foo_value', - 'bar_name=bar_value'] - args.attributes = None - parameters = [{'name': 'foo_name', 'value': 'foo_value'}, - {'name': 'bar_name', 'value': 'bar_value'}] - self.shell.do_plan_patch(self.tuskar, args, outfile=self.outfile) - self.assertEqual(self.tuskar.plans.patch.call_count, 1) - - self.assertEqual('plan_uuid', - self.tuskar.plans.patch.call_args[0][0]) - self.assertEqual( - sorted(parameters, key=lambda k: k['name']), - sorted(self.tuskar.plans.patch.call_args[0][1], - key=lambda k: k['name'])) - - @mock.patch('tuskarclient.v2.plans_shell.print_plan_summary') - def test_plan_patch_deprecated(self, mock_print_summary): - """Test plan_patch with the deprecated --attribute flag.""" - args = empty_args() - args.plan_uuid = 'plan_uuid' - args.attributes = ['foo_name=foo_value', - 'bar_name=bar_value'] - args.parameters = None - parameters = [{'name': 'foo_name', 'value': 'foo_value'}, - {'name': 'bar_name', 'value': 'bar_value'}] - self.shell.do_plan_patch(self.tuskar, args, outfile=self.outfile) - self.assertEqual(self.tuskar.plans.patch.call_count, 1) - - self.assertEqual('plan_uuid', - self.tuskar.plans.patch.call_args[0][0]) - self.assertEqual( - sorted(parameters, key=lambda k: k['name']), - sorted(self.tuskar.plans.patch.call_args[0][1], - key=lambda k: k['name'])) - - @mock.patch('tuskarclient.v2.plans_shell.print_plan_detail') - def test_plan_update(self, mock_print_detail): - args = empty_args() - args.plan_uuid = 'plan_uuid' - args.parameters = ['foo_name=foo_value', - 'bar_name=bar_value'] - parameters = [{'name': 'foo_name', 'value': 'foo_value'}, - {'name': 'bar_name', 'value': 'bar_value'}] - args.attributes = None - self.shell.do_plan_update(self.tuskar, args, outfile=self.outfile) - self.assertEqual(self.tuskar.plans.patch.call_count, 1) - - self.assertEqual('plan_uuid', - self.tuskar.plans.patch.call_args[0][0]) - self.assertEqual( - sorted(parameters, key=lambda k: k['name']), - sorted(self.tuskar.plans.patch.call_args[0][1], - key=lambda k: k['name'])) - - @mock.patch('tuskarclient.common.utils.find_resource') - def test_print_plan_summary(self, mock_find_resource): - mock_find_resource.return_value = mock_plan() - args = empty_args() - args.plan = '5' - args.verbose = False - - self.shell.do_plan_show(self.tuskar, args, outfile=self.outfile) - mock_find_resource.assert_called_with(self.tuskar.plans, '5') - - @mock.patch('tuskarclient.common.utils.find_resource') - def test_print_plan_wrap(self, mock_find_resource): - mock_find_resource.return_value = mock_plan() - mock_find_resource.return_value.parameters.append( - {'name': 'foo', - 'value': 'This is a really long parameter value with ' - 'multiple lines to test the output wrapping.\n' - 'Indents is assumed to be code:\n' - ' {\n' - ' "like": "this"\n' - ' }\n'} - ) - - args = empty_args() - args.plan = '5' - args.verbose = True - - self.shell.do_plan_show(self.tuskar, args, outfile=self.outfile) - output = self.outfile.getvalue() - - # Lines should not be way to long: - self.assertTrue(all(len(line) < 100 for line in output.splitlines())) - # The lines are rewraped: - self.assertIn("wrapping. Indents", output) - # But not if the start with an indent: - self.assertIn(" {", output) - - @mock.patch('tuskarclient.common.utils.find_resource') - def test_print_plan_detail(self, mock_find_resource): - mock_find_resource.return_value = mock_plan() - args = empty_args() - args.plan = '5' - args.verbose = True - - self.shell.do_plan_show(self.tuskar, args, outfile=self.outfile) - mock_find_resource.assert_called_with(self.tuskar.plans, '5') - - def test_filter_parameters_to_dict(self): - parameters = [{'name': 'compute-1::count', 'value': '2'}] - self.assertEqual( - self.shell.filter_parameters_to_dict(parameters, 'count'), - {'compute-1': '2'} - ) - - @mock.patch('tuskarclient.v2.plans_shell.print', create=True) - @mock.patch('tuskarclient.v2.plans_shell.os.mkdir', create=True) - @mock.patch('tuskarclient.v2.plans_shell.os.path.isdir', create=True) - @mock.patch('tuskarclient.v2.plans_shell.open', create=True) - @mock.patch('tuskarclient.v2.plans_shell.os.path.exists', create=True) - @mock.patch('tuskarclient.v2.plans_shell.os.makedirs', create=True) - def test_plan_templates( - self, mock_makedirs, mock_exists, mock_open, mock_isdir, - mock_mkdir, mock_print): - args = empty_args() - args.plan_uuid = 'plan_uuid' - args.output_dir = 'outdir/subdir' - - # Simulate the first exists check being false and the subsequent check - # being true so as to exercise that makesdirs is only called once - # per nested directory. - exists_return_values = [False, True] - - def toggle_exists_result(*e_args, **e_kwargs): - return exists_return_values.pop(0) - mock_exists.side_effect = toggle_exists_result - - mock_isdir.return_value = False - self.tuskar.plans.templates.return_value = { - 'name_foo': 'value_foo', - 'name_bar': 'value_bar', - 'nested/name_baz': 'value_baz', - 'nested/name_zom': 'value_zom' - } - - self.shell.do_plan_templates(self.tuskar, args, outfile=self.outfile) - - # Initial check and creation of the output directory - mock_isdir.assert_any_call('outdir/subdir') - mock_mkdir.assert_any_call('outdir/subdir') - - # Checks and creation of nested directory - self.assertEqual(mock_exists.call_count, 2) - self.assertEqual(mock_makedirs.call_count, 1) - mock_makedirs.assert_called_with('outdir/subdir/nested') - - self.tuskar.plans.templates.assert_called_with('plan_uuid') - - mock_open.assert_any_call('outdir/subdir/name_foo', 'w+') - mock_open.assert_any_call('outdir/subdir/name_bar', 'w+') - mock_open.assert_any_call('outdir/subdir/nested/name_baz', 'w+') - mock_open.assert_any_call('outdir/subdir/nested/name_zom', 'w+') - self.assertEqual(mock_open.call_count, 4) - - mock_opened_file = mock_open.return_value.__enter__.return_value - mock_opened_file.write.assert_any_call('value_foo') - mock_opened_file.write.assert_any_call('value_bar') - mock_opened_file.write.assert_any_call('value_baz') - mock_opened_file.write.assert_any_call('value_zom') - self.assertEqual(mock_opened_file.write.call_count, 4) - - mock_print.assert_any_call('The following templates will be written:') - mock_print.assert_any_call('outdir/subdir/name_foo') - mock_print.assert_any_call('outdir/subdir/name_bar') - mock_print.assert_any_call('outdir/subdir/nested/name_baz') - mock_print.assert_any_call('outdir/subdir/nested/name_zom') - self.assertEqual(mock_print.call_count, 5) diff --git a/tuskarclient/tests/v2/test_roles.py b/tuskarclient/tests/v2/test_roles.py deleted file mode 100644 index 941ddab..0000000 --- a/tuskarclient/tests/v2/test_roles.py +++ /dev/null @@ -1,44 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import mock - -import tuskarclient.tests.utils as tutils -from tuskarclient.v2 import roles - - -class RoleManagerTest(tutils.TestCase): - - def setUp(self): - """Create a mock API object and bind to the PlanManager manager. - """ - super(RoleManagerTest, self).setUp() - self.api = mock.Mock() - self.rm = roles.RoleManager(self.api) - - def test_list(self): - """Test retrieving a list of Roles via GET.""" - self.assertThat('_list', tutils.IsMethodOn(self.rm)) - self.rm._list = mock.Mock(return_value=['fake_role']) - - self.assertEqual(self.rm.list(), ['fake_role']) - self.rm._list.assert_called_with('/roles') - - def test_path_without_id(self): - """Test _path returns list uri.""" - self.assertEqual(self.rm._path(), '/roles') - - def test_path_with_id(self): - """Test _path returns single item uri.""" - plan_id = self.getUniqueString() - self.assertEqual(self.rm._path(plan_id), - '/roles/%s' % plan_id) diff --git a/tuskarclient/tests/v2/test_roles_shell.py b/tuskarclient/tests/v2/test_roles_shell.py deleted file mode 100644 index 3c01cbd..0000000 --- a/tuskarclient/tests/v2/test_roles_shell.py +++ /dev/null @@ -1,47 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import mock -import six - -import tuskarclient.tests.utils as tutils -from tuskarclient.v2 import roles_shell - - -def empty_args(): - args = mock.Mock(spec=[]) - for attr in ['uuid', 'name', 'version', 'description']: - setattr(args, attr, None) - return args - - -class BaseRolesShellTest(tutils.TestCase): - - def setUp(self): - super(BaseRolesShellTest, self).setUp() - self.outfile = six.StringIO() - self.tuskar = mock.MagicMock() - self.shell = roles_shell - - -class RolesShellTest(BaseRolesShellTest): - - @mock.patch('tuskarclient.common.formatting.print_list') - def test_role_list(self, mock_print_list): - args = empty_args() - - self.shell.do_role_list(self.tuskar, args, outfile=self.outfile) - # testing the other arguments would be just copy-paste - mock_print_list.assert_called_with( - self.tuskar.roles.list.return_value, mock.ANY, mock.ANY, - outfile=self.outfile - ) diff --git a/tuskarclient/v2/__init__.py b/tuskarclient/v2/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tuskarclient/v2/client.py b/tuskarclient/v2/client.py deleted file mode 100644 index 622363f..0000000 --- a/tuskarclient/v2/client.py +++ /dev/null @@ -1,29 +0,0 @@ -# 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 tuskarclient.openstack.common.apiclient import client -from tuskarclient.v2 import plans -from tuskarclient.v2 import roles - - -class Client(client.BaseClient): - """Client for the Tuskar v2 HTTP API. - - :param string endpoint: Endpoint URL for the tuskar service. - :param string token: Keystone authentication token. - :param integer timeout: Timeout for client http requests. (optional) - """ - - def __init__(self, http_client, extensions=None): - super(Client, self).__init__(http_client, extensions) - self.plans = plans.PlanManager(self) - self.roles = roles.RoleManager(self) diff --git a/tuskarclient/v2/plans.py b/tuskarclient/v2/plans.py deleted file mode 100644 index 77410e9..0000000 --- a/tuskarclient/v2/plans.py +++ /dev/null @@ -1,168 +0,0 @@ -# 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 tuskarclient.openstack.common.apiclient import base -from tuskarclient.v2 import roles - - -class Plan(base.Resource): - """Represents an instance of a Plan in the Tuskar API. - - :param manager: Manager object - :param info: dictionary representing resource attributes - :param loaded: prevent lazy-loading if set to True - """ - - def __init__(self, manager, info, loaded=False): - super(Plan, self).__init__(manager, info, loaded=loaded) - self.roles = [roles.Role(None, role) for role in self.roles] - - -class Templates(base.Resource): - """Represents sets of templates of a Plan in the Tuskar API. - - :param manager: Manager object - :param info: dictionary representing resource attributes - :param loaded: prevent lazy-loading if set to True - """ - - -class PlanManager(base.BaseManager): - """PlanManager interacts with the Tuskar API and provides CRUD - operations for the Plan type. - """ - - #: The class used to represent an Plan instance - resource_class = Plan - - @staticmethod - def _path(plan_id=None): - - if plan_id: - return '/plans/%s' % plan_id - - return '/plans' - - def _roles_path(self, plan_id, role_id=None): - roles_path = '%s/roles' % self._path(plan_id) - - if role_id: - return '%(roles_path)s/%(role_id)s' % {'roles_path': roles_path, - 'role_id': role_id} - - return roles_path - - def _templates_path(self, plan_id): - templates_path = '%s/templates' % self._path(plan_id) - - return templates_path - - def get(self, plan_uuid): - """Get the Plan by its UUID. - - :param plan_uuid: UUID of the Plan. - :type plan_uuid: string - - :return: A Plan instance or None if its not found. - :rtype: tuskarclient.v2.plans.Plan or None - """ - return self._get(self._path(plan_uuid)) - - def list(self): - """Get a list of the existing Plans - - :return: A list of plans or an empty list if none are found. - :rtype: [tuskarclient.v2.plans.Plan] or [] - """ - return self._list(self._path()) - - def create(self, **fields): - """Create a new Plan. - - :param fields: A set of key/value pairs representing a Plan. - :type fields: string - - :return: A Plan instance or None if its not found. - :rtype: tuskarclient.v2.plans.Plan - """ - return self._post(self._path(), fields) - - def patch(self, plan_uuid, parameter_list): - """Patch an existing Plan. - - :param plan_uuid: UUID of the Plan. - :type plan_uuid: string - - :param parameter_list: a list of parameter name/value dicts - Example: [{'name': , 'value': }] - :type parameter_list: list - - :return: A Plan instance or None if its not found. - :rtype: tuskarclient.v2.plans.Plan or None - """ - return self._patch(self._path(plan_uuid), - parameter_list) - - def delete(self, plan_uuid): - """Delete a Plan. - - :param plan_uuid: uuid of the Plan. - :type plan_uuid: string - - :return: None - :rtype: None - """ - return self._delete(self._path(plan_uuid)) - - def add_role(self, plan_uuid, role_uuid): - """Adds a Role to a Plan. - - :param plan_uuid: UUID of the Plan. - :type plan_uuid: string - - :param role_uuid: UUID of the Role. - :type role_uuid: string - - :return: A Plan instance or None if its not found. - :rtype: tuskarclient.v2.plans.Plan - """ - return self._post(self._roles_path(plan_uuid), {'uuid': role_uuid}) - - def remove_role(self, plan_uuid, role_uuid): - """Removes a Role from a Plan. - - :param plan_uuid: UUID of the Plan. - :type plan_uuid: string - - :param role_uuid: UUID of the Role. - :type role_uuid: string - - :return: A Plan instance or None if its not found. - :rtype: tuskarclient.v2.plans.Plan - """ - - return self._delete(self._roles_path(plan_uuid, role_uuid)) - - def templates(self, plan_uuid): - """Gets template files from a Plan. - - :param plan_uuid: UUID of the Plan. - :type plan_uuid: string - - :return: Template files contents - :rtype: dict - """ - - self.resource_class = Templates - res = self._get(self._templates_path(plan_uuid)).to_dict() - self.resource_class = Plan - return res diff --git a/tuskarclient/v2/plans_shell.py b/tuskarclient/v2/plans_shell.py deleted file mode 100644 index 98ef268..0000000 --- a/tuskarclient/v2/plans_shell.py +++ /dev/null @@ -1,302 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from __future__ import print_function - -import os -import sys - -import tuskarclient.common.formatting as fmt -from tuskarclient.common import utils -from tuskarclient.openstack.common.apiclient import exceptions as exc - - -def do_plan_list(tuskar, args, outfile=sys.stdout): - """Show a list of the Plans.""" - plans = tuskar.plans.list() - fields = ['uuid', 'name', 'description', 'roles'] - - formatters = { - 'roles': fmt.list_plan_roles_formatter, - } - - fmt.print_list(plans, fields, formatters, outfile=outfile) - - -@utils.arg('plan', metavar="", - help="UUID of the Plan to show.") -@utils.arg('--verbose', default=False, action="store_true", - help="Display full plan details") -@utils.arg('--only-empty-parameters', default=False, action="store_true", - help="Display only parameters with empty or None value") -def do_plan_show(tuskar, args, outfile=sys.stdout): - """Show an individual Plan by its UUID.""" - plan = utils.find_resource(tuskar.plans, args.plan) - if args.only_empty_parameters: - plan._info['parameters'] = ( - filter_empty_parameters(plan._info['parameters'])) - if args.verbose: - print_plan_detail(plan, outfile=outfile) - else: - print_plan_summary(plan, outfile=outfile) - - -def print_plan_summary(plan, outfile=sys.stdout): - """Print a summary of Plan information (for plan-show etc.).""" - - formatters = { - 'roles': fmt.parameters_v2_formatter, - 'parameters': fmt.parameters_v2_formatter, - } - plan_dict = plan.to_dict() - plan_dict['parameters'] = [param for param in - plan_dict['parameters'] - if param['name'].endswith('::count')] - fmt.print_dict(plan_dict, formatters, outfile=outfile) - - -@utils.arg('plan', metavar="", - help="UUID of the Plan to show a scale.") -def do_plan_show_scale(tuskar, args, outfile=sys.stdout): - """Show scale counts of Plan.""" - plan = utils.find_resource(tuskar.plans, args.plan) - scales = filter_parameters_to_dict(plan.parameters, 'count') - fmt.print_dict(scales, outfile=outfile) - - -@utils.arg('plan', metavar="", - help="UUID of the Plan to show a scale.") -def do_plan_show_flavors(tuskar, args, outfile=sys.stdout): - """Show flavors assigned to roles of Plan.""" - plan = utils.find_resource(tuskar.plans, args.plan) - flavors = filter_parameters_to_dict(plan.parameters, 'Flavor') - fmt.print_dict(flavors, outfile=outfile) - - -def filter_parameters_to_dict(parameters, param_name): - """Filters list of parameters for given parameter name suffix.""" - filtered_params = {} - suffix = '::{0}'.format(param_name) - for param in parameters: - if param['name'].endswith(suffix): - filtered_params[param['name'].replace(suffix, '')] = param["value"] - return filtered_params - - -def filter_empty_parameters(parameters): - """Filters parameters with empty or None value.""" - filtered_parameters = [param for param in parameters - if param['value'] == '' or param['value'] is None] - return filtered_parameters - - -def print_plan_detail(plan, outfile=sys.stdout): - """Print detailed Plan information (for plan-show --verbose etc.).""" - - formatters = { - 'roles': fmt.parameters_v2_formatter, - 'parameters': fmt.parameters_v2_formatter, - } - plan_dict = plan.to_dict() - fmt.print_dict(plan_dict, formatters, outfile=outfile) - - -@utils.arg('plan', metavar="", - help="UUID of the plan to delete.") -def do_plan_delete(tuskar, args, outfile=sys.stdout): - """Delete an plan by its UUID.""" - plan = utils.find_resource(tuskar.plans, args.plan) - tuskar.plans.delete(plan.uuid) - print(u'Deleted Plan "%s".' % plan.name, file=outfile) - - -@utils.arg('name', help="Name of the Plan to create.") -@utils.arg('-d', '--description', metavar="", - help='User-readable text describing the Plan.') -def do_plan_create(tuskar, args, outfile=sys.stdout): - """Create a new plan.""" - name = vars(args).get('name') - try: - plan = tuskar.plans.create( - name=name, - description=vars(args).get('description') - ) - except exc.Conflict: - raise exc.CommandError('Plan with name "%s" already exists.' % name) - print_plan_summary(plan, outfile=outfile) - - -@utils.arg('plan_uuid', help="UUID of the Plan to assign role to.") -@utils.arg('-r', '--role-uuid', metavar="", - required=True, help='UUID of the Role to be assigned.') -def do_plan_add_role(tuskar, args, outfile=sys.stdout): - """Associate role to a plan.""" - plan = tuskar.plans.add_role( - vars(args).get('plan_uuid'), - vars(args).get('role_uuid') - ) - print_plan_summary(plan, outfile=outfile) - - -@utils.arg('plan_uuid', help="UUID of the Plan to remove role from.") -@utils.arg('-r', '--role-uuid', metavar="", - required=True, help='UUID of the Role to be removed.') -def do_plan_remove_role(tuskar, args, outfile=sys.stdout): - """Remove role from a plan.""" - plan = tuskar.plans.remove_role( - vars(args).get('plan_uuid'), - vars(args).get('role_uuid') - ) - print_plan_summary(plan, outfile=outfile) - - -@utils.arg('role_name', help="Name of role which you want scale.") -@utils.arg('plan_uuid', help="UUID of the Plan to modify.") -@utils.arg('-C', '--count', help="Count of nodes to be set.", required=True) -def do_plan_scale(tuskar, args, outfile=sys.stdout): - """Scale plan by changing count of roles.""" - roles = tuskar.roles.list() - plan = utils.find_resource(tuskar.plans, args.plan_uuid) - parameters = [] - - for role in roles: - versioned_name = "{name}-{v}".format(name=role.name, v=role.version) - if versioned_name == args.role_name: - role_name_key = versioned_name + "::count" - parameters.append({'name': role_name_key, - 'value': args.count}) - old_val = [p['value'] for p in plan.parameters - if p['name'] == role_name_key][0] - - if old_val != args.count: - print("Scaling {role} count: {old_val} -> {new_val}".format( - role=args.role_name, old_val=old_val, new_val=args.count - ), file=outfile) - else: - print("Keeping scale {role} count: {count}".format( - role=args.role_name, count=old_val), file=outfile) - return - - if parameters: - return tuskar.plans.patch(args.plan_uuid, parameters) - else: - print("ERROR: no roles were found in the Plan with the name {0}". - format(args.role_name), file=sys.stderr) - - -@utils.arg('role_name', help="Name of role which you want to flavor.") -@utils.arg('plan_uuid', help="UUID of the Plan to modify.") -@utils.arg('-F', '--flavor', help="Flavor which shall be assigned to role.", - required=True) -def do_plan_flavor(tuskar, args, outfile=sys.stdout): - """Change flavor of role in the plan.""" - roles = tuskar.roles.list() - plan = utils.find_resource(tuskar.plans, args.plan_uuid) - parameters = [] - - for role in roles: - versioned_name = "{name}-{v}".format(name=role.name, v=role.version) - if versioned_name == args.role_name: - role_name_key = versioned_name + "::Flavor" - parameters.append({'name': role_name_key, - 'value': args.flavor}) - old_val = [p['value'] for p in plan.parameters - if p['name'] == role_name_key][0] - - if old_val != args.flavor: - print("Changing {role} flavor: {old_val} -> {new_val}".format( - role=args.role_name, old_val=old_val, new_val=args.flavor - ), file=outfile) - else: - print("Keeping flavor {role} unchanged: {flavor}".format( - role=args.role_name, flavor=old_val), file=outfile) - return - - if parameters: - return tuskar.plans.patch(args.plan_uuid, parameters) - else: - print("ERROR: no roles were found in the Plan with the name {0}". - format(args.role_name), file=sys.stderr) - - -@utils.arg('plan_uuid', help="UUID of the Plan to modify.") -@utils.arg('-A', '--attribute', dest='attributes', metavar='', - help=('This can be specified multiple times. This argument is ' - 'deprecated, use -P and --parameter instead.'), - action='append') -@utils.arg('-P', '--parameter', dest='parameters', metavar='', - help='This can be specified multiple times.', - action='append') -def do_plan_update(tuskar, args, outfile=sys.stdout): - """Change an existing plan.""" - - parameters = args.parameters - - if args.attributes: - print("WARNING: The attribute flags -A and --attribute are" - " deprecated and will be removed in a later release." - " Use -P and --parameter instead.", file=sys.stderr) - parameters = args.attributes - - parameters = [{'name': pair[0], 'value': pair[1]} - for pair - in utils.format_key_value_args(parameters).items()] - return tuskar.plans.patch(args.plan_uuid, parameters) - - -@utils.arg('plan_uuid', help="UUID of the Plan to modify.") -@utils.arg('-A', '--attribute', dest='attributes', metavar='', - help='This can be specified multiple times.', - action='append') -def do_plan_patch(*args, **kwargs): - """Change an existing plan [Deprecated].""" - print("WARNING: plan-patch method is deprecated" - " and will be removed in a later release." - " Use plan-update instead.", file=sys.stderr) - do_plan_update(*args, **kwargs) - - -@utils.arg('plan_uuid', - help="UUID of the Plan whose Templates will be retrieved.") -@utils.arg('-O', '--output-dir', metavar='', - required=True, - help='Directory to write template files into. ' + - 'It will be created if it does not exist.') -def do_plan_templates(tuskar, args, outfile=sys.stdout): - """Download the Heat templates for a Plan.""" - # check that output directory exists and we can write into it - output_dir = args.output_dir - - if not os.path.isdir(output_dir): - os.mkdir(output_dir) - - # retrieve templates - templates = tuskar.plans.templates(args.plan_uuid) - - # write file for each key-value in templates - print("The following templates will be written:") - for template_name, template_content in templates.items(): - - # It's possible to organize the role templates and their dependent - # files into directories, in which case the template_name will carry - # the directory information. If that's the case, first create the - # directory structure (if it hasn't already been created by another - # file in the templates list). - template_dir = os.path.dirname(template_name) - output_template_dir = os.path.join(output_dir, template_dir) - if template_dir and not os.path.exists(output_template_dir): - os.makedirs(output_template_dir) - - filename = os.path.join(output_dir, template_name) - with open(filename, 'w+') as template_file: - template_file.write(template_content) - print(filename) diff --git a/tuskarclient/v2/roles.py b/tuskarclient/v2/roles.py deleted file mode 100644 index 8fd65ed..0000000 --- a/tuskarclient/v2/roles.py +++ /dev/null @@ -1,47 +0,0 @@ -# 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 tuskarclient.openstack.common.apiclient import base - - -class Role(base.Resource): - """Represents an instance of a Role in the Tuskar API. - - :param manager: Manager object - :param info: dictionary representing resource attributes - :param loaded: prevent lazy-loading if set to True - """ - - -class RoleManager(base.BaseManager): - """RoleManager interacts with the Tuskar API and provides - operations for adding/removing Roles to/from Plans. - """ - - # The class used to represent a Role instance - resource_class = Role - - @staticmethod - def _path(role_id=None): - - if role_id: - return '/roles/%s' % role_id - - return '/roles' - - def list(self): - """Get a list of the existing Roles - - :return: A list of Roles or an empty list if none are found. - :rtype: [tuskarclient.v2.plans.Role] or [] - """ - return self._list(self._path()) diff --git a/tuskarclient/v2/roles_shell.py b/tuskarclient/v2/roles_shell.py deleted file mode 100644 index f834bf2..0000000 --- a/tuskarclient/v2/roles_shell.py +++ /dev/null @@ -1,31 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from __future__ import print_function - -import sys - -import six - -import tuskarclient.common.formatting as fmt - - -def do_role_list(tuskar, args, outfile=sys.stdout): - """Show a list of the Roles.""" - roles = tuskar.roles.list() - fields = ['uuid', 'name', 'version', 'description'] - - formatters = { - 'description': six.text_type.strip, - } - - fmt.print_list(roles, fields, formatters, outfile=outfile) diff --git a/tuskarclient/v2/shell.py b/tuskarclient/v2/shell.py deleted file mode 100644 index 8d3035d..0000000 --- a/tuskarclient/v2/shell.py +++ /dev/null @@ -1,32 +0,0 @@ -# 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 tuskarclient.common import utils -from tuskarclient.v2 import plans_shell -from tuskarclient.v2 import roles_shell - -COMMAND_MODULES = [ - plans_shell, - roles_shell -] - - -def enhance_parser(parser, subparsers): - """Take a basic (nonversioned) parser and enhance it with - commands and options specific for this version of API. - - :param parser: top level parser - :param subparsers: top level parser's subparsers collection - where subcommands will go - """ - for command_module in COMMAND_MODULES: - utils.define_commands_from_module(subparsers, command_module)