From 1269bb20c770ca9869f54da9e1db9af7173b9809 Mon Sep 17 00:00:00 2001 From: Steve Baker Date: Wed, 10 Oct 2012 08:55:03 +1300 Subject: [PATCH] Initial import --- .gitignore | 4 + LICENSE | 175 ++++++++++ MANIFEST.in | 5 + README.rst | 12 + doc/source/conf.py | 65 ++++ doc/source/index.rst | 32 ++ heatclient/__init__.py | 26 ++ heatclient/__init__.pyc | Bin 0 -> 530 bytes heatclient/client.py | 19 ++ heatclient/client.pyc | Bin 0 -> 544 bytes heatclient/common/__init__.py | 0 heatclient/common/__init__.pyc | Bin 0 -> 162 bytes heatclient/common/base.py | 131 +++++++ heatclient/common/base.pyc | Bin 0 -> 6012 bytes heatclient/common/http.py | 276 +++++++++++++++ heatclient/common/http.pyc | Bin 0 -> 9793 bytes heatclient/common/utils.py | 134 ++++++++ heatclient/common/utils.pyc | Bin 0 -> 5142 bytes heatclient/exc.py | 163 +++++++++ heatclient/exc.pyc | Bin 0 -> 7804 bytes heatclient/exceptions.py | 166 +++++++++ heatclient/openstack/__init__.py | 0 heatclient/openstack/__init__.pyc | Bin 0 -> 165 bytes heatclient/openstack/common/__init__.py | 0 heatclient/openstack/common/__init__.pyc | Bin 0 -> 172 bytes heatclient/openstack/common/importutils.py | 59 ++++ heatclient/openstack/common/importutils.pyc | Bin 0 -> 2006 bytes heatclient/openstack/common/setup.py | 358 ++++++++++++++++++++ heatclient/openstack/common/setup.pyc | Bin 0 -> 13115 bytes heatclient/openstack/common/version.py | 148 ++++++++ heatclient/openstack/common/version.pyc | Bin 0 -> 6640 bytes heatclient/shell.py | 348 +++++++++++++++++++ heatclient/v1/__init__.py | 16 + heatclient/v1/__init__.pyc | Bin 0 -> 225 bytes heatclient/v1/client.py | 33 ++ heatclient/v1/client.pyc | Bin 0 -> 1182 bytes heatclient/v1/shell.py | 29 ++ heatclient/v1/stacks.py | 198 +++++++++++ heatclient/v1/stacks.pyc | Bin 0 -> 7915 bytes heatclient/version.py | 19 ++ heatclient/version.pyc | Bin 0 -> 362 bytes heatclient/versioninfo | 1 + openstack-common.conf | 7 + run_tests.sh | 49 +++ setup.cfg | 15 + setup.py | 47 +++ tests/__init__.py | 0 tests/__init__.pyc | Bin 0 -> 150 bytes tests/test_foo.py | 7 + tests/test_foo.pyc | Bin 0 -> 608 bytes tools/pip-requires | 4 + tools/test-requires | 11 + tools/with_venv.sh | 10 + tox.ini | 46 +++ 54 files changed, 2613 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 MANIFEST.in create mode 100644 README.rst create mode 100644 doc/source/conf.py create mode 100644 doc/source/index.rst create mode 100644 heatclient/__init__.py create mode 100644 heatclient/__init__.pyc create mode 100644 heatclient/client.py create mode 100644 heatclient/client.pyc create mode 100644 heatclient/common/__init__.py create mode 100644 heatclient/common/__init__.pyc create mode 100644 heatclient/common/base.py create mode 100644 heatclient/common/base.pyc create mode 100644 heatclient/common/http.py create mode 100644 heatclient/common/http.pyc create mode 100644 heatclient/common/utils.py create mode 100644 heatclient/common/utils.pyc create mode 100644 heatclient/exc.py create mode 100644 heatclient/exc.pyc create mode 100644 heatclient/exceptions.py create mode 100644 heatclient/openstack/__init__.py create mode 100644 heatclient/openstack/__init__.pyc create mode 100644 heatclient/openstack/common/__init__.py create mode 100644 heatclient/openstack/common/__init__.pyc create mode 100644 heatclient/openstack/common/importutils.py create mode 100644 heatclient/openstack/common/importutils.pyc create mode 100644 heatclient/openstack/common/setup.py create mode 100644 heatclient/openstack/common/setup.pyc create mode 100644 heatclient/openstack/common/version.py create mode 100644 heatclient/openstack/common/version.pyc create mode 100644 heatclient/shell.py create mode 100644 heatclient/v1/__init__.py create mode 100644 heatclient/v1/__init__.pyc create mode 100644 heatclient/v1/client.py create mode 100644 heatclient/v1/client.pyc create mode 100644 heatclient/v1/shell.py create mode 100644 heatclient/v1/stacks.py create mode 100644 heatclient/v1/stacks.pyc create mode 100644 heatclient/version.py create mode 100644 heatclient/version.pyc create mode 100644 heatclient/versioninfo create mode 100644 openstack-common.conf create mode 100755 run_tests.sh create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 tests/__init__.py create mode 100644 tests/__init__.pyc create mode 100644 tests/test_foo.py create mode 100644 tests/test_foo.pyc create mode 100644 tools/pip-requires create mode 100644 tools/test-requires create mode 100755 tools/with_venv.sh create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..fd264265 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build +dist +python_heatclient.egg-info +*.log \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..67db8588 --- /dev/null +++ b/LICENSE @@ -0,0 +1,175 @@ + + 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 new file mode 100644 index 00000000..03db8ab0 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,5 @@ +include AUTHORS +include ChangeLog +include heatclient/versioninfo +exclude .gitignore +exclude .gitreview diff --git a/README.rst b/README.rst new file mode 100644 index 00000000..bfbde8e8 --- /dev/null +++ b/README.rst @@ -0,0 +1,12 @@ +Python bindings to the Heat orchestration API +============================================= + +This is a client library for Heat built on the Heat orchestration API. It +provides a Python API (the ``heatclient`` module) and a command-line tool +(``heat``). + +Development takes place via the usual OpenStack processes as outlined in the +`OpenStack wiki `_. The master +repository is on `GitHub `_. + +See release notes and more at ``_. diff --git a/doc/source/conf.py b/doc/source/conf.py new file mode 100644 index 00000000..3dd7959c --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +# + +import os +import sys + +project = 'python-heatclient' + +# -- General configuration ---------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] + +# autodoc generation is a bit aggressive and a nuisance when doing heavy +# text edit cycles. +# execute "export SPHINX_DEBUG=1" in your terminal to disable + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +copyright = u'OpenStack LLC' + +# If true, '()' will be appended to :func: etc. cross-reference text. +add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +add_module_names = True + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# -- Options for HTML output -------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +html_theme = 'nature' + +# Output file base name for HTML help builder. +htmlhelp_basename = '%sdoc' % project + + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass +# [howto/manual]). +latex_documents = [ + ( + 'index', + '%s.tex' % project, + u'%s Documentation' % project, + u'OpenStack LLC', + 'manual' + ), +] + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 00000000..4313aa36 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,32 @@ +Python API +========== +In order to use the python api directly, you must first obtain an auth token and identify which endpoint you wish to speak to. Once you have done so, you can use the API like so:: + + >>> from heatclient import Client + >>> heat = Client('1', endpoint=OS_IMAGE_ENDPOINT, token=OS_AUTH_TOKEN) +... + + +Command-line Tool +================= +In order to use the CLI, you must provide your OpenStack username, password, tenant, and auth endpoint. Use the corresponding configuration options (``--os-username``, ``--os-password``, ``--os-tenant-id``, and ``--os-auth-url``) or set them in environment variables:: + + export OS_USERNAME=user + export OS_PASSWORD=pass + export OS_TENANT_ID=b363706f891f48019483f8bd6503c54b + export OS_AUTH_URL=http://auth.example.com:5000/v2.0 + +The command line tool will attempt to reauthenticate using your provided credentials for every request. You can override this behavior by manually supplying an auth token using ``--os-image-url`` and ``--os-auth-token``. You can alternatively set these environment variables:: + + export OS_IMAGE_URL=http://heat.example.org:8004/ + export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155 + +Once you've configured your authentication parameters, you can run ``heat help`` to see a complete listing of available commands. + + +Release Notes +============= + +0.1.0 +----- +* Initial release diff --git a/heatclient/__init__.py b/heatclient/__init__.py new file mode 100644 index 00000000..0585a75d --- /dev/null +++ b/heatclient/__init__.py @@ -0,0 +1,26 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +#NOTE(bcwaldon): this try/except block is needed to run setup.py due to +# its need to import local code before installing required dependencies +try: + import heatclient.client + Client = heatclient.client.Client +except ImportError: + import warnings + warnings.warn("Could not import heatclient.client", ImportWarning) + +import heatclient.version + +__version__ = heatclient.version.version_info.deferred_version_string() diff --git a/heatclient/__init__.pyc b/heatclient/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b6f9e2a20417080af87a22d5cd8a844a2ec7378 GIT binary patch literal 530 zcmZWlO-}+b5S{L_fXhPEn0WC(JaK{j1IC!32V-JPJYcvq>$XUjr6uiv;mq&wSNa2V z_5;z_WMALBc{{JI|Je#c*@B%1Ofz_?`|bUpBgjI?IL*q_S;(_Bf7qiP#;$%ZEc|5_OSQL#y7^;1d5XWgmdbc;mNIrIH}W+Waueq!O>&1xx`oEtqnoGAW9ImIjFd_=C|epgE)*15bc Sp3U_Ld={}|)+IdiBJUfUNr^Q8 literal 0 HcmV?d00001 diff --git a/heatclient/client.py b/heatclient/client.py new file mode 100644 index 00000000..8237b8e1 --- /dev/null +++ b/heatclient/client.py @@ -0,0 +1,19 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from heatclient.common import utils + + +def Client(version, *args, **kwargs): + module = utils.import_versioned_module(version, 'client') + client_class = getattr(module, 'Client') + return client_class(*args, **kwargs) diff --git a/heatclient/client.pyc b/heatclient/client.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd1471491b90165372e0dcf32c95667e03b8e1ba GIT binary patch literal 544 zcmb_Y%TB{E5L_qaAwBtL)twnz9apX~<+OI?(i%B{Z)T(r=@Q>fxY$OC@4e_|9Lg?Ue(5&%81DYv zskQ}k0!;z<4lJ9y+GH$dWuCIqtTO9#VOa{j$lA5cU2`=zh4dA&KeWF|+x6K0^C^t| qh@{Tdi5i8$CHhFr" % (self.__class__.__name__, info) + + def get(self): + # 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) + + def __eq__(self, other): + 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/heatclient/common/base.pyc b/heatclient/common/base.pyc new file mode 100644 index 0000000000000000000000000000000000000000..820292725e43863b55a28aec726c2f962b7aea7e GIT binary patch literal 6012 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 sys.stderr, msg + sys.exit(1) diff --git a/heatclient/common/utils.pyc b/heatclient/common/utils.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e86b9a5b66e5bb5369879d4009c209e743ba1281 GIT binary patch literal 5142 zcmcIo-*4Q;5uUv}opiEf%Z}~%N1GC;U8{z3stdHpgOLQa;@AzU8nm4hYF&aNS2~_} zj3GcGTQW^k9}`}qJLB03Kabh`Uj-nH@qKCg1lK0xi{RMot>TeW_HNt|5lej z`kQ;2$mFkq-;Z&-f1ycn8OcSqXS9)RGwpB4c4JC6WxF}0Te97n(hIV^pmam>whR_! zdr=JKHYHz@K}WVbcy7sSEV3-=f)eK>14mn4W3?4Y7qxR$(j_I%OWIN5f~3nztVw!K ziHnl1C~=93r>iRAvZR=DLFp^(f407c&1GNVZ)@Fv^i4GOD5)RLad0p!D?jpC?$k77 zplL4Pc308-373&w?BB>AMNS%W+<;*6Y$cS*yM_B5-0sI{jz#*opq7z6k<*6sF}opO zihQY(&||>#Y@Lg3`3~B++8YgQ;T`NDq62;jR1~FI;-knf

<+TD1Rd*8-mRybSv zdMi|5cT^;*m**2CI%V5`MRFZo;vS>f=#>Mz;k-SxI~%Dz+{nu$&Yh2w{f*(+_sZh^ zo{fEyXV7Qk4KgVQgR%pWfl9fdXMtCurzNQT}^ipE}|#oRzmKx9-#5& z18-L=+7ui9K7ML-%%z2ypE&rCBk!Vtd*CNH=C%54^VA5giB}wTycg(9lAP&yUq=Jy zy}TeN8Xw0-u6}L~5EGnBR$cbaZFgsDzFW_M?oT|ylGWXy-mXI&4g6rw7WQaZeRzlB zxoTVH5b2j$Q9n_U{EQUwmmqvtS?|YD(krvXMtSD^kER-lNoY>uhd5S;{0D^Bs6oiU zrkgKU;?;#pcVu$jJgj?W8S72lebsP8Q`D zS3~yOQoWMnw)9(kMCbM61-crSIrxkneLQ3FHnUaQV#uB>iO1{(IbLE`J8jDFAIw@O zovEbGbp97mrmx*9@B`5fK7 zMnDJc;*gkj#&tqqs`6}Vv-)@^xBC24&HP-S7cl+^p=>>=IM4P96%-GLwn()zFJB>N zaQ(^-^Ek0e?^z#vUuoXhDvP=^PSdC=Uul?wA#%QgzU%awGL}32I>GE_Hcy@M?UvO5 zl5FL4GrJ1=q1T5}lP@JwJDpU|;q<782Uf+Vv5(IRm(+cTU3kUgT|BvKXvD0WPGi-y z&8k^3HyYQ>HFMEiY-sQksZ>}|YC+T}1s(yzJtX$#6qx7%zzl&&3V0&I(zH$OnZQ(9K%7(P17;=Ou}SE zYZIMn?f-%p|C9!Yji4LC(!EC-=P3+Z-B%;S{)U~kQJ;NLT$UEL31jasOZ8ZcF@=%e zjBnBSur!Q1Sh`;iEU#`ZrmboFMd^ z^jD<6NSl3N38Z(IJm*4qpcdqrmrU5bb6~8r=;L8oe@g zUG*L##oYn(RvFxO!K=*of;eDrfN4)QgK{_E=@Zx^_}O*0)>+GFsAUc{gO3Y@eG`2M z1P%YprP*HQwGWXVVu)7a3jR?!Lh7J`qfu59FJ3&^R9<8R6|129q^gjw$VjC<(^_R+ zIjWHTlRS2AQqNG)C<xATO}5x(V5$Z9cmlqui7>|))9OR0$R@foh&_@ z>^oY+SsP@#8VW3K)n=2Oy5Q?u?Jh3|_54*|{ z(uuVLzQ>(#U(c(NJ$rLAYBKi?V@DH{q&pbZl26(*JkE|Tq69GPqn1Gnnk30o@5``5VL$7qh;rx7+^-sX`E2*MCJOmHM453}miinBuD2Sgq$ zqLS0``mo3NBvthv!M2u%uS@0E*d>QMxnY66Xx%ZIY6@MuCX45Axgm3cJFMKsrHNQ`ILI&%W zx>D=1(&^}A>G0N(pP}JVEPAxUQ7Bw+_yXW8^6M9ELpK)y?9|OD5~eSBM9=)~wtINH#(KAYZDsln zH6%#YUkRc?nT~QhKcx;)=pxN(o#jS5^{|%Q6HZL6(f@YeaarL_RGO$@(}{90$F!_k zhT*Om_4+Iw%&f-(0=(7LNffm3(0KAo+6qyjUprtce!!W0>&R$~dg~P8#&z-U@JXVo z&wbPz_1B2-7i7hK273=*LhLDS$E+pT;+k1!Zc}hVm7-Ks_hQ$}vz?HUAO4oj2W(g+ zYf&-xd|mF`#K63-i;|m_QnKHzzj`PXLP9cRH5J}vbDs^d7=FU$Q#QY1^PCMsDa7|Q zQI+TBQ0ndCVF{}|s3G?u&sKK}%?jRS^uMvvSTk#_PN&maxzt(gT<^3=cUD&x{||?d BkJJDF literal 0 HcmV?d00001 diff --git a/heatclient/exc.py b/heatclient/exc.py new file mode 100644 index 00000000..d3d3cab6 --- /dev/null +++ b/heatclient/exc.py @@ -0,0 +1,163 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import sys + + +class BaseException(Exception): + """An error occurred.""" + def __init__(self, message=None): + self.message = message + + def __str__(self): + return self.message or self.__class__.__doc__ + + +class CommandError(BaseException): + """Invalid usage of CLI""" + + +class InvalidEndpoint(BaseException): + """The provided endpoint is invalid.""" + + +class CommunicationError(BaseException): + """Unable to communicate with server.""" + + +class ClientException(Exception): + """DEPRECATED""" + + +class HTTPException(ClientException): + """Base exception for all HTTP-derived exceptions""" + code = 'N/A' + + def __init__(self, details=None): + self.details = details + + def __str__(self): + return "%s (HTTP %s)" % (self.__class__.__name__, self.code) + + +class HTTPMultipleChoices(HTTPException): + code = 300 + + def __str__(self): + self.details = ("Requested version of OpenStack Images API is not" + "available.") + return "%s (HTTP %s) %s" % (self.__class__.__name__, self.code, + self.details) + + +class BadRequest(HTTPException): + """DEPRECATED""" + code = 400 + + +class HTTPBadRequest(BadRequest): + pass + + +class Unauthorized(HTTPException): + """DEPRECATED""" + code = 401 + + +class HTTPUnauthorized(Unauthorized): + pass + + +class Forbidden(HTTPException): + """DEPRECATED""" + code = 403 + + +class HTTPForbidden(Forbidden): + pass + + +class NotFound(HTTPException): + """DEPRECATED""" + code = 404 + + +class HTTPNotFound(NotFound): + pass + + +class HTTPMethodNotAllowed(HTTPException): + code = 405 + + +class Conflict(HTTPException): + """DEPRECATED""" + code = 409 + + +class HTTPConflict(Conflict): + pass + + +class OverLimit(HTTPException): + """DEPRECATED""" + code = 413 + + +class HTTPOverLimit(OverLimit): + pass + + +class HTTPInternalServerError(HTTPException): + code = 500 + + +class HTTPNotImplemented(HTTPException): + code = 501 + + +class HTTPBadGateway(HTTPException): + code = 502 + + +class ServiceUnavailable(HTTPException): + """DEPRECATED""" + code = 503 + + +class HTTPServiceUnavailable(ServiceUnavailable): + pass + + +#NOTE(bcwaldon): Build a mapping of HTTP codes to corresponding exception +# classes +_code_map = {} +for obj_name in dir(sys.modules[__name__]): + if obj_name.startswith('HTTP'): + obj = getattr(sys.modules[__name__], obj_name) + _code_map[obj.code] = obj + + +def from_response(response): + """Return an instance of an HTTPException based on httplib response.""" + cls = _code_map.get(response.status, HTTPException) + return cls() + + +class NoTokenLookupException(Exception): + """DEPRECATED""" + pass + + +class EndpointNotFound(Exception): + """DEPRECATED""" + pass diff --git a/heatclient/exc.pyc b/heatclient/exc.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dbb3ca5924e9c1e9bb6d1b2f108ea3b429094e1b GIT binary patch literal 7804 zcmc&(TW=f36+TN!v~{y=S*9&JwpW(r(2XsnZQAq_q>*SP6`(M}NI?+-$zruLidJ6k zGCNDzE>fU?{nEDWHNdlN>GP9kbL5$#GH@Gw+0@o+EjI)Hy?)wAAw?FBa5GmO4UmiqweVy=q@Lsjlagx^x-Wf}sAbFkCgyFqmsTWATR`A}m)Jc-JNKG2v+m?Ef zq+9ULTIwZ|cM9s9rB0E&OKQr@FmI`sN!}}XU$@k0k~LD(hWEauULpB|g7*zet&)6D zP#;?ARgwz@bG>6~vr;$zYaM{;t^azJ8DT%i9Sh#%IOm=N zoTJ&H7Y;Q!fJ;V(VG>LkUN^AdfRl~P3GqsAg1(<5w@=pJXM~gd2OD*b5?WwIrsrDA5B=94h`N8AG z_Oom!i68DLKMSHz#o1!tP(2M6nBhFn;dx;kW}XM>HC7}iz*1;!Sk75`MFm5A>l`tycJ3aBt=p@W6cv`y1O=E$IQ*p-2**FjDNvq|@ zas>es@U!{Aq|4m&1@8J|kHZY_^fY&?@tz-r((NEl++^FWudUXM4zP!SUyXvNp$+Uf z!_Anw;$)LN?#ieX~=K4VefHY*x4QFX&e*3gKadF|F<0e4>C&s`4u6o4Fq zq(LIpX#u?ecD~8YBD_Jm@4}t91bJ`>oi3mQ@(@@WKnp;xlo--+NWwfHcA_k7N2%8qUdZgEloX|4*QSH8pl9OntZ>XJP5z@ ze&A>7iT~_iYIPVC{&#?K-(i`5-zPV0jzHn773WgLZRMcs#{k7|pa$@%57AFG4WHw+ zUO*QMib%c?LC4YPEVk@jmhTUcxC@UE9P*mmDQGwl>tF!j^X9h2gRG4D{qMMz;Na(> zZt-%7xrE9(Y3%wj7x=hV9S5d@$&Z-WmU7%JF2P8bFRVLR){eq0H&wcw#9A#FpXIe? zlpMU4-{#ebK37?WdmvAmb+o|!)!bN29k}?m;S9Lq!f*3q`y@_mlW=aQNy{tN`5Od* zxu|r-$>8TkvY9+q@mi8R?zF9*0a)h{F$O9-DqUao%U*k(pN!x2Gi~C=T3_%R5-4d4RDT7MS%KrzM2`U22wa{8 z;6&hBC*Vqfqe~#07f6T&vQ2@cO<)@cBozY3vcQoiTy(SO0A^wi9X6IgMaRe`!!-Zp z$NV~>^O73&d$P6b;r9zub>O&8GmYA@Vdil6vn(|eFkfE$dIR&qdje)Sh0rjY>7D#% uly_k%z0BQsxjIrE>i!$6j#h_x+~0anIoEKtQgN<3lTOu{E{{~I!~X*!bV<+v literal 0 HcmV?d00001 diff --git a/heatclient/exceptions.py b/heatclient/exceptions.py new file mode 100644 index 00000000..da6f8d4c --- /dev/null +++ b/heatclient/exceptions.py @@ -0,0 +1,166 @@ +# Copyright 2010 Jacob Kaplan-Moss +""" +Exception definitions. +""" + + +class UnsupportedVersion(Exception): + """Indicates that the user is trying to use an unsupported + version of the API""" + pass + + +class CommandError(Exception): + pass + + +class AuthorizationFailure(Exception): + pass + + +class NoUniqueMatch(Exception): + pass + + +class AuthSystemNotFound(Exception): + """When the user specify a AuthSystem but not installed.""" + def __init__(self, auth_system): + self.auth_system = auth_system + + def __str__(self): + return "AuthSystemNotFound: %s" % repr(self.auth_system) + + +class NoTokenLookupException(Exception): + """This form of authentication does not support looking up + endpoints from an existing token.""" + pass + + +class EndpointNotFound(Exception): + """Could not find Service or Region in Service Catalog.""" + pass + + +class AmbiguousEndpoints(Exception): + """Found more than one matching endpoint in Service Catalog.""" + def __init__(self, endpoints=None): + self.endpoints = endpoints + + def __str__(self): + return "AmbiguousEndpoints: %s" % repr(self.endpoints) + + +class ConnectionRefused(Exception): + """ + Connection refused: the server refused the connection. + """ + def __init__(self, response=None): + self.response = response + + def __str__(self): + return "ConnectionRefused: %s" % repr(self.response) + + +class ClientException(Exception): + """ + The base exception class for all exceptions this library raises. + """ + def __init__(self, code, message=None, details=None, request_id=None): + self.code = code + self.message = message or self.__class__.message + self.details = details + self.request_id = request_id + + def __str__(self): + formatted_string = "%s (HTTP %s)" % (self.message, self.code) + if self.request_id: + formatted_string += " (Request-ID: %s)" % self.request_id + + return formatted_string + + +class BadRequest(ClientException): + """ + HTTP 400 - Bad request: you sent some malformed data. + """ + http_status = 400 + message = "Bad request" + + +class Unauthorized(ClientException): + """ + HTTP 401 - Unauthorized: bad credentials. + """ + http_status = 401 + message = "Unauthorized" + + +class Forbidden(ClientException): + """ + HTTP 403 - Forbidden: your credentials don't give you access to this + resource. + """ + http_status = 403 + message = "Forbidden" + + +class NotFound(ClientException): + """ + HTTP 404 - Not found + """ + http_status = 404 + message = "Not found" + + +class OverLimit(ClientException): + """ + HTTP 413 - Over limit: you're over the API limits for this time period. + """ + http_status = 413 + message = "Over limit" + + +# NotImplemented is a python keyword. +class HTTPNotImplemented(ClientException): + """ + HTTP 501 - Not Implemented: the server does not support this operation. + """ + http_status = 501 + message = "Not Implemented" + + +# In Python 2.4 Exception is old-style and thus doesn't have a __subclasses__() +# so we can do this: +# _code_map = dict((c.http_status, c) +# for c in ClientException.__subclasses__()) +# +# Instead, we have to hardcode it: +_code_map = dict((c.http_status, c) for c in [BadRequest, Unauthorized, + Forbidden, NotFound, OverLimit, HTTPNotImplemented]) + + +def from_response(response, body): + """ + Return an instance of an ClientException or subclass + based on an httplib2 response. + + Usage:: + + resp, body = http.request(...) + if resp.status != 200: + raise exception_from_response(resp, body) + """ + cls = _code_map.get(response.status, ClientException) + request_id = response.get('x-compute-request-id') + if body: + message = "n/a" + details = "n/a" + if hasattr(body, 'keys'): + error = body[body.keys()[0]] + message = error.get('message', None) + details = error.get('details', None) + return cls(code=response.status, message=message, details=details, + request_id=request_id) + else: + return cls(code=response.status, request_id=request_id) diff --git a/heatclient/openstack/__init__.py b/heatclient/openstack/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/heatclient/openstack/__init__.pyc b/heatclient/openstack/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2f4e2ad91be1e226e828f48e14e74dfbbf23b9aa GIT binary patch literal 165 zcmZSn%*(YdvN#}_0SXv_v;zCHhFr{DRaxh_d+j%)HE!_;@{_ WdJdqGHo5sJr8%i~Ad8EEm;nF`rYM{M literal 0 HcmV?d00001 diff --git a/heatclient/openstack/common/__init__.py b/heatclient/openstack/common/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/heatclient/openstack/common/__init__.pyc b/heatclient/openstack/common/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f06a059993db0657602647045fae84c7686c1234 GIT binary patch literal 172 zcmZSn%*(YdvN#}_0SXv_v;zCHhFr{DRaxh_dAT+}!*;{rLFI dyv&mLcs-yt4xq_4x%nxjIjMFa`-_2?0RUIED_Z~n literal 0 HcmV?d00001 diff --git a/heatclient/openstack/common/importutils.py b/heatclient/openstack/common/importutils.py new file mode 100644 index 00000000..f45372b4 --- /dev/null +++ b/heatclient/openstack/common/importutils.py @@ -0,0 +1,59 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 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 related utilities and helper functions. +""" + +import sys +import traceback + + +def import_class(import_str): + """Returns a class from a string including module and class""" + mod_str, _sep, class_str = import_str.rpartition('.') + try: + __import__(mod_str) + return getattr(sys.modules[mod_str], class_str) + except (ValueError, AttributeError), exc: + raise ImportError('Class %s cannot be found (%s)' % + (class_str, + traceback.format_exception(*sys.exc_info()))) + + +def import_object(import_str, *args, **kwargs): + """Import a class and return an instance of it.""" + return import_class(import_str)(*args, **kwargs) + + +def import_object_ns(name_space, import_str, *args, **kwargs): + """ + Import a class and return an instance of it, first by trying + to find the class in a default namespace, then failing back to + a full path if not found in the default namespace. + """ + import_value = "%s.%s" % (name_space, import_str) + try: + return import_class(import_value)(*args, **kwargs) + except ImportError: + return import_class(import_str)(*args, **kwargs) + + +def import_module(import_str): + """Import a module.""" + __import__(import_str) + return sys.modules[import_str] diff --git a/heatclient/openstack/common/importutils.pyc b/heatclient/openstack/common/importutils.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7911c17da5d5e78b5c76664fd99cf6eff8e55b58 GIT binary patch literal 2006 zcmcgt+ioL85Un0xHZ}=bE=#gVNDWeu5y9q>2ebl3Ai+aGUWpA6(r7%>6OS|Qwx-Jg zqeMuAN5rS_4g3=y0HenM`c$2A=g+<2@88C+bK3qAd_Td6pTiVH zXRwBfgla=)a9Tg1vxJHc{eplF6{qg!VJGyu0q4MadpP7 zuxqaBiYpgw<_kM_^~yE2T>G4>;Ny5${e|C2Zm_k7(Z@K(h(Ezx5iJzkjiK*4bZzK5 zq3aH9-XS)0WeDqr6XFgnx-x%!)u9`}dFG!`mwUv0DigXGQu8C3>n?&H2wX(*4lM?{ zaY#2rzmf#Lf%ASgxhCx{HI!)6``Akzl zCpveWF*kTHkZq!_wU|0b)~?z5&r23|(Ur--d)Yu+x&C2e}zkOAi0WVxGV`2|13 ztPIUE=!1JS{o_C*v!O_!3MkoYHV?2Us*|X2< zKuWsN+Ca`x2Y}^sXG3W#9_y~@E7&!X3#NigDaxAjh{`0o_+H%m;$;svZg~l_h0FO} zEJSGrl;rY}^vX~w#Gn&Eg5W{40r_n9m7T8W4%9$K!p<}QHpFIBW?zyqMi^9W-o~72 zlMou>A236U-+$op$d*+TA)1ZlW&@?^0UWT54Dj4-by<0&rf_ApuG#u*>Ea62eIyay zmKiz`L@LE@Ip`4?T2WowRmO8$l~#n=suCy3vHv=2tlOf{bQs5n@nj^-X_lv=m0OL- z^{L*knV-HYaav$^3iML+dS3+~8u&*6q&~inwJ3du`VS$AeY4lfLMmQfc8n1p!Mu=# zL8=!>KGEZ?Fy!19$!xpPxGj0*M!LdO2x|o)ufMMs-zoVPmo)PzfG8On{nOG0mZn9> w({!p(rFpdpeOQUi4ed|-y#N3J literal 0 HcmV?d00001 diff --git a/heatclient/openstack/common/setup.py b/heatclient/openstack/common/setup.py new file mode 100644 index 00000000..317e82d8 --- /dev/null +++ b/heatclient/openstack/common/setup.py @@ -0,0 +1,358 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 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. + +""" +Utilities with minimum-depends for use in setup.py +""" + +import datetime +import os +import re +import subprocess +import sys + +from setuptools.command import sdist + + +def parse_mailmap(mailmap='.mailmap'): + mapping = {} + if os.path.exists(mailmap): + fp = open(mailmap, 'r') + for l in fp: + l = l.strip() + if not l.startswith('#') and ' ' in l: + canonical_email, alias = [x for x in l.split(' ') + if x.startswith('<')] + mapping[alias] = canonical_email + return mapping + + +def canonicalize_emails(changelog, mapping): + """Takes in a string and an email alias mapping and replaces all + instances of the aliases in the string with their real email. + """ + for alias, email in mapping.iteritems(): + changelog = changelog.replace(alias, email) + return changelog + + +# Get requirements from the first file that exists +def get_reqs_from_files(requirements_files): + for requirements_file in requirements_files: + if os.path.exists(requirements_file): + return open(requirements_file, 'r').read().split('\n') + return [] + + +def parse_requirements(requirements_files=['requirements.txt', + 'tools/pip-requires']): + requirements = [] + for line in get_reqs_from_files(requirements_files): + # For the requirements list, we need to inject only the portion + # after egg= so that distutils knows the package it's looking for + # such as: + # -e git://github.com/openstack/nova/master#egg=nova + if re.match(r'\s*-e\s+', line): + requirements.append(re.sub(r'\s*-e\s+.*#egg=(.*)$', r'\1', + line)) + # such as: + # http://github.com/openstack/nova/zipball/master#egg=nova + elif re.match(r'\s*https?:', line): + requirements.append(re.sub(r'\s*https?:.*#egg=(.*)$', r'\1', + line)) + # -f lines are for index locations, and don't get used here + elif re.match(r'\s*-f\s+', line): + pass + # argparse is part of the standard library starting with 2.7 + # adding it to the requirements list screws distro installs + elif line == 'argparse' and sys.version_info >= (2, 7): + pass + else: + requirements.append(line) + + return requirements + + +def parse_dependency_links(requirements_files=['requirements.txt', + 'tools/pip-requires']): + dependency_links = [] + # dependency_links inject alternate locations to find packages listed + # in requirements + for line in get_reqs_from_files(requirements_files): + # skip comments and blank lines + if re.match(r'(\s*#)|(\s*$)', line): + continue + # lines with -e or -f need the whole line, minus the flag + if re.match(r'\s*-[ef]\s+', line): + dependency_links.append(re.sub(r'\s*-[ef]\s+', '', line)) + # lines that are only urls can go in unmolested + elif re.match(r'\s*https?:', line): + dependency_links.append(line) + return dependency_links + + +def write_requirements(): + venv = os.environ.get('VIRTUAL_ENV', None) + if venv is not None: + with open("requirements.txt", "w") as req_file: + output = subprocess.Popen(["pip", "-E", venv, "freeze", "-l"], + stdout=subprocess.PIPE) + requirements = output.communicate()[0].strip() + req_file.write(requirements) + + +def _run_shell_command(cmd): + output = subprocess.Popen(["/bin/sh", "-c", cmd], + stdout=subprocess.PIPE) + out = output.communicate() + if len(out) == 0: + return None + if len(out[0].strip()) == 0: + return None + return out[0].strip() + + +def _get_git_next_version_suffix(branch_name): + datestamp = datetime.datetime.now().strftime('%Y%m%d') + if branch_name == 'milestone-proposed': + revno_prefix = "r" + else: + revno_prefix = "" + _run_shell_command("git fetch origin +refs/meta/*:refs/remotes/meta/*") + milestone_cmd = "git show meta/openstack/release:%s" % branch_name + milestonever = _run_shell_command(milestone_cmd) + if not milestonever: + milestonever = "" + post_version = _get_git_post_version() + # post version should look like: + # 0.1.1.4.gcc9e28a + # where the bit after the last . is the short sha, and the bit between + # the last and second to last is the revno count + (revno, sha) = post_version.split(".")[-2:] + first_half = "%s~%s" % (milestonever, datestamp) + second_half = "%s%s.%s" % (revno_prefix, revno, sha) + return ".".join((first_half, second_half)) + + +def _get_git_current_tag(): + return _run_shell_command("git tag --contains HEAD") + + +def _get_git_tag_info(): + return _run_shell_command("git describe --tags") + + +def _get_git_post_version(): + current_tag = _get_git_current_tag() + if current_tag is not None: + return current_tag + else: + tag_info = _get_git_tag_info() + if tag_info is None: + base_version = "0.0" + cmd = "git --no-pager log --oneline" + out = _run_shell_command(cmd) + revno = len(out.split("\n")) + sha = _run_shell_command("git describe --always") + else: + tag_infos = tag_info.split("-") + base_version = "-".join(tag_infos[:-2]) + (revno, sha) = tag_infos[-2:] + return "%s.%s.%s" % (base_version, revno, sha) + + +def write_git_changelog(): + """Write a changelog based on the git changelog.""" + new_changelog = 'ChangeLog' + if not os.getenv('SKIP_WRITE_GIT_CHANGELOG'): + if os.path.isdir('.git'): + git_log_cmd = 'git log --stat' + changelog = _run_shell_command(git_log_cmd) + mailmap = parse_mailmap() + with open(new_changelog, "w") as changelog_file: + changelog_file.write(canonicalize_emails(changelog, mailmap)) + else: + open(new_changelog, 'w').close() + + +def generate_authors(): + """Create AUTHORS file using git commits.""" + jenkins_email = 'jenkins@review.(openstack|stackforge).org' + old_authors = 'AUTHORS.in' + new_authors = 'AUTHORS' + if not os.getenv('SKIP_GENERATE_AUTHORS'): + if os.path.isdir('.git'): + # don't include jenkins email address in AUTHORS file + git_log_cmd = ("git log --format='%aN <%aE>' | sort -u | " + "egrep -v '" + jenkins_email + "'") + changelog = _run_shell_command(git_log_cmd) + mailmap = parse_mailmap() + with open(new_authors, 'w') as new_authors_fh: + new_authors_fh.write(canonicalize_emails(changelog, mailmap)) + if os.path.exists(old_authors): + with open(old_authors, "r") as old_authors_fh: + new_authors_fh.write('\n' + old_authors_fh.read()) + else: + open(new_authors, 'w').close() + + +_rst_template = """%(heading)s +%(underline)s + +.. automodule:: %(module)s + :members: + :undoc-members: + :show-inheritance: +""" + + +def read_versioninfo(project): + """Read the versioninfo file. If it doesn't exist, we're in a github + zipball, and there's really no way to know what version we really + are, but that should be ok, because the utility of that should be + just about nil if this code path is in use in the first place.""" + versioninfo_path = os.path.join(project, 'versioninfo') + if os.path.exists(versioninfo_path): + with open(versioninfo_path, 'r') as vinfo: + version = vinfo.read().strip() + else: + version = "0.0.0" + return version + + +def write_versioninfo(project, version): + """Write a simple file containing the version of the package.""" + open(os.path.join(project, 'versioninfo'), 'w').write("%s\n" % version) + + +def get_cmdclass(): + """Return dict of commands to run from setup.py.""" + + cmdclass = dict() + + def _find_modules(arg, dirname, files): + for filename in files: + if filename.endswith('.py') and filename != '__init__.py': + arg["%s.%s" % (dirname.replace('/', '.'), + filename[:-3])] = True + + class LocalSDist(sdist.sdist): + """Builds the ChangeLog and Authors files from VC first.""" + + def run(self): + write_git_changelog() + generate_authors() + # sdist.sdist is an old style class, can't use super() + sdist.sdist.run(self) + + cmdclass['sdist'] = LocalSDist + + # If Sphinx is installed on the box running setup.py, + # enable setup.py to build the documentation, otherwise, + # just ignore it + try: + from sphinx.setup_command import BuildDoc + + class LocalBuildDoc(BuildDoc): + def generate_autoindex(self): + print "**Autodocumenting from %s" % os.path.abspath(os.curdir) + modules = {} + option_dict = self.distribution.get_option_dict('build_sphinx') + source_dir = os.path.join(option_dict['source_dir'][1], 'api') + if not os.path.exists(source_dir): + os.makedirs(source_dir) + for pkg in self.distribution.packages: + if '.' not in pkg: + os.path.walk(pkg, _find_modules, modules) + module_list = modules.keys() + module_list.sort() + autoindex_filename = os.path.join(source_dir, 'autoindex.rst') + with open(autoindex_filename, 'w') as autoindex: + autoindex.write(""".. toctree:: + :maxdepth: 1 + +""") + for module in module_list: + output_filename = os.path.join(source_dir, + "%s.rst" % module) + heading = "The :mod:`%s` Module" % module + underline = "=" * len(heading) + values = dict(module=module, heading=heading, + underline=underline) + + print "Generating %s" % output_filename + with open(output_filename, 'w') as output_file: + output_file.write(_rst_template % values) + autoindex.write(" %s.rst\n" % module) + + def run(self): + if not os.getenv('SPHINX_DEBUG'): + self.generate_autoindex() + + for builder in ['html', 'man']: + self.builder = builder + self.finalize_options() + self.project = self.distribution.get_name() + self.version = self.distribution.get_version() + self.release = self.distribution.get_version() + BuildDoc.run(self) + cmdclass['build_sphinx'] = LocalBuildDoc + except ImportError: + pass + + return cmdclass + + +def get_git_branchname(): + for branch in _run_shell_command("git branch --color=never").split("\n"): + if branch.startswith('*'): + _branch_name = branch.split()[1].strip() + if _branch_name == "(no": + _branch_name = "no-branch" + return _branch_name + + +def get_pre_version(projectname, base_version): + """Return a version which is leading up to a version that will + be released in the future.""" + if os.path.isdir('.git'): + current_tag = _get_git_current_tag() + if current_tag is not None: + version = current_tag + else: + branch_name = os.getenv('BRANCHNAME', + os.getenv('GERRIT_REFNAME', + get_git_branchname())) + version_suffix = _get_git_next_version_suffix(branch_name) + version = "%s~%s" % (base_version, version_suffix) + write_versioninfo(projectname, version) + return version + else: + version = read_versioninfo(projectname) + return version + + +def get_post_version(projectname): + """Return a version which is equal to the tag that's on the current + revision if there is one, or tag plus number of additional revisions + if the current revision has no tag.""" + + if os.path.isdir('.git'): + version = _get_git_post_version() + write_versioninfo(projectname, version) + return version + return read_versioninfo(projectname) diff --git a/heatclient/openstack/common/setup.pyc b/heatclient/openstack/common/setup.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d8a45847bab4e007190100a650f7c07da21302b4 GIT binary patch literal 13115 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 literal 0 HcmV?d00001 diff --git a/heatclient/openstack/common/version.py b/heatclient/openstack/common/version.py new file mode 100644 index 00000000..3be64f65 --- /dev/null +++ b/heatclient/openstack/common/version.py @@ -0,0 +1,148 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC +# +# 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. + +""" +Utilities for consuming the auto-generated versioninfo files. +""" + +import datetime +import pkg_resources + +import setup + + +class _deferred_version_string(object): + """Internal helper class which provides delayed version calculation.""" + def __init__(self, version_info, prefix): + self.version_info = version_info + self.prefix = prefix + + def __str__(self): + return "%s%s" % (self.prefix, self.version_info.version_string()) + + def __repr__(self): + return "%s%s" % (self.prefix, self.version_info.version_string()) + + +class VersionInfo(object): + + def __init__(self, package, python_package=None, pre_version=None): + """Object that understands versioning for a package + :param package: name of the top level python namespace. For heat, + this would be "heat" for python-heatclient, it + would be "heatclient" + :param python_package: optional name of the project name. For + heat this can be left unset. For + python-heatclient, this would be + "python-heatclient" + :param pre_version: optional version that the project is working to + """ + self.package = package + if python_package is None: + self.python_package = package + else: + self.python_package = python_package + self.pre_version = pre_version + self.version = None + + def _generate_version(self): + """Defer to the openstack.common.setup routines for making a + version from git.""" + if self.pre_version is None: + return setup.get_post_version(self.python_package) + else: + return setup.get_pre_version(self.python_package, self.pre_version) + + def _newer_version(self, pending_version): + """Check to see if we're working with a stale version or not. + We expect a version string that either looks like: + 2012.2~f3~20120708.10.4426392 + which is an unreleased version of a pre-version, or: + 0.1.1.4.gcc9e28a + which is an unreleased version of a post-version, or: + 0.1.1 + Which is a release and which should match tag. + For now, if we have a date-embedded version, check to see if it's + old, and if so re-generate. Otherwise, just deal with it. + """ + try: + version_date = int(self.version.split("~")[-1].split('.')[0]) + if version_date < int(datetime.date.today().strftime('%Y%m%d')): + return self._generate_version() + else: + return pending_version + except Exception: + return pending_version + + def version_string_with_vcs(self, always=False): + """Return the full version of the package including suffixes indicating + VCS status. + + For instance, if we are working towards the 2012.2 release, + canonical_version_string should return 2012.2 if this is a final + release, or else something like 2012.2~f1~20120705.20 if it's not. + + :param always: if true, skip all version caching + """ + if always: + self.version = self._generate_version() + + if self.version is None: + + requirement = pkg_resources.Requirement.parse(self.python_package) + versioninfo = "%s/versioninfo" % self.package + try: + raw_version = pkg_resources.resource_string(requirement, + versioninfo) + self.version = self._newer_version(raw_version.strip()) + except (IOError, pkg_resources.DistributionNotFound): + self.version = self._generate_version() + + return self.version + + def canonical_version_string(self, always=False): + """Return the simple version of the package excluding any suffixes. + + For instance, if we are working towards the 2012.2 release, + canonical_version_string should return 2012.2 in all cases. + + :param always: if true, skip all version caching + """ + return self.version_string_with_vcs(always).split('~')[0] + + def version_string(self, always=False): + """Return the base version of the package. + + For instance, if we are working towards the 2012.2 release, + version_string should return 2012.2 if this is a final release, or + 2012.2-dev if it is not. + + :param always: if true, skip all version caching + """ + version_parts = self.version_string_with_vcs(always).split('~') + if len(version_parts) == 1: + return version_parts[0] + else: + return '%s-dev' % (version_parts[0],) + + def deferred_version_string(self, prefix=""): + """Generate an object which will expand in a string context to + the results of version_string(). We do this so that don't + call into pkg_resources every time we start up a program when + passing version information into the CONF constructor, but + rather only do the calculation when and if a version is requested + """ + return _deferred_version_string(self, prefix) diff --git a/heatclient/openstack/common/version.pyc b/heatclient/openstack/common/version.pyc new file mode 100644 index 0000000000000000000000000000000000000000..308875e1daa0abbf28784e6b09ff853c37facd82 GIT binary patch literal 6640 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 JvewwR`cDN&wRivk literal 0 HcmV?d00001 diff --git a/heatclient/shell.py b/heatclient/shell.py new file mode 100644 index 00000000..1b657ff8 --- /dev/null +++ b/heatclient/shell.py @@ -0,0 +1,348 @@ +# 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 OpenStack Images API. +""" + +import argparse +import logging +import re +import sys + +from keystoneclient.v2_0 import client as ksclient + +from heatclient import exc +from heatclient import client as heatclient +from heatclient.common import utils + + +class HeatShell(object): + + def get_base_parser(self): + parser = argparse.ArgumentParser( + prog='heat', + description=__doc__.strip(), + epilog='See "heat help COMMAND" ' + 'for help on a specific command.', + add_help=False, + formatter_class=HelpFormatter, + ) + + # Global arguments + parser.add_argument('-h', '--help', + action='store_true', + help=argparse.SUPPRESS, + ) + + parser.add_argument('-d', '--debug', + default=bool(utils.env('HEATCLIENT_DEBUG')), + action='store_true', + help='Defaults to env[HEATCLIENT_DEBUG]') + + parser.add_argument('-v', '--verbose', + default=False, action="store_true", + help="Print more verbose output") + + parser.add_argument('-k', '--insecure', + default=False, + action='store_true', + help="Explicitly allow glanceclient to perform \"insecure\" " + "SSL (https) requests. The server's certificate will " + "not be verified against any certificate authorities. " + "This option should be used with caution.") + + parser.add_argument('--cert-file', + help='Path of certificate file to use in SSL connection. This ' + 'file can optionally be prepended with the private key.') + + parser.add_argument('--key-file', + help='Path of client key to use in SSL connection. This option is ' + 'not necessary if your key is prepended to your cert file.') + + parser.add_argument('--ca-file', + help='Path of CA SSL certificate(s) used to verify the remote ' + 'server\'s certificate. Without this option glance looks ' + 'for the default system CA certificates.') + + parser.add_argument('--timeout', + default=600, + help='Number of seconds to wait for a response') + + parser.add_argument('--os-username', + default=utils.env('OS_USERNAME'), + help='Defaults to env[OS_USERNAME]') + + parser.add_argument('--os_username', + help=argparse.SUPPRESS) + + parser.add_argument('--os-password', + default=utils.env('OS_PASSWORD'), + help='Defaults to env[OS_PASSWORD]') + + parser.add_argument('--os_password', + help=argparse.SUPPRESS) + + parser.add_argument('--os-tenant-id', + default=utils.env('OS_TENANT_ID'), + help='Defaults to env[OS_TENANT_ID]') + + parser.add_argument('--os_tenant_id', + help=argparse.SUPPRESS) + + parser.add_argument('--os-tenant-name', + default=utils.env('OS_TENANT_NAME'), + help='Defaults to env[OS_TENANT_NAME]') + + parser.add_argument('--os_tenant_name', + help=argparse.SUPPRESS) + + parser.add_argument('--os-auth-url', + default=utils.env('OS_AUTH_URL'), + help='Defaults to env[OS_AUTH_URL]') + + parser.add_argument('--os_auth_url', + help=argparse.SUPPRESS) + + parser.add_argument('--os-region-name', + default=utils.env('OS_REGION_NAME'), + help='Defaults to env[OS_REGION_NAME]') + + parser.add_argument('--os_region_name', + help=argparse.SUPPRESS) + + parser.add_argument('--os-auth-token', + default=utils.env('OS_AUTH_TOKEN'), + help='Defaults to env[OS_AUTH_TOKEN]') + + parser.add_argument('--os_auth_token', + help=argparse.SUPPRESS) + + parser.add_argument('--heat-url', + default=utils.env('HEAT_URL'), + help='Defaults to env[HEAT_URL]') + + parser.add_argument('--heat_url', + help=argparse.SUPPRESS) + + parser.add_argument('--heat-api-version', + default=utils.env('HEAT_API_VERSION', default='1'), + help='Defaults to env[HEAT_API_VERSION] or 1') + + parser.add_argument('--heat_api_version', + help=argparse.SUPPRESS) + + parser.add_argument('--os-service-type', + default=utils.env('OS_SERVICE_TYPE'), + help='Defaults to env[OS_SERVICE_TYPE]') + + parser.add_argument('--os_service_type', + help=argparse.SUPPRESS) + + parser.add_argument('--os-endpoint-type', + default=utils.env('OS_ENDPOINT_TYPE'), + help='Defaults to env[OS_ENDPOINT_TYPE]') + + parser.add_argument('--os_endpoint_type', + help=argparse.SUPPRESS) + + return parser + + def get_subcommand_parser(self, version): + parser = self.get_base_parser() + + self.subcommands = {} + subparsers = parser.add_subparsers(metavar='') + submodule = utils.import_versioned_module(version, 'shell') + self._find_actions(subparsers, submodule) + self._find_actions(subparsers, self) + + return parser + + def _find_actions(self, subparsers, actions_module): + for attr in (a for a in dir(actions_module) if a.startswith('do_')): + # I prefer to be hypen-separated instead of underscores. + command = attr[3:].replace('_', '-') + callback = getattr(actions_module, attr) + desc = callback.__doc__ or '' + help = desc.strip().split('\n')[0] + arguments = getattr(callback, 'arguments', []) + + subparser = subparsers.add_parser(command, + help=help, + description=desc, + add_help=False, + formatter_class=HelpFormatter + ) + subparser.add_argument('-h', '--help', + action='help', + help=argparse.SUPPRESS, + ) + self.subcommands[command] = subparser + for (args, kwargs) in arguments: + subparser.add_argument(*args, **kwargs) + subparser.set_defaults(func=callback) + + # TODO(dtroyer): move this into the common client support? + # Compatibility check to remove API version as the trailing component + # in a service endpoint; also removes a trailing '/' + def _strip_version(self, endpoint): + """Strip a version from the last component of an endpoint if present""" + + # Get rid of trailing '/' if present + if endpoint.endswith('/'): + endpoint = endpoint[:-1] + url_bits = endpoint.split('/') + # regex to match 'v1' or 'v2.0' etc + if re.match('v\d+\.?\d*', url_bits[-1]): + endpoint = '/'.join(url_bits[:-1]) + return endpoint + + def _get_ksclient(self, **kwargs): + """Get an endpoint and auth token from Keystone. + + :param username: name of user + :param password: user's password + :param tenant_id: unique identifier of tenant + :param tenant_name: name of tenant + :param auth_url: endpoint to authenticate against + """ + return ksclient.Client(username=kwargs.get('username'), + password=kwargs.get('password'), + tenant_id=kwargs.get('tenant_id'), + tenant_name=kwargs.get('tenant_name'), + auth_url=kwargs.get('auth_url'), + insecure=kwargs.get('insecure')) + + def _get_endpoint(self, client, **kwargs): + """Get an endpoint using the provided keystone client.""" + endpoint = client.service_catalog.url_for( + service_type=kwargs.get('service_type') or 'orchestration', + endpoint_type=kwargs.get('endpoint_type') or 'publicURL') + return self._strip_version(endpoint) + + def main(self, argv): + # Parse args once to find version + parser = self.get_base_parser() + (options, args) = parser.parse_known_args(argv) + + # build available subcommands based on version + api_version = options.heat_api_version + subcommand_parser = self.get_subcommand_parser(api_version) + self.parser = subcommand_parser + + # Handle top-level --help/-h before attempting to parse + # a command off the command line + if options.help or not argv: + self.do_help(options) + return 0 + + # Parse args again and call whatever callback was selected + args = subcommand_parser.parse_args(argv) + + # Short-circuit and deal with help command right away. + if args.func == self.do_help: + self.do_help(args) + return 0 + + LOG = logging.getLogger('heatclient') + LOG.addHandler(logging.StreamHandler()) + LOG.setLevel(logging.DEBUG if args.debug else logging.INFO) + + heat_url = args.heat_url + auth_reqd = (utils.is_authentication_required(args.func) and + not (args.os_auth_token and heat_url)) + + if not auth_reqd: + endpoint = heat_url + token = args.os_auth_token + else: + if not args.os_username: + raise exc.CommandError("You must provide a username via" + " either --os-username or env[OS_USERNAME]") + + if not args.os_password: + raise exc.CommandError("You must provide a password via" + " either --os-password or env[OS_PASSWORD]") + + if not (args.os_tenant_id or args.os_tenant_name): + raise exc.CommandError("You must provide a tenant_id via" + " either --os-tenant-id or via env[OS_TENANT_ID]") + + if not args.os_auth_url: + raise exc.CommandError("You must provide an auth url via" + " either --os-auth-url or via env[OS_AUTH_URL]") + kwargs = { + 'username': args.os_username, + 'password': args.os_password, + 'tenant_id': args.os_tenant_id, + 'tenant_name': args.os_tenant_name, + 'auth_url': args.os_auth_url, + 'service_type': args.os_service_type, + 'endpoint_type': args.os_endpoint_type, + 'insecure': args.insecure + } + _ksclient = self._get_ksclient(**kwargs) + token = args.os_auth_token or _ksclient.auth_token + + endpoint = args.heat_url or \ + self._get_endpoint(_ksclient, **kwargs) + + kwargs = { + 'token': token, + 'insecure': args.insecure, + 'timeout': args.timeout, + 'ca_file': args.ca_file, + 'cert_file': args.cert_file, + 'key_file': args.key_file, + } + + client = heatclient.Client(api_version, endpoint, **kwargs) + + try: + args.func(client, args) + except exc.Unauthorized: + raise exc.CommandError("Invalid OpenStack Identity credentials.") + + @utils.arg('command', metavar='', nargs='?', + help='Display help for ') + def do_help(self, args): + """ + Display help about this program or one of its subcommands. + """ + if getattr(args, 'command', None): + if args.command in self.subcommands: + self.subcommands[args.command].print_help() + else: + raise exc.CommandError("'%s' is not a valid subcommand" % + args.command) + else: + self.parser.print_help() + + +class HelpFormatter(argparse.HelpFormatter): + def start_section(self, heading): + # Title-case the headings + heading = '%s%s' % (heading[0].upper(), heading[1:]) + super(HelpFormatter, self).start_section(heading) + + +def main(): + try: + HeatShell().main(sys.argv[1:]) + + except Exception, e: + print >> sys.stderr, e + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/heatclient/v1/__init__.py b/heatclient/v1/__init__.py new file mode 100644 index 00000000..d0322d27 --- /dev/null +++ b/heatclient/v1/__init__.py @@ -0,0 +1,16 @@ +# 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 heatclient.v1.client import Client diff --git a/heatclient/v1/__init__.pyc b/heatclient/v1/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c531029ea4e248262e2ffce1478ab9a7726c6c8a GIT binary patch literal 225 zcmY*SO9}!p3{7W1L=aqfh;E!PdlA8fI}zQvDPvohVmlQY3tq-cdH~I+poZk-<>go2 z$D>}AOf_h6HP84Va#-r0^evR|ftv9%59mssVx zdb1ttApaq)xyCjIi-~NuM>IC0kGk=R8hyg^mDa_y2F(GXdj4CY$dWlTpwq6rEi=9-Pc7rqj zEPnvJ=TzM$O`O28U*5Cd$M1b4_;olem6>L+TLFHL@tC(ra@-yi0Hp^D4?L26D10ab zSOh2sAUwG2!P0}g2R{J51AJZdA@9R19H0mJ8&~KdXM0H2)>fTFkH+$BV=S?!g%rx} zQINrV(s+SH00qa`3!mU%6pWqBLZ1|lQMEH>OI@ zY%SG_36)n`Dm!5(tTCcKGEG%s{Cxkyz9b*_?$+qec_B}aEp*Y!Ohk`_vU(%bgw5&W z9P$PcDl0Q?rB)A25Rq63OhJ}HH-xSz^wzMfF;sO9wFp3{K3vTH|# zhU{EPD|sPr1mjGJE$a~OD|){{>>}(_Fe2CoQ76<^dw?WOB|1ve#Es8WyqVF!eBz2@ zok9ZZhRTRy=@?$Gx@p>Mi49Q!95Ek|i`Tjov9Y2R-{M@faiKF_xEYMAYrEF!h+5rY zjqkDTIPNZ^>N=q*euc*n1O7`dBxZ+K@lvFz9pX)zmO5_=5}!CR*BPcl%6GW_H8?8W zb36{*;9%fixS!BI!@IkLqD+^iRta8$J$(4oj=O483TN~A^wiCk>&btE?TYuNE7!Xb L26pgA-YED30bC@F literal 0 HcmV?d00001 diff --git a/heatclient/v1/shell.py b/heatclient/v1/shell.py new file mode 100644 index 00000000..33aec66a --- /dev/null +++ b/heatclient/v1/shell.py @@ -0,0 +1,29 @@ +# 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. + +import argparse +import copy +import os +import sys + +from heatclient.common import utils + + +def do_list(hc, args): + print args + kwargs = {'tenant_id': args.os_tenant_name} + stacks = hc.stacks.list(**kwargs) + columns = ['ID', 'Name', 'Status'] + utils.print_list(stacks, columns) diff --git a/heatclient/v1/stacks.py b/heatclient/v1/stacks.py new file mode 100644 index 00000000..9a2fda05 --- /dev/null +++ b/heatclient/v1/stacks.py @@ -0,0 +1,198 @@ +# 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. + +import copy +import errno +import json +import os +import urllib + +from heatclient.common import base +from heatclient.common import utils + +UPDATE_PARAMS = ('name', 'disk_format', 'container_format', 'min_disk', + 'min_ram', 'owner', 'size', 'is_public', 'protected', + 'location', 'checksum', 'copy_from', 'properties', + #NOTE(bcwaldon: an attempt to update 'deleted' will be + # ignored, but we need to support it for backwards- + # compatibility with the legacy client library + 'deleted') + +CREATE_PARAMS = UPDATE_PARAMS + ('id',) + +DEFAULT_PAGE_SIZE = 20 + + +class Stack(base.Resource): + def __repr__(self): + return "" % self._info + + def update(self, **fields): + self.manager.update(self, **fields) + + def delete(self): + return self.manager.delete(self) + + def data(self, **kwargs): + return self.manager.data(self, **kwargs) + + +class StackManager(base.Manager): + resource_class = Stack + +# def get(self, image_id): +# """Get the metadata for a specific stack. +# +# :param image: image object or id to look up +# :rtype: :class:`Image` +# """ +# resp, body = self.api.raw_request('HEAD', '/v1/images/%s' % image_id) +# meta = self._image_meta_from_headers(dict(resp.getheaders())) +# return Stack(self, meta) +# +# def data(self, image, do_checksum=True): +# """Get the raw data for a specific image. +# +# :param image: image object or id to look up +# :param do_checksum: Enable/disable checksum validation +# :rtype: iterable containing image data +# """ +# image_id = base.getid(image) +# resp, body = self.api.raw_request('GET', '/v1/images/%s' % image_id) +# checksum = resp.getheader('x-image-meta-checksum', None) +# if do_checksum and checksum is not None: +# return utils.integrity_iter(body, checksum) +# else: +# return body + + def list(self, **kwargs): + """Get a list of stacks. + + :param page_size: number of items to request in each paginated request + :param limit: maximum number of stacks to return + :param marker: begin returning stacks that appear later in the stack + list than that represented by this stack id + :param filters: dict of direct comparison filters that mimics the + structure of a stack object + :rtype: list of :class:`Stack` + """ + print kwargs + absolute_limit = kwargs.get('limit') + + def paginate(qp, seen=0): + url = '/stacks?%s' % urllib.urlencode(qp) + + stacks = self._list(url, "stacks") + for stack in stacks: + seen += 1 + if absolute_limit is not None and seen > absolute_limit: + return + yield stack + + page_size = qp.get('limit') + if (page_size and len(stacks) == page_size and + (absolute_limit is None or 0 < seen < absolute_limit)): + qp['marker'] = stack.id + for image in paginate(qp, seen): + yield image + + params = {'limit': kwargs.get('page_size', DEFAULT_PAGE_SIZE)} + + if 'marker' in kwargs: + params['marker'] = kwargs['marker'] + + filters = kwargs.get('filters', {}) + properties = filters.pop('properties', {}) + for key, value in properties.items(): + params['property-%s' % key] = value + params.update(filters) + + return paginate(params) + +# def delete(self, image): +# """Delete an image.""" +# self._delete("/v1/images/%s" % base.getid(image)) +# +# def create(self, **kwargs): +# """Create an image +# +# TODO(bcwaldon): document accepted params +# """ +# image_data = kwargs.pop('data', None) +# if image_data is not None: +# image_size = self._get_file_size(image_data) +# if image_size != 0: +# kwargs.setdefault('size', image_size) +# else: +# image_data = None +# +# fields = {} +# for field in kwargs: +# if field in CREATE_PARAMS: +# fields[field] = kwargs[field] +# else: +# msg = 'create() got an unexpected keyword argument \'%s\'' +# raise TypeError(msg % field) +# +# copy_from = fields.pop('copy_from', None) +# hdrs = self._image_meta_to_headers(fields) +# if copy_from is not None: +# hdrs['x-heat-api-copy-from'] = copy_from +# +# resp, body_iter = self.api.raw_request( +# 'POST', '/v1/images', headers=hdrs, body=image_data) +# body = json.loads(''.join([c for c in body_iter])) +# return Stack(self, self._format_image_meta_for_user(body['image'])) +# +# def update(self, image, **kwargs): +# """Update an image +# +# TODO(bcwaldon): document accepted params +# """ +# hdrs = {} +# image_data = kwargs.pop('data', None) +# if image_data is not None: +# image_size = self._get_file_size(image_data) +# if image_size != 0: +# kwargs.setdefault('size', image_size) +# hdrs['Content-Length'] = image_size +# else: +# image_data = None +# +# try: +# purge_props = 'true' if kwargs.pop('purge_props') else 'false' +# except KeyError: +# pass +# else: +# hdrs['x-heat-registry-purge-props'] = purge_props +# +# fields = {} +# for field in kwargs: +# if field in UPDATE_PARAMS: +# fields[field] = kwargs[field] +# else: +# msg = 'update() got an unexpected keyword argument \'%s\'' +# raise TypeError(msg % field) +# +# copy_from = fields.pop('copy_from', None) +# hdrs.update(self._image_meta_to_headers(fields)) +# if copy_from is not None: +# hdrs['x-heat-api-copy-from'] = copy_from +# +# url = '/v1/images/%s' % base.getid(image) +# resp, body_iter = self.api.raw_request( +# 'PUT', url, headers=hdrs, body=image_data) +# body = json.loads(''.join([c for c in body_iter])) +# return Stack(self, self._format_image_meta_for_user(body['image'])) diff --git a/heatclient/v1/stacks.pyc b/heatclient/v1/stacks.pyc new file mode 100644 index 0000000000000000000000000000000000000000..71efc75d3d844a2ba5db56b1def132b05881dd9a GIT binary patch literal 7915 zcmcIpTXP&o74Df`t+cvWz9fp3Ks*mP-h{|XAVr7}35vBbCXOs-<=7E+CbKgwX|%gD ztLa%=a!C}G7;GNk5&i;IP!(SI1w2zl<&7$;^1x5v2f+88-is3RhGltr`!?ODyT9|D zb9zeuo|!a{%zC8y|4!leM|jL1ki_`!sJ2p@!@Q$5ok6{;Hr+wKq&7=~d|7RlCGV>C znCev2W<@z1t0d)dwK5AH&Qd^kiS@lq<&y@OT zb6Ul7(l8?p&kY++QNTv+c?g^Q2mjV;4zfiVNi#Ismc~%(6-nDjeHwOjF@>r)F|A-L z%R6CFoJKjy(jrV!oewG}P}xb+fE|nRejyJ#1qaOTqo)jLk_Wn|qAD>#x7TbZk(9c5 zR_LhEalvugSrirtW?&)O(NW9vI?^x7x(C5lo^^^zG~ul-#!a-58Drhn9Kz-BaqrQRaVP8OMN{Z|y15wZ zdyCu+90%60*gYtAvUFibhegy*bXqJP%ien(Z=sjDjBebyAjow$4}wd`8;YeI5ue!- zp7-&X=a5+0cV!WWj{1!w2yBR$YqB7FqG5{lIKAgw~K_IFZ1f4AIwNdn`XL3G1SMBrt(Vj}~ z>EYTxgVA5YVdHXt z#|K&uzAcOTgK@_s1JbQXW{QMBnu|CEIgTeKgm4v^;n9Loz$G}{xzAj6$5lu8Uy^25 z709@1w@lgwT=D;?Ovb{?hp2P`V5+G>v%0Dauh`KZ=r|-EJaDlWdZw$RWGjh0(frGk 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 Gf9k(7Xx+&G literal 0 HcmV?d00001 diff --git a/heatclient/version.py b/heatclient/version.py new file mode 100644 index 00000000..a9498ad1 --- /dev/null +++ b/heatclient/version.py @@ -0,0 +1,19 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +from heatclient.openstack.common import version as common_version + +version_info = common_version.VersionInfo('heatclient', + python_package='python-heatclient') diff --git a/heatclient/version.pyc b/heatclient/version.pyc new file mode 100644 index 0000000000000000000000000000000000000000..39ac4833ae14a5a600c345361fe205cd4b6e4918 GIT binary patch literal 362 zcmY*VO-sZu5S?_pD(foi**{PZHh(}w@Z?2AVGq5OnvAyEB*lyc&;Df3qHnj=>X5w5 zy!m=$_HnTIEaHu!(Neq@xbXyFaTK*gPEnDN;)I@NWD~N$l-`vpQnGWp%XU!Re8DmW zPj&%4zr-eZS-`Kk64N&9MGg^mZ&C+ec9nUq9yuQUQm0elbv6er$N#$X(6Nt#zT5=o zg1<$Ywljyg{Mot>emy9-@()3lh{s&nfXFtD6U0k|x(<$Y6z=(6TkdrmOx14J=|S=z deR`6r$H6&@_Auwn1^e3a`3ipAeCR4!s&9xAVJ83p literal 0 HcmV?d00001 diff --git a/heatclient/versioninfo b/heatclient/versioninfo new file mode 100644 index 00000000..6e8bf73a --- /dev/null +++ b/heatclient/versioninfo @@ -0,0 +1 @@ +0.1.0 diff --git a/openstack-common.conf b/openstack-common.conf new file mode 100644 index 00000000..01784325 --- /dev/null +++ b/openstack-common.conf @@ -0,0 +1,7 @@ +[DEFAULT] + +# The list of modules to copy from openstack-common +modules=setup,importutils,version + +# The base module to hold the copy of openstack.common +base=heatclient diff --git a/run_tests.sh b/run_tests.sh new file mode 100755 index 00000000..6bee8057 --- /dev/null +++ b/run_tests.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +function usage { + echo "Usage: $0 [OPTION]..." + echo "Run python-heatclient's test suite(s)" + echo "" + echo " -p, --pep8 Just run pep8" + echo " -h, --help Print this usage message" + echo "" + echo "This script is deprecated and currently retained for compatibility." + echo 'You can run the full test suite for multiple environments by running "tox".' + echo 'You can run tests for only python 2.7 by running "tox -e py27", or run only' + echo 'the pep8 tests with "tox -e pep8".' + exit +} + +command -v tox > /dev/null 2>&1 +if [ $? -ne 0 ]; then + echo 'This script requires "tox" to run.' + echo 'You can install it with "pip install tox".' + exit 1; +fi + +just_pep8=0 + +function process_option { + case "$1" in + -h|--help) usage;; + -p|--pep8) let just_pep8=1;; + esac +} + +for arg in "$@"; do + process_option $arg +done + +if [ $just_pep8 -eq 1 ]; then + tox -e pep8 + exit +fi + +tox -e py27 $toxargs 2>&1 | tee run_tests.err.log || exit +if [ ${PIPESTATUS[0]} -ne 0 ]; then + exit ${PIPESTATUS[0]} +fi + +if [ -z "$toxargs" ]; then + tox -e pep8 +fi diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..ff57e674 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,15 @@ +[nosetests] +cover-package = heatclient +cover-html = true +cover-erase = true +cover-inclusive = true +verbosity=2 +detailed-errors=1 + +[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 new file mode 100644 index 00000000..821eaa12 --- /dev/null +++ b/setup.py @@ -0,0 +1,47 @@ +import os +import sys + +import setuptools + +from heatclient.openstack.common import setup + + +requires = setup.parse_requirements() +dependency_links = setup.parse_dependency_links() +tests_require = setup.parse_requirements(['tools/test-requires']) + +if sys.version_info < (2, 6): + requires.append('simplejson') + + +def read(fname): + return open(os.path.join(os.path.dirname(__file__), fname)).read() + +setuptools.setup( + name="python-heatclient", + version=setup.get_post_version('heatclient'), + description="Client library for Heat orchestration API", + long_description=read('README.rst'), + url='https://github.com/heat-api/python-heatclient', + license='Apache', + author='Heat API Developers', + author_email='discuss@heat-api.org', + packages=setuptools.find_packages(exclude=['tests', 'tests.*']), + include_package_data=True, + classifiers=[ + 'Development Status :: 4 - Beta', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + ], + cmdclass=setup.get_cmdclass(), + install_requires=requires, + dependency_links=dependency_links, + tests_require=tests_require, + setup_requires=['setuptools-git>=0.4'], + test_suite="nose.collector", + entry_points={'console_scripts': ['heat = heatclient.shell:main']}, +) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/__init__.pyc b/tests/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..902c4a4f938b1e9595239da619bc7d82196f1db6 GIT binary patch literal 150 zcmZSn%*(YesW>2+0SXv_v;zCHf_)#U;i1@$s2?nI-Y@dO!sn UKr?J|^HWN5Qtd!C6$3E?0F^i*!2kdN literal 0 HcmV?d00001 diff --git a/tests/test_foo.py b/tests/test_foo.py new file mode 100644 index 00000000..7f0c66d7 --- /dev/null +++ b/tests/test_foo.py @@ -0,0 +1,7 @@ +import unittest + + +class fooTest(unittest.TestCase): + + def test_foo(self): + self.assertTrue(True) diff --git a/tests/test_foo.pyc b/tests/test_foo.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9047d469d7090b76c830c9b81dba0861af7c0e5b GIT binary patch literal 608 zcmcIhOHRW;4D}?apb8e;A}k;mK!E7JLPOwQpzvjztl2hL}CV%w!Tvk10;1?)Ro5~-t|BZnh<>@qQs&fy9X z-soyGv6==80$s?2yE;LYf|FZBX*2emit?79(u%ifHwT9hy8>zg*?aM^I7W!Lhc zsbUBT-eaUI08YSRRm*3H#gvds?B2qr_e0UM_d%Wa{a8&M(#+HywEUT%%-S1w5HgDn gLZ+crQyNs+Yb*-k0RIOWp~>}r*c*GrAm=px24kX%8vp=0.6,<0.7 +python-keystoneclient>=0.1.2,<0.2 +warlock<2 diff --git a/tools/test-requires b/tools/test-requires new file mode 100644 index 00000000..18557f05 --- /dev/null +++ b/tools/test-requires @@ -0,0 +1,11 @@ +distribute>=0.6.24 + +mox +nose +nose-exclude +nosexcover +openstack.nose_plugin +nosehtmloutput +pep8==1.2 +setuptools-git>=0.4 +sphinx>=1.1.2 diff --git a/tools/with_venv.sh b/tools/with_venv.sh new file mode 100755 index 00000000..e6e44f59 --- /dev/null +++ b/tools/with_venv.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +command -v tox > /dev/null 2>&1 +if [ $? -ne 0 ]; then + echo 'This script requires "tox" to run.' + echo 'You can install it with "pip install tox".' + exit 1; +fi + +tox -evenv -- $@ diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..48361a02 --- /dev/null +++ b/tox.ini @@ -0,0 +1,46 @@ +[tox] +envlist = py26,py27,pep8 + +[testenv] +setenv = VIRTUAL_ENV={envdir} + NOSE_WITH_OPENSTACK=1 + NOSE_OPENSTACK_COLOR=1 + NOSE_OPENSTACK_RED=0.05 + NOSE_OPENSTACK_YELLOW=0.025 + NOSE_OPENSTACK_SHOW_ELAPSED=1 +deps = -r{toxinidir}/tools/pip-requires + -r{toxinidir}/tools/test-requires +commands = nosetests + +[testenv:pep8] +deps = pep8==1.1 +commands = pep8 --repeat --show-source heatclient setup.py + +[testenv:venv] +commands = {posargs} + +[testenv:cover] +commands = nosetests --cover-erase --cover-package=heatclient --with-xcoverage + +[tox:jenkins] +downloadcache = ~/cache/pip + +[testenv:jenkins26] +basepython = python2.6 +setenv = NOSE_WITH_XUNIT=1 +deps = file://{toxinidir}/.cache.bundle + +[testenv:jenkins27] +basepython = python2.7 +setenv = NOSE_WITH_XUNIT=1 +deps = file://{toxinidir}/.cache.bundle + +[testenv:jenkinscover] +deps = file://{toxinidir}/.cache.bundle +setenv = NOSE_WITH_XUNIT=1 +commands = nosetests --cover-erase --cover-package=heatclient --with-xcoverage + +[testenv:jenkinsvenv] +deps = file://{toxinidir}/.cache.bundle +setenv = NOSE_WITH_XUNIT=1 +commands = {posargs}