python-glareclient-0.5.2/0000775000175100017510000000000013230110365015344 5ustar zuulzuul00000000000000python-glareclient-0.5.2/PKG-INFO0000664000175100017510000000210513230110365016437 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: python-glareclient Version: 0.5.2 Summary: Glare Artifact Repository Home-page: http://docs.openstack.org/developer/python-glareclient Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: Apache License, Version 2.0 Description-Content-Type: UNKNOWN Description: Python bindings to the Glare Artifact Repository ================================================ Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 python-glareclient-0.5.2/CONTRIBUTING.rst0000666000175100017510000000107313230110105020000 0ustar zuulzuul00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps documented at: http://docs.openstack.org/infra/manual/developers.html#development-workflow Once those steps have been completed, changes to OpenStack should be submitted for review via the Gerrit tool, following the workflow documented at: http://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. Bugs should be filed on Launchpad, not GitHub: https://bugs.launchpad.net/python-glareclient python-glareclient-0.5.2/LICENSE0000666000175100017510000002363613230110105016355 0ustar zuulzuul00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. python-glareclient-0.5.2/releasenotes/0000775000175100017510000000000013230110365020035 5ustar zuulzuul00000000000000python-glareclient-0.5.2/releasenotes/notes/0000775000175100017510000000000013230110365021165 5ustar zuulzuul00000000000000python-glareclient-0.5.2/releasenotes/notes/.placeholder0000666000175100017510000000000013230110105023430 0ustar zuulzuul00000000000000python-glareclient-0.5.2/releasenotes/source/0000775000175100017510000000000013230110365021335 5ustar zuulzuul00000000000000python-glareclient-0.5.2/releasenotes/source/index.rst0000666000175100017510000000020513230110105023165 0ustar zuulzuul00000000000000========================= glareclient Release Notes ========================= .. toctree:: :maxdepth: 1 unreleased mitaka python-glareclient-0.5.2/releasenotes/source/_templates/0000775000175100017510000000000013230110365023472 5ustar zuulzuul00000000000000python-glareclient-0.5.2/releasenotes/source/_templates/.placeholder0000666000175100017510000000000013230110105025735 0ustar zuulzuul00000000000000python-glareclient-0.5.2/releasenotes/source/conf.py0000666000175100017510000002137113230110105022632 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # Glare Release Notes documentation build configuration file, created by # sphinx-quickstart on Tue Nov 3 17:40:50 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'oslosphinx', 'reno.sphinxext', ] # 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-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'glareclient Release Notes' copyright = u'2016, Glare Developers' # Release notes are version independent. # The full version, including alpha/beta/rc tags. release = '' # The short X.Y version. version = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # 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'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # 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_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'GlareClientReleaseNotesdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'glareclientReleaseNotes.tex', u'glareclient Release Notes Documentation', u'Glare Developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'glareclientreleasenotes', u'glareclient Release Notes Documentation', [u'Glare Developers'], 1) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'glareclientReleaseNotes', u'glareclient Release Notes Documentation', u'Glare Developers', 'glareclientReleaseNotes', 'Python bindings for the Glare Artifact Repository.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] python-glareclient-0.5.2/releasenotes/source/_static/0000775000175100017510000000000013230110365022763 5ustar zuulzuul00000000000000python-glareclient-0.5.2/releasenotes/source/_static/.placeholder0000666000175100017510000000000013230110105025226 0ustar zuulzuul00000000000000python-glareclient-0.5.2/releasenotes/source/unreleased.rst0000666000175100017510000000015313230110105024207 0ustar zuulzuul00000000000000============================ Current Series Release Notes ============================ .. release-notes:: python-glareclient-0.5.2/setup.py0000666000175100017510000000200613230110105017046 0ustar zuulzuul00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools # In python < 2.7.4, a lazy loading of package `pbr` will break # setuptools if some other modules registered functions in `atexit`. # solution from: http://bugs.python.org/issue15881#msg170215 try: import multiprocessing # noqa except ImportError: pass setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) python-glareclient-0.5.2/python_glareclient.egg-info/0000775000175100017510000000000013230110365022730 5ustar zuulzuul00000000000000python-glareclient-0.5.2/python_glareclient.egg-info/PKG-INFO0000664000175100017510000000210513230110364024022 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: python-glareclient Version: 0.5.2 Summary: Glare Artifact Repository Home-page: http://docs.openstack.org/developer/python-glareclient Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: Apache License, Version 2.0 Description-Content-Type: UNKNOWN Description: Python bindings to the Glare Artifact Repository ================================================ Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 python-glareclient-0.5.2/python_glareclient.egg-info/entry_points.txt0000664000175100017510000000223213230110364026224 0ustar zuulzuul00000000000000[console_scripts] glare = glareclient.shell:main [openstack.artifact.v1] artifact_activate = glareclient.osc.v1.artifacts:ActivateArtifact artifact_add_tag = glareclient.osc.v1.artifacts:AddTag artifact_create = glareclient.osc.v1.artifacts:CreateArtifact artifact_deactivate = glareclient.osc.v1.artifacts:DeactivateArtifact artifact_delete = glareclient.osc.v1.artifacts:DeleteArtifact artifact_download = glareclient.osc.v1.blobs:DownloadBlob artifact_list = glareclient.osc.v1.artifacts:ListArtifacts artifact_location = glareclient.osc.v1.blobs:AddLocation artifact_publish = glareclient.osc.v1.artifacts:PublishArtifact artifact_reactivate = glareclient.osc.v1.artifacts:ReactivateArtifact artifact_remove-location = glareclient.osc.v1.blobs:RemoveLocation artifact_remove_tag = glareclient.osc.v1.artifacts:RemoveTag artifact_schema = glareclient.osc.v1.artifacts:TypeSchema artifact_show = glareclient.osc.v1.artifacts:ShowArtifact artifact_type-list = glareclient.osc.v1.artifacts:TypeList artifact_update = glareclient.osc.v1.artifacts:UpdateArtifact artifact_upload = glareclient.osc.v1.blobs:UploadBlob [openstack.cli.extension] artifact = glareclient.osc.plugin python-glareclient-0.5.2/python_glareclient.egg-info/pbr.json0000664000175100017510000000005613230110364024406 0ustar zuulzuul00000000000000{"git_version": "6da299f", "is_release": true}python-glareclient-0.5.2/python_glareclient.egg-info/requires.txt0000664000175100017510000000026613230110364025333 0ustar zuulzuul00000000000000pbr!=2.1.0,>=2.0.0 Babel!=2.4.0,>=2.3.4 PrettyTable<0.8,>=0.7.1 keystoneauth1>=3.2.0 requests>=2.14.2 six>=1.9.0 oslo.utils>=3.31.0 oslo.i18n>=3.15.3 oslo.log>=3.30.0 osc-lib>=1.7.0 python-glareclient-0.5.2/python_glareclient.egg-info/dependency_links.txt0000664000175100017510000000000113230110364026775 0ustar zuulzuul00000000000000 python-glareclient-0.5.2/python_glareclient.egg-info/SOURCES.txt0000664000175100017510000000445613230110365024625 0ustar zuulzuul00000000000000.coveragerc .testr.conf AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE README.rst requirements.txt run_tests.sh setup.cfg setup.py test-requirements.txt tox.ini doc/source/conf.py doc/source/index.rst doc/source/man/glare.rst glareclient/__init__.py glareclient/_i18n.py glareclient/client.py glareclient/exc.py glareclient/shell.py glareclient/common/__init__.py glareclient/common/exceptions.py glareclient/common/http.py glareclient/common/keycloak_auth.py glareclient/common/progressbar.py glareclient/common/utils.py glareclient/osc/__init__.py glareclient/osc/plugin.py glareclient/osc/v1/__init__.py glareclient/osc/v1/artifacts.py glareclient/osc/v1/blobs.py glareclient/tests/__init__.py glareclient/tests/unit/__init__.py glareclient/tests/unit/base.py glareclient/tests/unit/fakes.py glareclient/tests/unit/test_common_http.py glareclient/tests/unit/test_progressbar.py glareclient/tests/unit/test_shell.py glareclient/tests/unit/test_utils.py glareclient/tests/unit/osc/__init__.py glareclient/tests/unit/osc/test_plugin.py glareclient/tests/unit/osc/v1/__init__.py glareclient/tests/unit/osc/v1/fakes.py glareclient/tests/unit/osc/v1/fakes_schemas.py glareclient/tests/unit/osc/v1/test_artifacts.py glareclient/tests/unit/osc/v1/test_blob.py glareclient/tests/unit/v1/__init__.py glareclient/tests/unit/v1/test_artifacts.py glareclient/tests/unit/var/badcert.crt glareclient/tests/unit/var/ca.crt glareclient/tests/unit/var/certificate.crt glareclient/tests/unit/var/expired-cert.crt glareclient/tests/unit/var/privatekey.key glareclient/tests/unit/var/wildcard-certificate.crt glareclient/tests/unit/var/wildcard-san-certificate.crt glareclient/v1/__init__.py glareclient/v1/artifacts.py glareclient/v1/client.py glareclient/v1/versions.py python_glareclient.egg-info/PKG-INFO python_glareclient.egg-info/SOURCES.txt python_glareclient.egg-info/dependency_links.txt python_glareclient.egg-info/entry_points.txt python_glareclient.egg-info/not-zip-safe python_glareclient.egg-info/pbr.json python_glareclient.egg-info/requires.txt python_glareclient.egg-info/top_level.txt releasenotes/notes/.placeholder releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder tools/glare.bash_completion tools/with_venv.shpython-glareclient-0.5.2/python_glareclient.egg-info/top_level.txt0000664000175100017510000000001413230110364025454 0ustar zuulzuul00000000000000glareclient python-glareclient-0.5.2/python_glareclient.egg-info/not-zip-safe0000664000175100017510000000000113230110332025150 0ustar zuulzuul00000000000000 python-glareclient-0.5.2/glareclient/0000775000175100017510000000000013230110365017635 5ustar zuulzuul00000000000000python-glareclient-0.5.2/glareclient/osc/0000775000175100017510000000000013230110365020421 5ustar zuulzuul00000000000000python-glareclient-0.5.2/glareclient/osc/__init__.py0000666000175100017510000000000013230110105022512 0ustar zuulzuul00000000000000python-glareclient-0.5.2/glareclient/osc/v1/0000775000175100017510000000000013230110365020747 5ustar zuulzuul00000000000000python-glareclient-0.5.2/glareclient/osc/v1/__init__.py0000666000175100017510000000310313230110105023047 0ustar zuulzuul00000000000000# Copyright (c) 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import argparse class TypeMapperAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, self._type_name_mapper(values)) @staticmethod def _type_name_mapper(type_name): if type_name in ['images', 'image']: return 'images' elif type_name in ['heat-templates', 'heat-template', 'heat_templates', 'heat_template']: return 'heat_templates' elif type_name in ['heat-environments', 'heat-environment', 'heat_environments', 'heat_environment']: return 'heat_environments' elif type_name in ['tosca-templates', 'tosca-template', 'tosca_templates', 'tosca_template']: return 'tosca_templates' elif type_name in ['murano-packages', 'murano-package', 'murano_packages', 'murano_package']: return 'murano_packages' return type_name python-glareclient-0.5.2/glareclient/osc/v1/blobs.py0000666000175100017510000002545213230110105022424 0ustar zuulzuul00000000000000# Copyright (c) 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging import sys from osc_lib.command import command from glareclient.common import progressbar from glareclient.common import utils from glareclient import exc from glareclient.osc.v1 import TypeMapperAction LOG = logging.getLogger(__name__) def _default_blob_property(type_name): if type_name == 'images': return 'image' elif type_name == 'murano_packages': return 'package' elif type_name in ('heat_templates', 'tosca_templates'): return 'template' elif type_name == 'heat_environments': return 'environment' utils.exit('Unknown artifact type. Please specify --blob-property.') class UploadBlob(command.ShowOne): """Upload blob""" def get_parser(self, prog_name): parser = super(UploadBlob, self).get_parser(prog_name) parser.add_argument( 'type_name', metavar='', action=TypeMapperAction, help='Name of artifact type.', ), parser.add_argument( 'name', metavar='', help='Name or id of the artifact to upload.', ), parser.add_argument( '--artifact-version', '-V', metavar='', default='latest', help='Version of the artifact.', ), parser.add_argument( '--id', '-i', action='store_true', help='The specified id of the artifact.', ), parser.add_argument( '--file', metavar='', help='Local file that contains data to be uploaded.', ) parser.add_argument( '--blob-property', '-p', metavar='', help='Name of the blob field.' ) parser.add_argument( '--content-type', '-C', metavar='', default='application/octet-stream', help='Content-type of the blob.' ) parser.add_argument( '--progress', action='store_true', help='Show download progress bar.' ) return parser def take_action(self, parsed_args): LOG.debug('take_action({0})'.format(parsed_args)) client = self.app.client_manager.artifact af_id = utils.get_artifact_id(client, parsed_args) if not parsed_args.blob_property: parsed_args.blob_property = _default_blob_property( parsed_args.type_name) if parsed_args.file is None: if sys.stdin.isatty(): raise exc.CommandError('Blob file should be specified or ' 'explicitly connected to stdin') blob = sys.stdin else: blob = open(parsed_args.file, 'rb') if parsed_args.progress: blob = progressbar.VerboseFileWrapper(blob) client.artifacts.upload_blob(af_id, parsed_args.blob_property, blob, content_type=parsed_args.content_type, type_name=parsed_args.type_name) data = client.artifacts.get(af_id, type_name=parsed_args.type_name) data_to_display = {'blob_property': parsed_args.blob_property} if '/' in parsed_args.blob_property: dict_name, __, key_name = parsed_args.blob_property.partition('/') data_to_display.update(data[dict_name][key_name]) else: data_to_display.update(data[parsed_args.blob_property]) return self.dict2columns(data_to_display) class DownloadBlob(command.Command): """Download blob""" def get_parser(self, prog_name): parser = super(DownloadBlob, self).get_parser(prog_name) parser.add_argument( 'type_name', metavar='', action=TypeMapperAction, help='Name of artifact type.', ), parser.add_argument( 'name', metavar='', help='Name or id of the artifact to download.', ), parser.add_argument( '--artifact-version', '-V', metavar='', default='latest', help='Version of the artifact.', ), parser.add_argument( '--id', '-i', action='store_true', help='The specified id of the artifact.', ), parser.add_argument( '--progress', action='store_true', help='Show download progress bar.' ) parser.add_argument( '--file', metavar='', help='Local file to save downloaded blob to. ' 'If this is not specified and there is no redirection ' 'the blob will not be saved.' ) parser.add_argument( '--blob-property', '-p', metavar='', default=None, help='Name of the blob field.' ) return parser def take_action(self, parsed_args): LOG.debug('take_action({0})'.format(parsed_args)) client = self.app.client_manager.artifact if not parsed_args.blob_property: parsed_args.blob_property = _default_blob_property( parsed_args.type_name) af_id = utils.get_artifact_id(client, parsed_args) data = client.artifacts.download_blob(af_id, parsed_args.blob_property, type_name=parsed_args.type_name) if parsed_args.progress: data = progressbar.VerboseFileWrapper(data) if not (sys.stdout.isatty() and parsed_args.file is None): utils.save_blob(data, parsed_args.file) else: msg = ('No redirection or local file specified for downloaded ' 'blob. Please specify a local file with --file to save ' 'downloaded blob or redirect output to another source.') utils.exit(msg) class AddLocation(command.ShowOne): """Add external location""" def get_parser(self, prog_name): parser = super(AddLocation, self).get_parser(prog_name) parser.add_argument( 'type_name', metavar='', action=TypeMapperAction, help='Name of artifact type.', ), parser.add_argument( 'name', metavar='', help='Name or id of the artifact to download.', ), parser.add_argument( '--artifact-version', '-V', metavar='', default='latest', help='Version of the artifact.', ), parser.add_argument( '--id', '-i', action='store_true', help='The specified id of the artifact.', ), parser.add_argument( '--url', metavar='', help='External location that contains data to be uploaded.', ) parser.add_argument( '--md5', metavar='', help='Specify a checksum md5.', ) parser.add_argument( '--sha1', metavar='', help='Specify a checksum sha1.', ) parser.add_argument( '--sha256', metavar='', help='Specify a checksum sha256.', ) parser.add_argument( '--blob-property', '-p', metavar='', help='Name of the blob field.' ) return parser def take_action(self, parsed_args): LOG.debug('take_action({0})'.format(parsed_args)) client = self.app.client_manager.artifact af_id = utils.get_artifact_id(client, parsed_args) if not parsed_args.blob_property: parsed_args.blob_property = _default_blob_property( parsed_args.type_name) data = { 'url': parsed_args.url, 'md5': parsed_args.md5, 'sha1': parsed_args.sha1, 'sha256': parsed_args.sha256 } client.artifacts.add_external_location( af_id, parsed_args.blob_property, data, type_name=parsed_args.type_name) data = client.artifacts.get(af_id, type_name=parsed_args.type_name) data_to_display = {'blob_property': parsed_args.blob_property} if '/' in parsed_args.blob_property: dict_name, __, key_name = parsed_args.blob_property.partition('/') data_to_display.update(data[dict_name][key_name]) else: data_to_display.update(data[parsed_args.blob_property]) return self.dict2columns(data_to_display) class RemoveLocation(command.ShowOne): """Remove external location""" def get_parser(self, prog_name): parser = super(RemoveLocation, self).get_parser(prog_name) parser.add_argument( 'type_name', metavar='', action=TypeMapperAction, help='Name of artifact type.', ), parser.add_argument( 'name', metavar='', help='Name or id of the artifact to download.', ), parser.add_argument( '--artifact-version', '-V', metavar='', default='latest', help='Version of the artifact.', ), parser.add_argument( '--id', '-i', action='store_true', help='The specified id of the artifact.', ), parser.add_argument( '--blob-property', '-p', metavar='', help='Name of the blob field.' ) return parser def take_action(self, parsed_args): LOG.debug('take_action({0})'.format(parsed_args)) client = self.app.client_manager.artifact af_id = utils.get_artifact_id(client, parsed_args) if not parsed_args.blob_property: parsed_args.blob_property = _default_blob_property( parsed_args.type_name) client.artifacts.remove_external_location( af_id, parsed_args.blob_property, type_name=parsed_args.type_name) python-glareclient-0.5.2/glareclient/osc/v1/artifacts.py0000666000175100017510000005100213230110105023271 0ustar zuulzuul00000000000000# Copyright (c) 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging from osc_lib.command import command from oslo_utils import strutils from pprint import pformat import six from glareclient.common import utils as glare_utils from glareclient.osc.v1 import TypeMapperAction LOG = logging.getLogger(__name__) def print_artifact(client, data, type_name): schema = \ client.artifacts.get_type_schema(type_name=type_name)['properties'] columns = ('field', 'value', 'glare type') column_headers = [c.capitalize() for c in columns] table = [] for key, value in six.iteritems(data): if schema[key]['glareType'] == 'Blob': value = pformat(value) table.append((key, value, schema[key]['glareType'])) return (column_headers, table) class ListArtifacts(command.Lister): """List of artifacts""" def get_parser(self, prog_name): parser = super(ListArtifacts, self).get_parser(prog_name) parser.add_argument( 'type_name', metavar='', action=TypeMapperAction, help='Name of artifact type.', ) parser.add_argument( '--limit', '-l', default=20, metavar='', type=int, help='Maximum number of artifacts to get.', ) parser.add_argument( '--page-size', '-p', default=20, metavar='', type=int, help='Number of artifacts to request in each paginated request.', ) parser.add_argument( '--filter', '-F', default=[], action='append', metavar='', help='Filtering artifact list by a user-defined property.', ) parser.add_argument( '--sort', '-S', default='name:asc', metavar='[:]', help='Comma-separated list of sort keys and directions in the ' 'form of [:].', ) return parser def take_action(self, parsed_args): LOG.debug('take_action({0})'.format(parsed_args)) client = self.app.client_manager.artifact params = {'limit': parsed_args.limit, 'filters': [f.split('=', 1) for f in parsed_args.filter], 'sort': parsed_args.sort, 'page_size': parsed_args.page_size} type_name = parsed_args.type_name data = client.artifacts.list(type_name=type_name, **params) columns = ['id', 'name', 'version', 'owner', 'visibility', 'status'] if type_name == 'all': columns.insert(3, 'type_name') column_headers = [c.capitalize().replace("_", " ") for c in columns] table = [] for af in data: table.append(glare_utils.get_item_properties(af, columns)) return (column_headers, table) class ShowArtifact(command.Lister): """Show details artifact""" def get_parser(self, prog_name): parser = super(ShowArtifact, self).get_parser(prog_name) parser.add_argument( 'type_name', metavar='', action=TypeMapperAction, help='Name of artifact type.', ), parser.add_argument( 'name', metavar='', help='Name or id of the artifact to show.', ), parser.add_argument( '--artifact-version', '-V', metavar='', default='latest', help='Version of the artifact.', ), parser.add_argument( '--id', '-i', action='store_true', help='Flag indicates to use artifact id instead of its name.', ), return parser def take_action(self, parsed_args): LOG.debug('take_action({0})'.format(parsed_args)) client = self.app.client_manager.artifact af_id = glare_utils.get_artifact_id(client, parsed_args) data = client.artifacts.get(af_id, type_name=parsed_args.type_name) return print_artifact(client, data, parsed_args.type_name) class CreateArtifact(command.Lister): """Create a new artifact""" def get_parser(self, prog_name): parser = super(CreateArtifact, self).get_parser(prog_name) parser.add_argument( 'type_name', metavar='', action=TypeMapperAction, help='Name of artifact type.', ), parser.add_argument( 'name', metavar='', help='Name of the artifact.', ), parser.add_argument( '--artifact-version', '-V', default='0.0.0', metavar='', help='Version of the artifact.', ) parser.add_argument( '--property', '-p', metavar='', action='append', default=[], help='Simple artifact property.' ) parser.add_argument( '--list', '-l', metavar='', action='append', default=[], help='Artifact list property.' ) parser.add_argument( '--dict', '-d', metavar='', action='append', default=[], help='Artifact dict property.' ) return parser def take_action(self, parsed_args): LOG.debug('take_action({0})'.format(parsed_args)) prop = {} for datum in parsed_args.property: key, value = datum.split('=', 1) prop[key] = value for datum in parsed_args.list: key, value = datum.split('=', 1) value = strutils.split_by_commas(value) prop[key] = value for datum in parsed_args.dict: key, value = datum.split('=', 1) value = strutils.split_by_commas(value) prop[key] = {} for elem in value: k, v = elem.split(':', 1) prop[key][k] = v client = self.app.client_manager.artifact data = client.artifacts.create(parsed_args.name, type_name=parsed_args.type_name, version=parsed_args.artifact_version, **prop) return print_artifact(client, data, parsed_args.type_name) class UpdateArtifact(command.Lister): """Update the properties of the artifact""" def get_parser(self, prog_name): parser = super(UpdateArtifact, self).get_parser(prog_name) parser.add_argument( 'type_name', metavar='', action=TypeMapperAction, help='Name of artifact type.', ), parser.add_argument( 'name', metavar='', help='Name or id of the artifact to update.', ), parser.add_argument( '--artifact-version', '-V', metavar='', default='latest', help='Version of the artifact.', ), parser.add_argument( '--id', '-i', action='store_true', help='Flag indicates to use artifact id instead of its name.', ), parser.add_argument( '--remove-property', '-r', metavar='', action='append', default=[], help='Property that will be removed.' ), parser.add_argument( '--property', '-p', metavar='', action='append', default=[], help='Simple artifact property.' ) parser.add_argument( '--list', '-l', metavar='', action='append', default=[], help='Artifact list property.' ) parser.add_argument( '--dict', '-d', metavar='', action='append', default=[], help='Artifact dict property.' ) return parser def take_action(self, parsed_args): LOG.debug('take_action({0})'.format(parsed_args)) prop = {} for datum in parsed_args.property: key, value = datum.split('=', 1) prop[key] = value for datum in parsed_args.list: key, value = datum.split('=', 1) value = strutils.split_by_commas(value) prop[key] = value for datum in parsed_args.dict: key, value = datum.split('=', 1) value = strutils.split_by_commas(value) prop[key] = {} for elem in value: k, v = elem.split(':', 1) prop[key][k] = v client = self.app.client_manager.artifact af_id = glare_utils.get_artifact_id(client, parsed_args) data = client.artifacts.update( af_id, type_name=parsed_args.type_name, remove_props=parsed_args.remove_property, **prop) return print_artifact(client, data, parsed_args.type_name) class DeleteArtifact(command.Command): """Delete the artifact""" def get_parser(self, prog_name): parser = super(DeleteArtifact, self).get_parser(prog_name) parser.add_argument( 'type_name', metavar='', action=TypeMapperAction, help='Name of artifact type.', ), parser.add_argument( 'name', metavar='', help='Name or id of the artifact to delete.', ), parser.add_argument( '--artifact-version', '-V', metavar='', default='latest', help='Version of the artifact.', ), parser.add_argument( '--id', '-i', action='store_true', help='Flag indicates to use artifact id instead of its name.', ), return parser def take_action(self, parsed_args): LOG.debug('take_action({0})'.format(parsed_args)) client = self.app.client_manager.artifact af_id = glare_utils.get_artifact_id(client, parsed_args) client.artifacts.delete(af_id, type_name=parsed_args.type_name) class ActivateArtifact(command.Lister): """Activate the artifact""" def get_parser(self, prog_name): parser = super(ActivateArtifact, self).get_parser(prog_name) parser.add_argument( 'type_name', metavar='', action=TypeMapperAction, help='Name of artifact type.', ), parser.add_argument( 'name', metavar='', help='Name or id of the artifact to activate.', ), parser.add_argument( '--artifact-version', '-V', metavar='', default='latest', help='Version of the artifact.', ), parser.add_argument( '--id', '-i', action='store_true', help='Flag indicates to use artifact id instead of its name.', ), return parser def take_action(self, parsed_args): LOG.debug('take_action({0})'.format(parsed_args)) client = self.app.client_manager.artifact af_id = glare_utils.get_artifact_id(client, parsed_args) data = client.artifacts.activate(af_id, type_name=parsed_args.type_name) return print_artifact(client, data, parsed_args.type_name) class DeactivateArtifact(command.Lister): """Deactivate the artifact""" def get_parser(self, prog_name): parser = super(DeactivateArtifact, self).get_parser(prog_name) parser.add_argument( 'type_name', metavar='', action=TypeMapperAction, help='Name of artifact type.', ), parser.add_argument( 'name', metavar='', help='Name or id of the artifact to deactivate.', ), parser.add_argument( '--artifact-version', '-V', metavar='', default='latest', help='Version of the artifact.', ), parser.add_argument( '--id', '-i', action='store_true', help='Flag indicates to use artifact id instead of its name.', ), return parser def take_action(self, parsed_args): LOG.debug('take_action({0})'.format(parsed_args)) client = self.app.client_manager.artifact af_id = glare_utils.get_artifact_id(client, parsed_args) data = client.artifacts.deactivate(af_id, type_name=parsed_args.type_name) return print_artifact(client, data, parsed_args.type_name) class ReactivateArtifact(command.Lister): """Reactivate the artifact""" def get_parser(self, prog_name): parser = super(ReactivateArtifact, self).get_parser(prog_name) parser.add_argument( 'type_name', metavar='', action=TypeMapperAction, help='Name of artifact type.', ), parser.add_argument( 'name', metavar='', help='Name or id of the artifact to reactivate.', ), parser.add_argument( '--artifact-version', '-V', metavar='', default='latest', help='Version of the artifact.', ), parser.add_argument( '--id', '-i', action='store_true', help='Flag indicates to use artifact id instead of its name.', ), return parser def take_action(self, parsed_args): LOG.debug('take_action({0})'.format(parsed_args)) client = self.app.client_manager.artifact af_id = glare_utils.get_artifact_id(client, parsed_args) data = client.artifacts.reactivate(af_id, type_name=parsed_args.type_name) return print_artifact(client, data, parsed_args.type_name) class PublishArtifact(command.Lister): """Publish the artifact""" def get_parser(self, prog_name): parser = super(PublishArtifact, self).get_parser(prog_name) parser.add_argument( 'type_name', metavar='', action=TypeMapperAction, help='Name of artifact type.', ), parser.add_argument( 'name', metavar='', help='Name or id of the artifact to publish.', ), parser.add_argument( '--artifact-version', '-V', metavar='', default='latest', help='Version of the artifact.', ), parser.add_argument( '--id', '-i', action='store_true', help='Flag indicates to use artifact id instead of its name.', ), return parser def take_action(self, parsed_args): LOG.debug('take_action({0})'.format(parsed_args)) client = self.app.client_manager.artifact af_id = glare_utils.get_artifact_id(client, parsed_args) data = client.artifacts.publish(af_id, type_name=parsed_args.type_name) return print_artifact(client, data, parsed_args.type_name) class AddTag(command.Lister): """Add tag to artifact""" def get_parser(self, prog_name): parser = super(AddTag, self).get_parser(prog_name) parser.add_argument( 'type_name', metavar='', action=TypeMapperAction, help='Name of artifact type.', ) parser.add_argument( 'name', metavar='', help='Name or id of the artifact to publish.', ) parser.add_argument( 'tag', metavar='', help='Value of the tag to add to the artifact.', ) parser.add_argument( '--artifact-version', '-V', metavar='', default='latest', help='Version of the artifact.', ) parser.add_argument( '--id', '-i', action='store_true', help='Flag indicates to use artifact id instead of its name.', ) return parser def take_action(self, parsed_args): LOG.debug('take_action({0})'.format(parsed_args)) client = self.app.client_manager.artifact af_id = glare_utils.get_artifact_id(client, parsed_args) data = client.artifacts.add_tag( af_id, tag_value=parsed_args.tag, type_name=parsed_args.type_name) return print_artifact(client, data, parsed_args.type_name) class RemoveTag(command.Lister): """Remove tag from the artifact""" def get_parser(self, prog_name): parser = super(RemoveTag, self).get_parser(prog_name) parser.add_argument( 'type_name', metavar='', action=TypeMapperAction, help='Name of artifact type.', ) parser.add_argument( 'name', metavar='', help='Name or id of the artifact to publish.', ) parser.add_argument( 'tag', metavar='', help='Value of the tag to remove from the artifact.', ) parser.add_argument( '--artifact-version', '-V', metavar='', default='latest', help='Version of the artifact.', ) parser.add_argument( '--id', '-i', action='store_true', help='Flag indicates to use artifact id instead of its name.', ) return parser def take_action(self, parsed_args): LOG.debug('take_action({0})'.format(parsed_args)) client = self.app.client_manager.artifact af_id = glare_utils.get_artifact_id(client, parsed_args) data = client.artifacts.remove_tag( af_id, tag_value=parsed_args.tag, type_name=parsed_args.type_name) return print_artifact(client, data, parsed_args.type_name) class TypeList(command.Lister): """List of type names""" def get_parser(self, prog_name): parser = super(TypeList, self).get_parser(prog_name) return parser def take_action(self, parsed_args): LOG.debug('take_action({0})'.format(parsed_args)) client = self.app.client_manager.artifact data = client.artifacts.get_type_list() columns = ('name', 'version') column_headers = [c.capitalize() for c in columns] return (column_headers, data) class TypeSchema(command.Lister): """Schema of type name.""" def get_parser(self, prog_name): parser = super(TypeSchema, self).get_parser(prog_name) parser.add_argument( 'type_name', metavar='', action=TypeMapperAction, help='Name of artifact type.', ) return parser def take_action(self, parsed_args): LOG.debug('take_action({0})'.format(parsed_args)) client = self.app.client_manager.artifact data = client.artifacts.get_type_schema( type_name=parsed_args.type_name)['properties'] columns = ('name', 'glare_type', 'mutable', 'required', 'sortable', 'filters', 'available_values') column_headers = [c.capitalize() for c in columns] table = [] for name, values in six.iteritems(data): row = ( name, values.get('glareType'), values.get('mutable', False), values.get('required_on_activate', True), values.get('sortable', False), values.get('filter_ops'), values.get('enum', '') ) table.append(row) return (column_headers, table) python-glareclient-0.5.2/glareclient/osc/plugin.py0000666000175100017510000000330613230110105022265 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging from osc_lib import utils from glareclient._i18n import _ LOG = logging.getLogger(__name__) DEFAULT_API_VERSION = "1" API_VERSION_OPTION = "os_artifact_api_version" API_NAME = "artifact" API_VERSIONS = { '1': 'glareclient.v1.client.Client', } def make_client(instance): """Returns an artifact service client""" glare_client = utils.get_client_class( API_NAME, instance._api_version[API_NAME], API_VERSIONS) LOG.debug("Instantiating glare client: {0}".format( glare_client)) kwargs = dict( region_name=instance._region_name, session=instance.session, service_type='artifact', ) return glare_client(instance.get_configuration().get('glare_url'), **kwargs) def build_option_parser(parser): """Hook to add global options""" parser.add_argument( '--os-artifact-api-version', metavar='', default=utils.env('OS_ARTIFACT_API_VERSION'), help=_('Artifact API version, default=%s ' '(Env: OS_ARTIFACT_API_VERSION)') % DEFAULT_API_VERSION) return parser python-glareclient-0.5.2/glareclient/client.py0000666000175100017510000000264313230110105021464 0ustar zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from glareclient.common import utils def Client(version='1', endpoint=None, session=None, *args, **kwargs): """Client for the Glare Artifact Repository. Generic client for the Glare Artifact Repository. See version classes for specific details. :param string version: The version of API to use. :param session: A keystoneauth1 session that should be used for transport. :type session: keystoneauth1.session.Session """ if endpoint is not None: kwargs.setdefault('endpoint_override', endpoint) if version is None: raise RuntimeError("You must provide a client version") module = utils.import_versioned_module(int(version), 'client') client_class = getattr(module, 'Client') return client_class(endpoint, *args, session=session, **kwargs) python-glareclient-0.5.2/glareclient/common/0000775000175100017510000000000013230110365021125 5ustar zuulzuul00000000000000python-glareclient-0.5.2/glareclient/common/__init__.py0000666000175100017510000000000013230110105023216 0ustar zuulzuul00000000000000python-glareclient-0.5.2/glareclient/common/progressbar.py0000666000175100017510000000720213230110105024023 0ustar zuulzuul00000000000000# 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. import decimal import os import sys SPIN_CHARS = ('-', '\\', '|', '/') CHUNKSIZE = 1024 * 64 # 64kB class _ProgressBarBase(object): """A progress bar provider for a wrapped object. Base abstract class used by specific class wrapper to show a progress bar when the wrapped object are consumed. :param wrapped: Object to wrap that hold data to be consumed. :param totalsize: The total size of the data in the wrapped object. :note: The progress will be displayed only if sys.stdout is a tty. """ def __init__(self, wrapped): self._wrapped = wrapped self._show_progress = sys.stdout.isatty() self._percent = 0 self._totalread = 0 self._spin_index = 0 if hasattr(wrapped, "len"): self._totalsize = wrapped.len elif hasattr(wrapped, "fileno"): self._totalsize = os.fstat(wrapped.fileno()).st_size else: self._totalsize = 0 def _display_progress_bar(self, size_read): self._totalread += size_read if self._show_progress: if self._totalsize: percent = float(self._totalread) / float(self._totalsize) # Output something like this: [==========> ] 49% sys.stdout.write('\r[{0:<30}] {1:.0%}'.format( '=' * int(decimal.Decimal(percent * 29).quantize( decimal.Decimal('1'), rounding=decimal.ROUND_HALF_UP)) + '>', percent )) else: sys.stdout.write( '\r[%s] %d bytes' % (SPIN_CHARS[self._spin_index], self._totalread)) self._spin_index += 1 if self._spin_index == len(SPIN_CHARS): self._spin_index = 0 sys.stdout.flush() def __getattr__(self, attr): # Forward other attribute access to the wrapped object. return getattr(self._wrapped, attr) class VerboseFileWrapper(_ProgressBarBase): """A file wrapper with a progress bar. The file wrapper shows and advances a progress bar whenever the wrapped file's read method is called. """ def read(self, *args, **kwargs): data = self._wrapped.read(*args, **kwargs) if data: self._display_progress_bar(len(data)) else: if self._show_progress: # Break to a new line from the progress bar for incoming # output. sys.stdout.write('\n') return data def __iter__(self): return self def next(self): try: data = self._wrapped.next() self._display_progress_bar(len(data)) return data except StopIteration: if self._show_progress: # Break to a new line from the progress bar for incoming # output. sys.stdout.write('\n') raise # In Python 3, __next__() has replaced next(). __next__ = next python-glareclient-0.5.2/glareclient/common/exceptions.py0000666000175100017510000000026413230110105023654 0ustar zuulzuul00000000000000# This is here for compatibility purposes. Once all known OpenStack clients # are updated to use glareclient.exc, this file should be removed from glareclient.exc import * # noqa python-glareclient-0.5.2/glareclient/common/keycloak_auth.py0000666000175100017510000000554213230110105024322 0ustar zuulzuul00000000000000# Copyright 2016 - Nokia Networks # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import logging import pprint import requests from six.moves import urllib from glareclient.common import utils LOG = logging.getLogger(__name__) def authenticate(**kwargs): """Performs authentication using Keycloak OpenID Protocol. :param auth_url: Base authentication url of KeyCloak server (e.g. "https://my.keycloak:8443/auth" :param client_id: Client ID (according to OpenID Connect protocol). :param realm_name: KeyCloak realm name. :param username: User name (Optional, if None then access_token must be provided). :param password: Password (Optional). :param cacert: SSL certificate file (Optional). :param insecure: If True, SSL certificate is not verified (Optional). """ auth_url = kwargs.get('auth_url') client_id = kwargs.get('client_id') realm_name = kwargs.get('realm_name') username = kwargs.get('username') password = kwargs.get('password') insecure = kwargs.get('insecure', False) cacert = kwargs.get('cacert', utils.get_system_ca_file()) if not auth_url: raise ValueError('Base authentication url is not provided.') if not client_id: raise ValueError('Client ID is not provided.') if not realm_name: raise ValueError('Project(realm) name is not provided.') if not username: raise ValueError('Username is not provided.') if password is None: raise ValueError('Password is not provided.') access_token_endpoint = ( "%s/realms/%s/protocol/openid-connect/token" % (auth_url, realm_name) ) verify = None if urllib.parse.urlparse(access_token_endpoint).scheme == "https": verify = False if insecure else cacert if cacert else True body = { 'grant_type': 'password', 'username': username, 'password': password, 'scope': 'profile', 'client_id': client_id } resp = requests.post( access_token_endpoint, data=body, verify=verify ) try: resp.raise_for_status() except Exception as e: raise Exception("Failed to get access token:\n %s" % str(e)) LOG.debug( "HTTP response from OIDC provider: %s" % pprint.pformat(resp.json()) ) return resp.json()['access_token'] python-glareclient-0.5.2/glareclient/common/http.py0000666000175100017510000003044313230110105022454 0ustar zuulzuul00000000000000# Copyright 2012 OpenStack Foundation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy import hashlib import socket from keystoneauth1 import adapter from oslo_log import log as logging from oslo_serialization import jsonutils from oslo_utils import encodeutils import requests import six from six.moves import urllib from glareclient.common import exceptions as exc from glareclient.common import keycloak_auth from glareclient.common import utils LOG = logging.getLogger(__name__) USER_AGENT = 'python-glareclient' CHUNKSIZE = 1024 * 64 # 64kB def _handle_response(resp): content_type = resp.headers.get('Content-Type') if not content_type: body_iter = six.StringIO(resp.text) try: body_iter = jsonutils.loads(''.join([c for c in body_iter])) except ValueError: body_iter = None elif 'json' in content_type: # Let's use requests json method, it should take care of # response encoding try: body_iter = resp.json() except Exception: body_iter = None else: # Do not read all response in memory when downloading a blob. body_iter = _close_after_stream(resp, CHUNKSIZE) return resp, body_iter def _close_after_stream(response, chunk_size): """Iterate over the content and ensure the response is closed after.""" # Yield each chunk in the response body for chunk in response.iter_content(chunk_size=chunk_size): yield chunk # Once we're done streaming the body, ensure everything is closed. # This will return the connection to the HTTPConnectionPool in urllib3 # and ideally reduce the number of HTTPConnectionPool full warnings. response.close() class HTTPClient(object): def __init__(self, endpoint, **kwargs): self.endpoint = endpoint self.auth_url = kwargs.get('auth_url') self.auth_token = kwargs.get('auth_token') self.username = kwargs.get('username') self.password = kwargs.get('password') self.region_name = kwargs.get('region_name') self.include_pass = kwargs.get('include_pass') self.endpoint_url = endpoint self.tenant_name = kwargs.get('tenant_name') self.cert_file = kwargs.get('cert_file') self.key_file = kwargs.get('key_file') self.timeout = kwargs.get('timeout') self.ssl_connection_params = { 'cacert': kwargs.get('cacert'), 'cert_file': kwargs.get('cert_file'), 'key_file': kwargs.get('key_file'), 'insecure': kwargs.get('insecure'), } self.verify_cert = None if urllib.parse.urlparse(endpoint).scheme == "https": if kwargs.get('insecure'): self.verify_cert = False else: self.verify_cert = kwargs.get( 'cacert', utils.get_system_ca_file()) def _safe_header(self, name, value): if name in ['X-Auth-Token', 'X-Subject-Token']: # because in python3 byte string handling is ... ug v = value.encode('utf-8') h = hashlib.sha1(v) d = h.hexdigest() return encodeutils.safe_decode(name), "{SHA1}%s" % d else: return (encodeutils.safe_decode(name), encodeutils.safe_decode(value)) def log_curl_request(self, url, method, kwargs): curl = ['curl -i -X %s' % method] for (key, value) in kwargs['headers'].items(): header = '-H \'%s: %s\'' % self._safe_header(key, value) curl.append(header) conn_params_fmt = [ ('key_file', '--key %s'), ('cert_file', '--cert %s'), ('cacert', '--cacert %s'), ] for (key, fmt) in conn_params_fmt: value = self.ssl_connection_params.get(key) if value: curl.append(fmt % value) if self.ssl_connection_params.get('insecure'): curl.append('-k') if 'data' in kwargs: curl.append('-d \'%s\'' % kwargs['data']) curl.append('%s%s' % (self.endpoint, url)) LOG.debug(' '.join(curl)) @staticmethod def log_http_response(resp): status = (resp.raw.version / 10.0, resp.status_code, resp.reason) dump = ['\nHTTP/%.1f %s %s' % status] dump.extend(['%s: %s' % (k, v) for k, v in resp.headers.items()]) dump.append('') if resp.content: content = resp.content if isinstance(content, six.binary_type): try: content = encodeutils.safe_decode(resp.content) except UnicodeDecodeError: pass else: dump.extend([content, '']) LOG.debug('\n'.join(dump)) def request(self, url, method, log=True, **kwargs): """Send an http request with the specified characteristics. Wrapper around requests.request to handle tasks such as setting headers and error handling. """ # Copy the kwargs so we can reuse the original in case of redirects kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {})) kwargs['headers'].setdefault('User-Agent', USER_AGENT) if self.auth_token: kwargs['headers'].setdefault('X-Auth-Token', self.auth_token) else: kwargs['headers'].update(self.credentials_headers()) if self.auth_url: kwargs['headers'].setdefault('X-Auth-Url', self.auth_url) if self.region_name: kwargs['headers'].setdefault('X-Region-Name', self.region_name) if self.tenant_name: kwargs['headers'].setdefault('X-Project-Id', self.tenant_name) self.log_curl_request(url, method, kwargs) if self.cert_file and self.key_file: kwargs['cert'] = (self.cert_file, self.key_file) if self.verify_cert is not None: kwargs['verify'] = self.verify_cert if self.timeout is not None: kwargs['timeout'] = float(self.timeout) # Allow the option not to follow redirects follow_redirects = kwargs.pop('redirect', True) # Since requests does not follow the RFC when doing redirection to sent # back the same method on a redirect we are simply bypassing it. For # example if we do a DELETE/POST/PUT on a URL and we get a 302 RFC says # that we should follow that URL with the same method as before, # requests doesn't follow that and send a GET instead for the method. # Hopefully this could be fixed as they say in a comment in a future # point version i.e.: 3.x # See issue: https://github.com/kennethreitz/requests/issues/1704 allow_redirects = False try: resp = requests.request( method, self.endpoint_url + url, allow_redirects=allow_redirects, **kwargs) except socket.gaierror as e: message = ("Error finding address for %(url)s: %(e)s" % {'url': self.endpoint_url + url, 'e': e}) raise exc.InvalidEndpoint(message=message) except (socket.error, socket.timeout, requests.exceptions.ConnectionError) as e: endpoint = self.endpoint message = ("Error communicating with %(endpoint)s %(e)s" % {'endpoint': endpoint, 'e': e}) raise exc.CommunicationError(message=message) if log: self.log_http_response(resp) if 'X-Auth-Key' not in kwargs['headers'] and \ (resp.status_code == 401 or (resp.status_code == 500 and "(HTTP 401)" in resp.content)): raise exc.HTTPUnauthorized("Authentication failed. Please try" " again.\n%s" % resp.content) elif 400 <= resp.status_code < 600: raise exc.from_response(resp) elif resp.status_code in (301, 302, 305): # Redirected. Reissue the request to the new location, # unless caller specified follow_redirects=False if follow_redirects: location = resp.headers.get('location') path = self.strip_endpoint(location) resp = self.request(path, method, **kwargs) elif resp.status_code == 300: raise exc.from_response(resp) return resp def strip_endpoint(self, location): if location is None: message = "Location not returned with 302" raise exc.InvalidEndpoint(message=message) elif location.startswith(self.endpoint): return location[len(self.endpoint):] else: message = "Prohibited endpoint redirect %s" % location raise exc.InvalidEndpoint(message=message) def credentials_headers(self): creds = {} if self.username: creds['X-Auth-User'] = self.username if self.password: creds['X-Auth-Key'] = self.password return creds def process_request(self, url, method, **kwargs): resp = self.request(url, method, **kwargs) return _handle_response(resp) def head(self, url, **kwargs): return self.process_request(url, "HEAD", **kwargs) def get(self, url, **kwargs): return self.process_request(url, "GET", **kwargs) def post(self, url, **kwargs): return self.process_request(url, "POST", **kwargs) def put(self, url, **kwargs): return self.process_request(url, "PUT", **kwargs) def delete(self, url, **kwargs): return self.request(url, "DELETE", **kwargs) def patch(self, url, **kwargs): return self.process_request(url, "PATCH", **kwargs) class SessionClient(adapter.LegacyJsonAdapter): """HTTP client based on Keystone client session.""" def request(self, url, method, **kwargs): resp, body = super(SessionClient, self).request( url, method, **kwargs) if resp.status_code == 300 or (400 <= resp.status_code < 600): raise exc.from_response(resp) return resp, body def construct_http_client(*args, **kwargs): session = kwargs.pop('session', None) auth = kwargs.pop('auth', None) endpoint = next(iter(args), None) keycloak_auth_url = kwargs.pop('keycloak_auth_url', None) auth_token = kwargs.pop('auth_token', None) if session: service_type = kwargs.pop('service_type', None) endpoint_type = kwargs.pop('endpoint_type', None) region_name = kwargs.pop('region_name', None) service_name = kwargs.pop('service_name', None) parameters = { 'endpoint_override': endpoint, 'session': session, 'auth': auth, 'interface': endpoint_type, 'service_type': service_type, 'region_name': region_name, 'service_name': service_name, 'user_agent': 'python-glareclient', } parameters.update(kwargs) return SessionClient(**parameters) elif endpoint: if keycloak_auth_url: kwargs['auth_token'] = keycloak_auth.authenticate( auth_url=keycloak_auth_url, client_id=kwargs.pop('openid_client_id', None), username=kwargs.pop('keycloak_username', None), password=kwargs.pop('keycloak_password', None), realm_name=kwargs.pop('keycloak_realm_name', None), **kwargs ) else: kwargs['auth_token'] = auth_token return HTTPClient(endpoint, **kwargs) else: raise AttributeError('Constructing a client must contain either an ' 'endpoint or a session') python-glareclient-0.5.2/glareclient/common/utils.py0000666000175100017510000001357713230110105022646 0ustar zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from __future__ import print_function import errno import hashlib import os import sys if os.name == 'nt': import msvcrt else: msvcrt = None from oslo_log import log as logging from oslo_utils import encodeutils from oslo_utils import importutils import requests from glareclient import exc LOG = logging.getLogger(__name__) SENSITIVE_HEADERS = ('X-Auth-Token', ) def env(*vars, **kwargs): """Search for the first defined of possibly many env vars. Returns the first environment variable defined in vars, or returns the default defined in kwargs. """ for v in vars: value = os.environ.get(v, None) if value: return value return kwargs.get('default', '') def import_versioned_module(version, submodule=None): module = 'glareclient.v%s' % version if submodule: module = '.'.join((module, submodule)) return importutils.import_module(module) def exit(msg='', exit_code=1): if msg: print(encodeutils.safe_decode(msg), file=sys.stderr) sys.exit(exit_code) class ResponseBlobWrapper(object): """Represent HTTP response as iterator with known length.""" def __init__(self, resp, verify_md5=True): self.hash_md5 = resp.headers.get("Content-MD5") self.blob_md5 = hashlib.md5() if 301 <= resp.status_code <= 302: # NOTE(sskripnick): handle redirect manually to prevent sending # auth token to external resource. # Use stream=True to prevent reading whole response into memory. # Set Accept-Encoding explicitly to "identity" because setting # stream=True forces Accept-Encoding to be "gzip, deflate". # It should be "identity" because we should know Content-Length. resp = requests.get(resp.headers.get("Location"), headers={"Accept-Encoding": "identity"}) self.len = resp.headers.get("Content-Length", 0) self.iter = resp.iter_content(65536) self.verify_md5 = verify_md5 def __iter__(self): return self def next(self): try: data = self.iter.next() if self.verify_md5: self.blob_md5.update(data) return data except StopIteration: if self.verify_md5 and self.blob_md5.hexdigest() != self.hash_md5: raise IOError(errno.EPIPE, 'Checksum mismatch: %s (expected %s)' % (self.blob_md5.hexdigest(), self.hash_md5)) raise __next__ = next def get_item_properties(item, fields, mixed_case_fields=None, formatters=None): """Return a tuple containing the item properties. :param item: a single item resource (e.g. Server, Project, etc) :param fields: tuple of strings with the desired field names :param mixed_case_fields: tuple of field names to preserve case :param formatters: dictionary mapping field names to callables to format the values """ if mixed_case_fields is None: mixed_case_fields = [] if formatters is None: formatters = {} row = [] for field in fields: if field in mixed_case_fields: field_name = field.replace(' ', '_') else: field_name = field.lower().replace(' ', '_') data = item[field_name] if field in formatters: row.append(formatters[field](data)) else: row.append(data) return tuple(row) def make_size_human_readable(size): suffix = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB'] base = 1024.0 index = 0 if size is None: size = 0 while size >= base: index = index + 1 size = size / base padded = '%.1f' % size stripped = padded.rstrip('0').rstrip('.') return '%s%s' % (stripped, suffix[index]) def save_blob(data, path): """Save a blob to the specified path. :param data: blob of the artifact :param path: path to save the blob to """ if path is None: blob = getattr(sys.stdout, 'buffer', sys.stdout) else: blob = open(path, 'wb') try: for chunk in data: blob.write(chunk) finally: if path is not None: blob.close() def get_artifact_id(client, parsed_args): if parsed_args.id: return parsed_args.name try: return client.artifacts.get_by_name( parsed_args.name, version=parsed_args.artifact_version, type_name=parsed_args.type_name)['id'] except exc.BadRequest as e: exit(msg=e.details) def get_system_ca_file(): """Return path to system default CA file.""" # Standard CA file locations for Debian/Ubuntu, RedHat/Fedora, # Suse, FreeBSD/OpenBSD, MacOSX, and the bundled ca ca_path = ['/etc/ssl/certs/ca-certificates.crt', '/etc/pki/tls/certs/ca-bundle.crt', '/etc/ssl/ca-bundle.pem', '/etc/ssl/cert.pem', '/System/Library/OpenSSL/certs/cacert.pem', requests.certs.where()] for ca in ca_path: LOG.debug("Looking for ca file %s", ca) if os.path.exists(ca): LOG.debug("Using ca file %s", ca) return ca LOG.warning("System ca file could not be found.") python-glareclient-0.5.2/glareclient/__init__.py0000666000175100017510000000215113230110105021737 0ustar zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # # 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 glareclient.client Client = glareclient.client.Client except ImportError: import warnings warnings.warn("Could not import glareclient.client", ImportWarning) import pbr.version version_info = pbr.version.VersionInfo('python-glareclient') try: __version__ = version_info.version_string() except AttributeError: __version__ = None python-glareclient-0.5.2/glareclient/_i18n.py0000666000175100017510000000213713230110105021122 0ustar zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import oslo_i18n as i18n _translators = i18n.TranslatorFactory(domain='glareclient') # 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 python-glareclient-0.5.2/glareclient/v1/0000775000175100017510000000000013230110365020163 5ustar zuulzuul00000000000000python-glareclient-0.5.2/glareclient/v1/client.py0000666000175100017510000000245413230110105022012 0ustar zuulzuul00000000000000 # Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from glareclient.common import http from glareclient.v1 import artifacts from glareclient.v1 import versions class Client(object): """Client for the Glare Artifact Repository v1 API. :param string endpoint: A user-supplied endpoint URL for the glare service. :param string token: Token for authentication. """ def __init__(self, endpoint, **kwargs): """Initialize a new client for the Glare v1 API.""" self.version = kwargs.get('version') self.http_client = http.construct_http_client(endpoint, **kwargs) self.artifacts = artifacts.Controller(self.http_client) self.versions = versions.VersionController(self.http_client) python-glareclient-0.5.2/glareclient/v1/versions.py0000666000175100017510000000166113230110105022403 0ustar zuulzuul00000000000000# Copyright 2015 OpenStack Foundation # Copyright 2015 Huawei Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. class VersionController(object): def __init__(self, http_client): self.http_client = http_client def list(self): """List all versions.""" url = '/versions' resp, body = self.http_client.get(url) return body.get('versions', None) python-glareclient-0.5.2/glareclient/v1/__init__.py0000666000175100017510000000117013230110105022265 0ustar zuulzuul00000000000000# Copyright (c) 2015 Mirantis, 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. from glareclient.v1.client import Client # noqa python-glareclient-0.5.2/glareclient/v1/artifacts.py0000666000175100017510000003331613230110105022515 0ustar zuulzuul00000000000000# Copyright (c) 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os from oslo_serialization import jsonutils from oslo_utils import encodeutils import six from six.moves.urllib import parse from glareclient.common import utils from glareclient import exc class Controller(object): def __init__(self, http_client, type_name=None): self.http_client = http_client self.type_name = type_name self.default_page_size = 20 self.sort_dir_values = ('asc', 'desc') def _check_type_name(self, type_name): """Check that type name and type versions were specified.""" type_name = type_name or self.type_name if type_name is None: msg = "Type name must be specified" raise exc.HTTPBadRequest(msg) return type_name def _validate_sort_param(self, sort): """Validates sorting argument for invalid keys and directions values. :param sort: comma-separated list of sort keys with optional <:dir> after each key """ for sort_param in sort.strip().split(','): key, _sep, dir = sort_param.partition(':') if dir and dir not in self.sort_dir_values: msg = ('Invalid sort direction: %(sort_dir)s.' ' It must be one of the following: %(available)s.' ) % {'sort_dir': dir, 'available': ', '.join(self.sort_dir_values)} raise exc.HTTPBadRequest(msg) return sort def create(self, name, version='0.0.0', type_name=None, **kwargs): """Create an artifact of given type and version. :param name: name of creating artifact. :param version: semver string describing an artifact version """ type_name = self._check_type_name(type_name) kwargs.update({'name': name, 'version': version}) url = '/artifacts/%s' % type_name resp, body = self.http_client.post(url, json=kwargs) return body def update(self, artifact_id, type_name=None, remove_props=None, **kwargs): """Update attributes of an artifact. :param artifact_id: ID of the artifact to modify. :param remove_props: List of property names to remove :param \*\*kwargs: Artifact attribute names and their new values. """ type_name = self._check_type_name(type_name) url = '/artifacts/%s/%s' % (type_name, artifact_id) hdrs = {'Content-Type': 'application/json-patch+json'} changes = [] if remove_props: for prop_name in remove_props: if prop_name not in kwargs: if '/' in prop_name: # we remove all values in dicts and lists explicitly, # i.e. matadata/key or releases/1 changes.append({'op': 'remove', 'path': '/%s' % prop_name}) else: # in other cases we just replace the value with None changes.append({'op': 'replace', 'path': '/%s' % prop_name, 'value': None}) for prop_name in kwargs: changes.append({'op': 'add', 'path': '/%s' % prop_name, 'value': kwargs[prop_name]}) resp, body = self.http_client.patch(url, headers=hdrs, json=changes) return body def get(self, artifact_id, type_name=None): """Get information about an artifact. :param artifact_id: ID of the artifact to get. """ type_name = self._check_type_name(type_name) url = '/artifacts/%s/%s' % (type_name, artifact_id) resp, body = self.http_client.get(url) return body def get_by_name(self, name, version='latest', type_name=None): """Get information about an artifact by name. :param name: name of the artifact to get. :param version: version of the artifact to get :param type_name: type name of the artifact """ type_name = self._check_type_name(type_name) url = '/artifacts/%s?version=%s&name=%s' % (type_name, version, name) resp, body = self.http_client.get(url) arts = body.get('artifacts', body.get(type_name)) if not arts: msg = ('Artifact with name=%s and version=%s not found.' % (name, version)) raise exc.BadRequest(msg) if len(arts) > 1: if type_name != "all": output = "\n".join([ "Artifact: %s, owner: %s, visibility: %s" % ( i['id'], i['owner'], i['visibility']) for i in arts]) else: output = "\n".join([ "Artifact: %s, owner: %s, visibility: %s, type: %s" % ( i['id'], i['owner'], i['visibility'], i['type_name']) for i in arts]) msg = ( 'There are more then one artifact with name=%s and version=%s.' ' Please provide the concrete id from the list:\n%s' % (name, version, output)) raise exc.BadRequest(msg) return arts[0] def list(self, type_name=None, **kwargs): """Retrieve a listing of artifacts objects. :param page_size: Number of artifacts to request in each paginated request. :returns: generator over list of artifacts. """ type_name = self._check_type_name(type_name) limit = kwargs.get('limit') page_size = kwargs.get('page_size') or self.default_page_size def paginate(url, page_size, limit=None): next_url = url while True: if limit and page_size > limit: next_url = next_url.replace("limit=%s" % page_size, "limit=%s" % limit) resp, body = self.http_client.get(next_url) # For backward compatibility we also look for the list of # artifacts under the type_name section. # In current versions it should be located in 'artifacts'. for artifact in body.get('artifacts', body.get(type_name)): yield artifact if limit: limit -= 1 if limit <= 0: raise StopIteration try: next_url = body['next'] except KeyError: return filters = kwargs.get('filters', []) filters.append(('limit', page_size)) url_params = [] for param, items in filters: values = [items] if not isinstance(items, list) else items for value in values: if isinstance(value, six.string_types): value = encodeutils.safe_encode(value) url_params.append({param: value}) url = '/artifacts/%s?' % type_name for param in url_params: url = '%s&%s' % (url, parse.urlencode(param)) if 'sort' in kwargs: url = '%s&sort=%s' % (url, self._validate_sort_param( kwargs['sort'])) for artifact in paginate(url, page_size, limit): yield artifact def activate(self, artifact_id, type_name=None): """Set artifact status to 'active'. :param artifact_id: ID of the artifact to get. """ return self.update(artifact_id, type_name, status='active') def deactivate(self, artifact_id, type_name=None): """Set artifact status to 'deactivated'. :param artifact_id: ID of the artifact to get. """ return self.update(artifact_id, type_name, status='deactivated') def reactivate(self, artifact_id, type_name=None): """Set artifact status to 'active'. :param artifact_id: ID of the artifact to get. """ return self.update(artifact_id, type_name, status='active') def publish(self, artifact_id, type_name=None): """Set artifact visibility to 'public'. :param artifact_id: ID of the artifact to get. """ return self.update(artifact_id, type_name, visibility='public') def delete(self, artifact_id, type_name=None): """Delete an artifact and all its data. :param artifact_id: ID of the artifact to delete. """ type_name = self._check_type_name(type_name) url = '/artifacts/%s/%s' % (type_name, artifact_id) self.http_client.delete(url) def upload_blob(self, artifact_id, blob_property, data, type_name=None, content_type=None): """Upload blob data. :param artifact_id: ID of the artifact to download a blob :param blob_property: blob property name """ content_type = content_type or 'application/octet-stream' hdrs = {'Content-Type': content_type} type_name = self._check_type_name(type_name) content_length = None if isinstance(data, six.string_types): content_length = len(data) else: try: content_length = os.path.getsize(data.name) except Exception: # if for some reason we can't get the file size, then we just # ignore it. pass if content_length is not None: hdrs['Content-Length'] = str(content_length) url = '/artifacts/%s/%s/%s' % (type_name, artifact_id, blob_property) self.http_client.put(url, headers=hdrs, data=data) def add_external_location(self, artifact_id, blob_property, data, type_name=None): """Add external location. :param artifact_id: ID of the artifact to download a blob :param blob_property: blob property name """ content_type = 'application/vnd+openstack.glare-custom-location+json' type_name = self._check_type_name(type_name) hdrs = {'Content-Type': content_type} url = '/artifacts/%s/%s/%s' % (type_name, artifact_id, blob_property) try: data = jsonutils.dumps(data) except TypeError: raise exc.HTTPBadRequest("json is malformed.") self.http_client.put(url, headers=hdrs, data=data) def remove_external_location(self, artifact_id, blob_property, type_name=None): """Remove external location. :param artifact_id: ID of the artifact with external location to be removed :param blob_property: blob property name """ type_name = self._check_type_name(type_name) url = '/artifacts/%s/%s/%s' % (type_name, artifact_id, blob_property) self.http_client.delete(url) def download_blob(self, artifact_id, blob_property, type_name=None, do_checksum=True): """Get blob data. :param artifact_id: ID of the artifact to download a blob :param blob_property: blob property name :param do_checksum: Enable/disable checksum validation. """ type_name = self._check_type_name(type_name) url = '/artifacts/%s/%s/%s' % (type_name, artifact_id, blob_property) resp, body = self.http_client.get(url, redirect=False, stream=True, headers={"Accept": "*/*"}) return utils.ResponseBlobWrapper(resp, do_checksum) def get_type_list(self): """Get list of type names.""" resp, body = self.http_client.get('/schemas') type_list = [] for type_name, type_schema in six.iteritems(body['schemas']): type_list.append((type_name, type_schema['version'])) return type_list def get_type_schema(self, type_name=None): """Show schema of type name.""" type_name = self._check_type_name(type_name) url = '/schemas/%s' % type_name resp, body = self.http_client.get(url) return body['schemas'][type_name] def add_tag(self, artifact_id, tag_value, type_name=None): """Add tag to artifact. :param artifact_id: ID of the artifact to add a tag :param tag_value: value of the tag to add """ type_name = self._check_type_name(type_name) url = '/artifacts/%s/%s' % (type_name, artifact_id) resp, body = self.http_client.get(url) tags = body['tags'] if tag_value in tags: return body tags.append(tag_value) return self.update(artifact_id, type_name, tags=tags) def remove_tag(self, artifact_id, tag_value, type_name=None): """Remove tag from artifact. :param artifact_id: ID of the artifact to remove a tag :param tag_value: value of the tag to remove """ type_name = self._check_type_name(type_name) url = '/artifacts/%s/%s' % (type_name, artifact_id) resp, body = self.http_client.get(url) tags = body['tags'] if tag_value not in tags: return body tags.remove(tag_value) return self.update(artifact_id, type_name, tags=tags) python-glareclient-0.5.2/glareclient/exc.py0000666000175100017510000001132213230110105020757 0ustar zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import re import sys import six 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 or self.__class__.__name__ def __str__(self): return "%s (HTTP %s)" % (self.details, self.code) class HTTPMultipleChoices(HTTPException): code = 300 def __str__(self): self.details = ("Requested version of OpenStack Glare 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, body=None): """Return an instance of an HTTPException based on httplib response.""" cls = _code_map.get(response.status_code, HTTPException) if body and 'json' in response.headers['content-type']: # Iterate over the nested objects and retrieve the "message" attribute. messages = [response.json().get('error').get('message')] # Join all of the messages together nicely and filter out any objects # that don't have a "message" attr. details = '\n'.join(i for i in messages if i is not None) return cls(details=details) elif body and 'html' in response.headers['content-type']: # Split the lines, strip whitespace and inline HTML from the response. details = [re.sub(r'<.+?>', '', i.strip()) for i in response.text.splitlines()] details = [i for i in details if i] # Remove duplicates from the list. details_seen = set() details_temp = [] for i in details: if i not in details_seen: details_temp.append(i) details_seen.add(i) # Return joined string separated by colons. details = ': '.join(details_temp) return cls(details=details) elif body: if six.PY3: body = body.decode('utf-8') details = body.replace('\n\n', '\n') return cls(details=details) return cls() class NoTokenLookupException(Exception): """DEPRECATED!""" pass class EndpointNotFound(Exception): """DEPRECATED!""" pass class SSLConfigurationError(BaseException): pass class SSLCertificateError(BaseException): pass python-glareclient-0.5.2/glareclient/shell.py0000666000175100017510000002736113230110105021321 0ustar zuulzuul00000000000000# Copyright 2017 - Nokia Networks # # 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 Glare APIs """ import argparse import logging import sys from cliff import app from cliff import commandmanager from osc_lib.command import command from glareclient import client from glareclient.common import utils import glareclient.osc.v1.artifacts import glareclient.osc.v1.blobs class OpenStackHelpFormatter(argparse.HelpFormatter): def __init__(self, prog, indent_increment=2, max_help_position=32, width=None): super(OpenStackHelpFormatter, self).__init__( prog, indent_increment, max_help_position, width ) def start_section(self, heading): # Title-case the headings. heading = '%s%s' % (heading[0].upper(), heading[1:]) super(OpenStackHelpFormatter, self).start_section(heading) class HelpAction(argparse.Action): """Custom help action. Provide a custom action so the -h and --help options to the main app will print a list of the commands. The commands are determined by checking the CommandManager instance, passed in as the "default" value for the action. """ def __call__(self, parser, namespace, values, option_string=None): outputs = [] max_len = 0 app = self.default parser.print_help(app.stdout) app.stdout.write('\nCommands for API v1 :\n') for name, ep in sorted(app.command_manager): factory = ep.load() cmd = factory(self, None) one_liner = cmd.get_description().split('\n')[0] outputs.append((name, one_liner)) max_len = max(len(name), max_len) for (name, one_liner) in outputs: app.stdout.write(' %s %s\n' % (name.ljust(max_len), one_liner)) sys.exit(0) class BashCompletionCommand(command.Command): """Prints all of the commands and options for bash-completion.""" def take_action(self, parsed_args): commands = set() options = set() for option, _action in self.app.parser._option_string_actions.items(): options.add(option) for command_name, _cmd in self.app.command_manager: commands.add(command_name) print(' '.join(commands | options)) class GlareShell(app.App): def __init__(self): super(GlareShell, self).__init__( description=__doc__.strip(), version=glareclient.__version__, command_manager=commandmanager.CommandManager('glare.cli'), ) self._set_shell_commands(self._get_commands()) def configure_logging(self): log_lvl = logging.DEBUG if self.options.debug else logging.WARNING logging.basicConfig( format="%(levelname)s (%(module)s) %(message)s", level=log_lvl ) logging.getLogger('iso8601').setLevel(logging.WARNING) if self.options.verbose_level <= 1: logging.getLogger('requests').setLevel(logging.WARNING) def build_option_parser(self, description, version, argparse_kwargs=None): """Return an argparse option parser for this application. Subclasses may override this method to extend the parser with more global options. :param description: full description of the application :paramtype description: str :param version: version number for the application :paramtype version: str :param argparse_kwargs: extra keyword argument passed to the ArgumentParser constructor :paramtype extra_kwargs: dict """ argparse_kwargs = argparse_kwargs or {} parser = argparse.ArgumentParser( description=description, add_help=False, formatter_class=OpenStackHelpFormatter, **argparse_kwargs ) parser.add_argument( '--version', action='version', version='%(prog)s {0}'.format(version), help='Show program\'s version number and exit.' ) parser.add_argument( '-v', '--verbose', action='count', dest='verbose_level', default=self.DEFAULT_VERBOSE_LEVEL, help='Increase verbosity of output. Can be repeated.', ) parser.add_argument( '--log-file', action='store', default=None, help='Specify a file to log output. Disabled by default.', ) parser.add_argument( '-q', '--quiet', action='store_const', dest='verbose_level', const=0, help='Suppress output except warnings and errors.', ) parser.add_argument( '-h', '--help', action=HelpAction, nargs=0, default=self, # tricky help="Show this help message and exit.", ) parser.add_argument( '--debug', default=False, action='store_true', help='Show tracebacks on errors.', ) parser.add_argument( '--os-glare-url', action='store', dest='glare_url', default=utils.env('OS_GLARE_URL'), help='Glare API host (Env: OS_GLARE_URL)' ) parser.add_argument( '--os-glare-version', action='store', dest='glare_version', default=utils.env('OS_GLARE_VERSION', default='v1'), help='Glare API version (default = v1) (Env: ' 'OS_GLARE_VERSION)' ) parser.add_argument( '--keycloak-auth-url', action='store', dest='keycloak_auth_url', default=utils.env('KEYCLOAK_AUTH_URL'), help='Keycloak auth url (Env: KEYCLOAK_AUTH_URL)') parser.add_argument( '--openid-client-id', action='store', dest='openid_client_id', default=utils.env('OPENID_CLIENT_ID') or 'admin-cli', help='Client ID (according to OpenID Connect)' ' (Env: OPENID_CLIENT_ID)') parser.add_argument( '--auth-token', action='store', dest='auth_token', default=utils.env('AUTH_TOKEN'), help='Authentication token (Env: AUTH_TOKEN)') parser.add_argument( '--keycloak-realm-name', action='store', dest='keycloak_realm_name', default=utils.env('KEYCLOAK_REALM_NAME'), help='With keycloak glare auth type: Realm name to scope to' ' (Env: KEYCLOAK_REALM_NAME)') parser.add_argument( '--keycloak-username', action='store', dest='keycloak_username', default=utils.env('KEYCLOAK_USERNAME'), help='Keycloak username (Env: KEYCLOAK_USERNAME)') parser.add_argument( '--keycloak-password', action='store', dest='keycloak_password', default=utils.env('KEYCLOAK_PASSWORD'), help='Keycloak user password (Env: KEYCLOAK_PASSWORD)') parser.add_argument( '--cert', action='store', dest='cert_file', default=utils.env('OS_GLARE_CERT'), help='Client Certificate (Env: OS_GLARE_CERT)' ) parser.add_argument( '--key', action='store', dest='key_file', default=utils.env('OS_GLARE_KEY'), help='Client Key (Env: OS_GLARE_KEY)' ) parser.add_argument( '--cacert', action='store', dest='cacert', default=utils.env('OS_GLARE_CACERT'), help='Authentication CA Certificate (Env: OS_GLARE_CACERT)' ) parser.add_argument( '--insecure', action='store_true', dest='insecure', default=utils.env('GLARECLIENT_INSECURE', default=False), help='Disables SSL/TLS certificate verification ' '(Env: GLARELCLIENT_INSECURE)' ) return parser def initialize_app(self, argv): self._clear_shell_commands() self._set_shell_commands(self._get_commands()) # bash-completion and help messages should not require client creation need_client = not ( ('bash-completion' in argv) or ('help' in argv) or ('-h' in argv) or ('--help' in argv) or not argv) self.client = self._create_client() if need_client else None # Adding client_manager variable to make glare client work with # unified OpenStack client. ClientManager = type( 'ClientManager', (object,), dict(artifact=self.client) ) self.client_manager = ClientManager() def _create_client(self): return client.Client( endpoint=self.options.glare_url, auth_token=self.options.auth_token, keycloak_auth_url=self.options.keycloak_auth_url, openid_client_id=self.options.openid_client_id, keycloak_realm_name=self.options.keycloak_realm_name, keycloak_username=self.options.keycloak_username, keycloak_password=self.options.keycloak_password, cert_file=self.options.cert_file, key_file=self.options.key_file, cacert=self.options.cacert, insecure=self.options.insecure ) def _set_shell_commands(self, cmds_dict): for k, v in cmds_dict.items(): self.command_manager.add_command(k, v) def _clear_shell_commands(self): exclude_cmds = ['help', 'complete'] cmds = self.command_manager.commands.copy() for k, v in cmds.items(): if k not in exclude_cmds: self.command_manager.commands.pop(k) @staticmethod def _get_commands(): return { 'bash-completion': BashCompletionCommand, 'list': glareclient.osc.v1.artifacts.ListArtifacts, 'show': glareclient.osc.v1.artifacts.ShowArtifact, 'create': glareclient.osc.v1.artifacts.CreateArtifact, 'delete': glareclient.osc.v1.artifacts.DeleteArtifact, 'update': glareclient.osc.v1.artifacts.UpdateArtifact, 'activate': glareclient.osc.v1.artifacts.ActivateArtifact, 'deactivate': glareclient.osc.v1.artifacts.DeactivateArtifact, 'reactivate': glareclient.osc.v1.artifacts.ReactivateArtifact, 'publish': glareclient.osc.v1.artifacts.PublishArtifact, 'add-tag': glareclient.osc.v1.artifacts.AddTag, 'remove-tag': glareclient.osc.v1.artifacts.RemoveTag, 'type-list': glareclient.osc.v1.artifacts.TypeList, 'schema': glareclient.osc.v1.artifacts.TypeSchema, 'upload': glareclient.osc.v1.blobs.UploadBlob, 'download': glareclient.osc.v1.blobs.DownloadBlob, 'location': glareclient.osc.v1.blobs.AddLocation, 'remove-location': glareclient.osc.v1.blobs.RemoveLocation } def main(argv=sys.argv[1:]): return GlareShell().run(argv) if __name__ == '__main__': sys.exit(main(sys.argv[1:])) python-glareclient-0.5.2/glareclient/tests/0000775000175100017510000000000013230110365020777 5ustar zuulzuul00000000000000python-glareclient-0.5.2/glareclient/tests/__init__.py0000666000175100017510000000000013230110105023070 0ustar zuulzuul00000000000000python-glareclient-0.5.2/glareclient/tests/unit/0000775000175100017510000000000013230110365021756 5ustar zuulzuul00000000000000python-glareclient-0.5.2/glareclient/tests/unit/test_shell.py0000666000175100017510000000363713230110105024501 0ustar zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # 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 os import sys import six import testtools from glareclient import shell class TestShell(testtools.TestCase): def shell(self, argstr): orig = (sys.stdout, sys.stderr) clean_env = {} _old_env, os.environ = os.environ, clean_env.copy() try: sys.stdout = six.moves.cStringIO() sys.stderr = six.moves.cStringIO() _shell = shell.GlareShell() _shell.run(argstr.split()) except SystemExit: exc_type, exc_value, exc_traceback = sys.exc_info() self.assertEqual(0, exc_value.code) finally: stdout = sys.stdout.getvalue() stderr = sys.stderr.getvalue() sys.stdout.close() sys.stderr.close() sys.stdout, sys.stderr = orig os.environ = _old_env return stdout, stderr def test_help(self): """Test that client is not created for help and bash complete""" for command in ('-h', '--help', 'help', 'help workbook-list', 'bash-completion'): with mock.patch('glareclient.client.Client') as client_mock: self.shell(command) self.assertFalse(client_mock.called) python-glareclient-0.5.2/glareclient/tests/unit/test_utils.py0000666000175100017510000000230213230110105024516 0ustar zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import testtools from glareclient.common import utils class TestUtils(testtools.TestCase): def test_make_size_human_readable(self): self.assertEqual("106B", utils.make_size_human_readable(106)) self.assertEqual("1000kB", utils.make_size_human_readable(1024000)) self.assertEqual("1MB", utils.make_size_human_readable(1048576)) self.assertEqual("1.4GB", utils.make_size_human_readable(1476395008)) self.assertEqual("9.3MB", utils.make_size_human_readable(9761280)) self.assertEqual("0B", utils.make_size_human_readable(None)) python-glareclient-0.5.2/glareclient/tests/unit/osc/0000775000175100017510000000000013230110365022542 5ustar zuulzuul00000000000000python-glareclient-0.5.2/glareclient/tests/unit/osc/__init__.py0000666000175100017510000000000013230110105024633 0ustar zuulzuul00000000000000python-glareclient-0.5.2/glareclient/tests/unit/osc/test_plugin.py0000666000175100017510000000250113230110105025441 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import mock from glareclient.osc import plugin from glareclient.tests.unit import base class TestArtifactPlugin(base.TestCaseShell): @mock.patch("glareclient.v1.client.Client") def test_make_client(self, p_client): instance = mock.Mock() instance._api_version = {"artifact": '1'} instance._region_name = 'glare_region' instance.session = 'glare_session' instance.get_configuration.return_value = { 'glare_url': 'http://example.com:9494/', 'keycloak_auth_url': None} plugin.make_client(instance) p_client.assert_called_with( 'http://example.com:9494/', region_name='glare_region', session='glare_session', service_type='artifact') python-glareclient-0.5.2/glareclient/tests/unit/osc/v1/0000775000175100017510000000000013230110365023070 5ustar zuulzuul00000000000000python-glareclient-0.5.2/glareclient/tests/unit/osc/v1/__init__.py0000666000175100017510000000000013230110105025161 0ustar zuulzuul00000000000000python-glareclient-0.5.2/glareclient/tests/unit/osc/v1/fakes_schemas.py0000666000175100017510000004205413230110105026235 0ustar zuulzuul00000000000000# Copyright 2016 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. FIXTURE_SCHEMA = { u'name': u'sample_artifact', u'properties': { u'activated_at': { u'description': u'Datetime when artifact has became active.', u'filter_ops': [u'eq', u'neq', u'in', u'gt', u'gte', u'lt', u'lte'], u'format': u'date-time', u'glareType': u'DateTime', u'readOnly': True, u'required_on_activate': False, u'sortable': True, u'type': [u'string', u'null']}, u'created_at': { u'description': u'Datetime when artifact has been created.', u'filter_ops': [u'eq', u'neq', u'in', u'gt', u'gte', u'lt', u'lte'], u'format': u'date-time', u'glareType': u'DateTime', u'readOnly': True, u'sortable': True, u'type': u'string'}, u'description': {u'default': u'', u'description': u'Artifact description.', u'filter_ops': [u'eq', u'neq', u'in'], u'glareType': u'String', u'maxLength': 4096, u'mutable': True, u'required_on_activate': False, u'type': [u'string', u'null']}, u'icon': {u'additionalProperties': False, u'description': u'Artifact icon.', u'filter_ops': [], u'glareType': u'Blob', u'properties': {u'md5': {u'type': [u'string', u'null']}, u'sha1': {u'type': [u'string', u'null']}, u'sha256': {u'type': [u'string', u'null']}, u'content_type': {u'type': u'string'}, u'external': {u'type': u'boolean'}, u'size': {u'type': [u'number', u'null']}, u'status': {u'enum': [u'saving', u'active', u'pending_delete'], u'type': u'string'}}, u'required': [u'size', u'md5', u'sha1', u'sha256', u'external', u'status', u'content_type'], u'required_on_activate': False, u'type': [u'object', u'null']}, u'id': {u'description': u'Artifact UUID.', u'filter_ops': [u'eq', u'neq', u'in'], u'glareType': u'String', u'maxLength': 255, u'pattern': u'^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-' u'fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}$', u'readOnly': True, u'sortable': True, u'type': u'string'}, u'license': {u'description': u'Artifact license type.', u'filter_ops': [u'eq', u'neq', u'in'], u'glareType': u'String', u'maxLength': 255, u'required_on_activate': False, u'type': [u'string', u'null']}, u'license_url': {u'description': u'URL to artifact license.', u'filter_ops': [u'eq', u'neq', u'in'], u'glareType': u'String', u'maxLength': 255, u'required_on_activate': False, u'type': [u'string', u'null']}, u'metadata': {u'additionalProperties': {u'type': u'string'}, u'default': {}, u'description': u'Key-value dict with useful information' u'about an artifact.', u'filter_ops': [u'eq', u'neq'], u'glareType': u'StringDict', u'maxProperties': 255, u'required_on_activate': False, u'type': [u'object', u'null']}, u'name': {u'description': u'Artifact Name.', u'filter_ops': [u'eq', u'neq', u'in'], u'glareType': u'String', u'maxLength': 255, u'required_on_activate': False, u'sortable': True, u'type': u'string'}, u'owner': {u'description': u'ID of user/tenant who uploaded artifact.', u'filter_ops': [u'eq', u'neq', u'in'], u'glareType': u'String', u'maxLength': 255, u'readOnly': True, u'required_on_activate': False, u'sortable': True, u'type': u'string'}, u'provided_by': {u'additionalProperties': False, u'description': u'Info about artifact authors.', u'filter_ops': [u'eq', u'neq', u'in'], u'glareType': u'StringDict', u'maxProperties': 255, u'properties': {u'company': {u'type': u'string'}, u'href': {u'type': u'string'}, u'name': {u'type': u'string'}}, u'required_on_activate': False, u'type': [u'object', u'null']}, u'release': {u'default': [], u'description': u'Target OpenStack release for artifact. ' u'It is usually the same when artifact ' u'was uploaded.', u'filter_ops': [u'eq', u'neq', u'in'], u'glareType': u'StringList', u'items': {u'type': u'string'}, u'maxItems': 255, u'required_on_activate': False, u'type': [u'array', u'null'], u'unique': True}, u'status': {u'default': u'drafted', u'description': u'Artifact status.', u'enum': [u'drafted', u'active', u'deactivated', u'deleted'], u'filter_ops': [u'eq', u'neq', u'in'], u'glareType': u'String', u'sortable': True, u'type': u'string'}, u'supported_by': {u'additionalProperties': {u'type': u'string'}, u'description': u'Info about persons who ' u'responsible for artifact support', u'filter_ops': [u'eq', u'neq', u'in'], u'glareType': u'StringDict', u'maxProperties': 255, u'required': [u'name'], u'required_on_activate': False, u'type': [u'object', u'null']}, u'tags': {u'default': [], u'description': u'List of tags added to Artifact.', u'filter_ops': [u'eq', u'neq', u'in'], u'glareType': u'StringList', u'items': {u'type': u'string'}, u'maxItems': 255, u'mutable': True, u'required_on_activate': False, u'type': [u'array', u'null']}, u'updated_at': { u'description': u'Datetime when artifact has been updated ' u'last time.', u'filter_ops': [u'eq', u'neq', u'in', u'gt', u'gte', u'lt', u'lte'], u'format': u'date-time', u'glareType': u'DateTime', u'readOnly': True, u'sortable': True, u'type': u'string'}, u'version': {u'default': u'0.0.0', u'description': u'Artifact version(semver).', u'filter_ops': [u'eq', u'neq', u'in', u'gt', u'gte', u'lt', u'lte'], u'glareType': u'String', u'pattern': u'/^([0-9]+)\\.([0-9]+)\\.([0-9]+)(?:-' u'([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?' u'(?:\\+[0-9A-Za-z-]+)?$/', u'required_on_activate': False, u'sortable': True, u'type': u'string'}, u'visibility': {u'default': u'private', u'description': u'Artifact visibility that defines if ' u'artifact can be available to other ' u'users.', u'filter_ops': [u'eq'], u'glareType': u'String', u'maxLength': 255, u'sortable': True, u'type': u'string'}, u'image': {u'additionalProperties': False, u'description': u'Image binary.', u'filter_ops': [], u'glareType': u'Blob', u'properties': { u'md5': {u'type': [u'string', u'null']}, u'sha1': {u'type': [u'string', u'null']}, u'sha256': {u'type': [u'string', u'null']}, u'content_type': {u'type': u'string'}, u'external': {u'type': u'boolean'}, u'size': {u'type': [u'number', u'null']}, u'status': {u'enum': [u'saving', u'active', u'pending_delete'], u'type': u'string'}}, u'required': [u'size', u'md5', u'sha1', u'sha256', u'external', u'status', u'content_type'], u'required_on_activate': False, u'type': [u'object', u'null']}, u'package': { u'additionalProperties': False, u'description': u'Murano Package binary.', u'filter_ops': [], u'glareType': u'Blob', u'properties': {u'md5': {u'type': [u'string', u'null']}, u'sha1': {u'type': [u'string', u'null']}, u'sha256': {u'type': [u'string', u'null']}, u'content_type': {u'type': u'string'}, u'external': {u'type': u'boolean'}, u'size': {u'type': [u'number', u'null']}, u'status': {u'enum': [u'saving', u'active', u'pending_delete'], u'type': u'string'}}, u'required': [u'size', u'md5', u'sha1', u'sha256', u'external', u'status', u'content_type'], u'required_on_activate': False, u'type': [u'object', u'null']}, u'environment': { u'additionalProperties': False, u'description': u'Heat Environment text body.', u'filter_ops': [], u'glareType': u'Blob', u'properties': {u'md5': {u'type': [u'string', u'null']}, u'sha1': {u'type': [u'string', u'null']}, u'sha256': {u'type': [u'string', u'null']}, u'content_type': {u'type': u'string'}, u'external': {u'type': u'boolean'}, u'size': {u'type': [u'number', u'null']}, u'status': {u'enum': [u'saving', u'active', u'pending_delete'], u'type': u'string'}}, u'required': [u'size', u'md5', u'sha1', u'sha256', u'external', u'status', u'content_type'], u'type': [u'object', u'null']}, u'blob': { u'additionalProperties': False, u'description': u'I am Blob', u'filter_ops': [], u'glareType': u'Blob', u'mutable': True, u'properties': { u'md5': {u'type': [u'string', u'null']}, u'sha1': {u'type': [u'string', u'null']}, u'sha256': {u'type': [u'string', u'null']}, u'content_type': { u'type': u'string'}, u'external': { u'type': u'boolean'}, u'size': {u'type': [ u'number', u'null']}, u'status': { u'enum': [ u'saving', u'active', u'pending_delete'], u'type': u'string'}}, u'required': [u'size', u'md5', u'sha1', u'sha256', u'external', u'status', u'content_type'], u'required_on_activate': False, u'type': [u'object', u'null']}, u'template': { u'additionalProperties': False, u'description': u'TOSCA template body.', u'filter_ops': [], u'glareType': u'Blob', u'properties': { u'md5': {u'type': [u'string', u'null']}, u'sha1': {u'type': [u'string', u'null']}, u'sha256': {u'type': [u'string', u'null']}, u'content_type': { u'type': u'string'}, u'external': {u'type': u'boolean'}, u'size': {u'type': [u'number', u'null']}, u'status': {u'enum': [u'saving', u'active', u'pending_delete'], u'type': u'string'}}, u'required': [u'size', u'md5', u'sha1', u'sha256', u'external', u'status', u'content_type'], u'type': [u'object', u'null']}, } } python-glareclient-0.5.2/glareclient/tests/unit/osc/v1/test_blob.py0000666000175100017510000002564113230110105025421 0ustar zuulzuul00000000000000# Copyright (c) 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import mock from glareclient import exc from glareclient.osc.v1 import blobs as osc_blob from glareclient.tests.unit.osc.v1 import fakes from glareclient.v1 import artifacts as api_art import testtools class TestUpload(testtools.TestCase): def setUp(self): self.mock_app = mock.Mock() self.mock_args = mock.Mock() self.mock_manager = mock.Mock() self.mock_manager.artifacts.get.return_value = {'image': {}} super(TestUpload, self).setUp() @mock.patch('glareclient.osc.v1.blobs.progressbar') @mock.patch('glareclient.osc.v1.blobs.sys') @mock.patch('glareclient.osc.v1.blobs.open', create=True) @mock.patch('glareclient.common.utils.get_artifact_id') def test_upload_file_progress(self, mock_get_id, mock_open, mock_sys, mock_progressbar): mock_parsed_args = mock.Mock(name='test-id', id=True, blob_property='image', file='/path/file', progress=True, content_type='application/test', type_name='test-type') mock_get_id.return_value = 'test-id' cli = osc_blob.UploadBlob(self.mock_app, self.mock_args) cli.app.client_manager.artifact = self.mock_manager cli.dict2columns = mock.Mock(return_value=42) self.assertEqual(42, cli.take_action(mock_parsed_args)) cli.dict2columns.assert_called_once_with({'blob_property': 'image'}) upload_args = ['test-id', 'image', mock_progressbar.VerboseFileWrapper.return_value] upload_kwargs = {'content_type': 'application/test', 'type_name': 'test-type'} self.mock_manager.artifacts.upload_blob.\ assert_called_once_with(*upload_args, **upload_kwargs) @mock.patch('glareclient.osc.v1.blobs.sys') @mock.patch('glareclient.osc.v1.blobs.open', create=True) @mock.patch('glareclient.common.utils.get_artifact_id') def test_upload_file_no_progress(self, mock_get_id, mock_open, mock_sys): mock_parsed_args = mock.Mock(name='test-id', id=True, blob_property='image', progress=False, file='/path/file', content_type='application/test', type_name='test-type') mock_get_id.return_value = 'test-id' cli = osc_blob.UploadBlob(self.mock_app, self.mock_args) cli.app.client_manager.artifact = self.mock_manager cli.dict2columns = mock.Mock(return_value=42) self.assertEqual(42, cli.take_action(mock_parsed_args)) cli.dict2columns.assert_called_once_with({'blob_property': 'image'}) upload_args = ['test-id', 'image', mock_open.return_value] upload_kwargs = {'content_type': 'application/test', 'type_name': 'test-type'} self.mock_manager.artifacts.upload_blob.\ assert_called_once_with(*upload_args, **upload_kwargs) @mock.patch('glareclient.osc.v1.blobs.sys') @mock.patch('glareclient.common.utils.get_artifact_id') def test_upload_file_stdin(self, mock_get_id, mock_sys): mock_sys.stdin.isatty.return_value = False mock_parsed_args = mock.Mock(name='test-id', id=True, blob_property='image', progress=False, file=None, content_type='application/test', type_name='test-type') mock_get_id.return_value = 'test-id' cli = osc_blob.UploadBlob(self.mock_app, self.mock_args) cli.app.client_manager.artifact = self.mock_manager cli.dict2columns = mock.Mock(return_value=42) self.assertEqual(42, cli.take_action(mock_parsed_args)) cli.dict2columns.assert_called_once_with({'blob_property': 'image'}) upload_args = ['test-id', 'image', mock_sys.stdin] upload_kwargs = {'content_type': 'application/test', 'type_name': 'test-type'} self.mock_manager.artifacts.upload_blob.\ assert_called_once_with(*upload_args, **upload_kwargs) @mock.patch('glareclient.osc.v1.blobs.sys') def test_upload_file_stdin_isatty(self, mock_sys): mock_sys.stdin.isatty.return_value = True mock_parsed_args = mock.Mock(id='test-id', blob_property='image', progress=False, file=None, content_type='application/test', type_name='test-type') cli = osc_blob.UploadBlob(self.mock_app, self.mock_args) cli.app.client_manager.artifact = self.mock_manager self.assertRaises(exc.CommandError, cli.take_action, mock_parsed_args) class TestBlobs(fakes.TestArtifacts): def setUp(self): super(TestBlobs, self).setUp() self.blob_mock = \ self.app.client_manager.artifact.blobs self.http = mock.MagicMock() class TestDownloadBlob(TestBlobs): def setUp(self): super(TestDownloadBlob, self).setUp() self.blob_mock.call.return_value = \ api_art.Controller(self.http, type_name='images') # Command to test self.cmd = osc_blob.DownloadBlob(self.app, None) self.COLUMNS = ('blob_property', 'id', 'name', 'size', 'status', 'version') def test_download_exception(self): arglist = ['images', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', '--blob-property', 'blob', '--file', None] verify = [('type_name', 'images')] parsed_args = self.check_parser(self.cmd, arglist, verify) with testtools.ExpectedException(SystemExit): self.cmd.take_action(parsed_args) def test_download_blob(self): arglist = ['images', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', '--blob-property', 'blob', '--file', '/path/to/file'] verify = [('type_name', 'images')] parsed_args = self.check_parser(self.cmd, arglist, verify) self.cmd.take_action(parsed_args) def test_download_without_blob_property(self): arglist = ['images', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', '--file', '/path/to/file'] verify = [('type_name', 'images')] parsed_args = self.check_parser(self.cmd, arglist, verify) self.cmd.take_action(parsed_args) def test_download_progress(self): arglist = ['images', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', '--file', '/path/to/file', '--progress'] verify = [('type_name', 'images')] parsed_args = self.check_parser(self.cmd, arglist, verify) self.cmd.take_action(parsed_args) class TestAddLocation(TestBlobs): def setUp(self): super(TestAddLocation, self).setUp() self.blob_mock.call.return_value = \ api_art.Controller(self.http, type_name='images') # Command to test self.cmd = osc_blob.AddLocation(self.app, None) self.COLUMNS = ('blob_property', 'content_type', 'external', 'md5', 'sha1', 'sha256', 'size', 'status', 'url') def test_add_location(self): arglist = ['images', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', '--id', '--url', 'fake_url', '--md5', "35d83e8eedfbdb87ff97d1f2761f8ebf", '--sha1', "942854360eeec1335537702399c5aed940401602", '--sha256', "d8a7834fc6652f316322d80196f6dcf2" "94417030e37c15412e4deb7a67a367dd"] verify = [('type_name', 'images'), ('url', 'fake_url')] parsed_args = self.check_parser(self.cmd, arglist, verify) columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.COLUMNS, columns) def test_add_dict_location(self): arglist = ['images', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', '--id', '--blob-property', 'nested_templates/blob', '--url', 'fake_url', '--md5', "35d83e8eedfbdb87ff97d1f2761f8ebf", '--sha1', "942854360eeec1335537702399c5aed940401602", '--sha256', "d8a7834fc6652f316322d80196f6dcf2" "94417030e37c15412e4deb7a67a367dd"] verify = [('type_name', 'images'), ('url', 'fake_url')] parsed_args = self.check_parser(self.cmd, arglist, verify) self.app.client_manager.artifact.artifacts.get = \ lambda *args, **kwargs: { 'nested_templates': {'blob': fakes.blob_fixture} } columns, data = self.cmd.take_action(parsed_args) self.app.client_manager.artifact.artifacts.get = fakes.mock_get self.assertEqual(self.COLUMNS, columns) class TestRemoveLocation(TestBlobs): def setUp(self): super(TestRemoveLocation, self).setUp() self.blob_mock.call.return_value = \ api_art.Controller(self.http, type_name='images') # Command to test self.cmd = osc_blob.RemoveLocation(self.app, None) def test_remove_location(self): arglist = ['images', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', '--id'] verify = [('type_name', 'images'), ('name', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba'), ('id', True)] parsed_args = self.check_parser(self.cmd, arglist, verify) self.assertIsNone(self.cmd.take_action(parsed_args)) def test_remove_dict_location(self): arglist = ['images', '--blob-property', 'nested_templates/blob', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', '--id'] verify = [('type_name', 'images'), ('name', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba'), ('id', True)] parsed_args = self.check_parser(self.cmd, arglist, verify) self.assertIsNone(self.cmd.take_action(parsed_args)) python-glareclient-0.5.2/glareclient/tests/unit/osc/v1/test_artifacts.py0000666000175100017510000005230013230110105026453 0ustar zuulzuul00000000000000# Copyright (c) 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import mock import testtools from glareclient.osc.v1 import artifacts as osc_art from glareclient.tests.unit.osc.v1 import fakes from glareclient.v1 import artifacts as api_art from osc_lib.tests.utils import ParserException class TestArtifacts(fakes.TestArtifacts): def setUp(self): super(TestArtifacts, self).setUp() self.artifact_mock = \ self.app.client_manager.artifact.artifacts self.http = mock.MagicMock() self.COLUMNS = set(['id', 'name', 'owner', 'status', 'version', 'visibility']) class TestListArtifacts(TestArtifacts): def setUp(self): super(TestListArtifacts, self).setUp() self.artifact_mock.call.return_value = \ api_art.Controller(self.http, type_name='images') # Command to test self.cmd = osc_art.ListArtifacts(self.app, None) self.COLUMNS = ['Id', 'Name', 'Version', 'Owner', 'Visibility', 'Status'] def test_artifact_list(self): arglist = ['images'] verify = [('type_name', 'images')] parsed_args = self.check_parser(self.cmd, arglist, verify) columns, data = self.cmd.take_action(parsed_args) # Check that columns are correct self.assertEqual(self.COLUMNS, columns) self.check_parser(self.cmd, arglist, verify) def test_artifact_list_all(self): arglist = ['all'] verify = [('type_name', 'all')] parsed_args = self.check_parser(self.cmd, arglist, verify) columns, data = self.cmd.take_action(parsed_args) # Check that columns are correct self.assertEqual(['Id', 'Name', 'Version', 'Type name', 'Owner', 'Visibility', 'Status'], columns) self.check_parser(self.cmd, arglist, verify) def test_artifact_list_with_multifilters(self): arglist = ['images', '--filter', 'spam:spam', '--filter', 'maps:maps'] verify = [('type_name', 'images'), ('filter', ['spam:spam', 'maps:maps'])] self.check_parser(self.cmd, arglist, verify) def test_artifact_list_with_sort(self): arglist = ['sample_artifact', '--sort', 'name:asc'] verify = [('type_name', 'sample_artifact'), ('sort', 'name:asc')] self.check_parser(self.cmd, arglist, verify) def test_artifact_list_with_multisort(self): arglist = ['images', '--sort', 'name:desc', '--sort', 'name:asc'] verify = [('type_name', 'images'), ('sort', 'name:asc')] self.check_parser(self.cmd, arglist, verify) def test_artifact_list_page_size(self): arglist = ['images', '--page-size', '1'] verify = [('type_name', 'images'), ('page_size', 1)] self.check_parser(self.cmd, arglist, verify) def test_artifact_list_limit(self): arglist = ['images', '--limit', '2'] verify = [('type_name', 'images'), ('limit', 2)] self.check_parser(self.cmd, arglist, verify) def test_artifact_list_multilimit(self): arglist = ['images', '--limit', '2', '--limit', '1'] verify = [('type_name', 'images'), ('limit', 1)] self.check_parser(self.cmd, arglist, verify) class TestShowArtifacts(TestArtifacts): def setUp(self): super(TestShowArtifacts, self).setUp() self.artifact_mock.call.return_value = \ api_art.Controller(self.http, type_name='images') # Command to test self.cmd = osc_art.ShowArtifact(self.app, None) def test_artifact_show(self): arglist = ['images', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba'] verify = [('type_name', 'images')] COLUMNS = set(['blob', 'environment', 'id', 'image', 'name', 'owner', 'package', 'status', 'template', 'version', 'visibility']) parsed_args = self.check_parser(self.cmd, arglist, verify) columns, data = self.cmd.take_action(parsed_args) name_fields = set([column[0] for column in data]) # Check that columns are correct self.assertEqual(COLUMNS, name_fields) def test_artifact_show_without_id(self): arglist = ['images'] verify = [('type_name', 'images')] with testtools.ExpectedException(ParserException): self.check_parser(self.cmd, arglist, verify) def test_artifact_show_without_type_id(self): arglist = ['fc15c365-d4f9-4b8b-a090-d9e230f1f6ba'] verify = [('type_name', 'images')] with testtools.ExpectedException(ParserException): self.check_parser(self.cmd, arglist, verify) def test_artifact_show_by_name(self): arglist = ['images', 'name1'] verify = [('type_name', 'images'), ('id', False)] COLUMNS = set(['blob', 'environment', 'id', 'image', 'name', 'owner', 'package', 'status', 'template', 'version', 'visibility']) parsed_args = self.check_parser(self.cmd, arglist, verify) columns, data = self.cmd.take_action(parsed_args) name_fields = set([column[0] for column in data]) # Check that columns are correct self.assertEqual(COLUMNS, name_fields) class TestCreateArtifacts(TestArtifacts): def setUp(self): super(TestCreateArtifacts, self).setUp() self.artifact_mock.call.return_value = \ api_art.Controller(self.http, type_name='images') # Command to test self.cmd = osc_art.CreateArtifact(self.app, None) def test_create_artifact(self): arglist = ['images', 'art', '--artifact-version', '0.2.4', '--property', 'blah=10'] verify = [('type_name', 'images'), ('property', ['blah=10']), ('artifact_version', '0.2.4')] parsed_args = self.check_parser(self.cmd, arglist, verify) columns, data = self.cmd.take_action(parsed_args) name_fields = set([column[0] for column in data]) # Check that columns are correct self.assertEqual(self.COLUMNS, name_fields) def test_create_artifact_list_prop(self): arglist = ['images', 'art', '--artifact-version', '0.2.4', '--list', 'l=10,11,12'] verify = [('type_name', 'images'), ('list', ['l=10,11,12']), ('artifact_version', '0.2.4')] parsed_args = self.check_parser(self.cmd, arglist, verify) with mock.patch.object( self.app.client_manager.artifact.artifacts, 'create') as patched_create: self.cmd.take_action(parsed_args) patched_create.assert_called_once_with( 'art', l=['10', '11', '12'], type_name='images', version='0.2.4') def test_create_artifact_dict_prop(self): arglist = ['images', 'art', '--artifact-version', '0.2.4', '--dict', 'd=a:10,b:11,c:12'] verify = [('type_name', 'images'), ('dict', ['d=a:10,b:11,c:12']), ('artifact_version', '0.2.4')] parsed_args = self.check_parser(self.cmd, arglist, verify) with mock.patch.object( self.app.client_manager.artifact.artifacts, 'create') as patched_create: self.cmd.take_action(parsed_args) patched_create.assert_called_once_with( 'art', d={'a': '10', 'c': '12', 'b': '11'}, type_name='images', version='0.2.4') def test_create_artifact_multiproperty(self): arglist = ['images', 'art', '--artifact-version', '0.2.4', '--property', 'blah=1', '--property', 'blag=2'] verify = [('type_name', 'images'), ('property', ['blah=1', 'blag=2']), ('artifact_version', '0.2.4')] self.check_parser(self.cmd, arglist, verify) def test_create_artifact_multiversion(self): arglist = ['images', 'art', '--artifact-version', '0.2.4', '--artifact-version', '0.2.5'] verify = [('type_name', 'images'), ('artifact_version', '0.2.5')] self.check_parser(self.cmd, arglist, verify) class TestUpdateArtifacts(TestArtifacts): def setUp(self): super(TestUpdateArtifacts, self).setUp() self.artifact_mock.call.return_value = \ api_art.Controller(self.http, type_name='images') # Command to test self.cmd = osc_art.UpdateArtifact(self.app, None) def test_artifact_update(self): arglist = ['images', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', '--property', 'blah=1', '--property', 'blag=2'] verify = [('type_name', 'images'), ('property', ['blah=1', 'blag=2'])] parsed_args = self.check_parser(self.cmd, arglist, verify) columns, data = self.cmd.take_action(parsed_args) name_fields = set([column[0] for column in data]) # Check that columns are correct self.assertEqual(self.COLUMNS, name_fields) def test_update_artifact_list_prop(self): arglist = ['images', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', '--list', 'l=10,11,12'] verify = [('type_name', 'images'), ('list', ['l=10,11,12'])] parsed_args = self.check_parser(self.cmd, arglist, verify) with mock.patch.object( self.app.client_manager.artifact.artifacts, 'update') as patched_update: self.cmd.take_action(parsed_args) patched_update.assert_called_once_with( 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', l=['10', '11', '12'], remove_props=[], type_name='images') def test_update_artifact_dict_prop(self): arglist = ['images', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', '--dict', 'd=a:10,b:11,c:12'] verify = [('type_name', 'images'), ('dict', ['d=a:10,b:11,c:12'])] parsed_args = self.check_parser(self.cmd, arglist, verify) with mock.patch.object( self.app.client_manager.artifact.artifacts, 'update') as patched_update: self.cmd.take_action(parsed_args) patched_update.assert_called_once_with( 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', d={'a': '10', 'c': '12', 'b': '11'}, remove_props=[], type_name='images') def test_artifact_update_bad(self): arglist = ['images', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', '--property', 'blah', '--property', 'blah' ] verify = [('type_name', 'images')] parsed_args = self.check_parser(self.cmd, arglist, verify) with testtools.ExpectedException(ValueError): self.cmd.take_action(parsed_args) def test_artifact_update_multiremove_prop(self): arglist = ['images', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', '--remove-property', 'prop1', '--remove-property', 'prop2'] verify = [('type_name', 'images'), ('remove_property', ['prop1', 'prop2'])] parsed_args = self.check_parser(self.cmd, arglist, verify) columns, data = self.cmd.take_action(parsed_args) name_fields = set([column[0] for column in data]) # Check that columns are correct self.assertEqual(self.COLUMNS, name_fields) class TestDeleteArtifacts(TestArtifacts): def setUp(self): super(TestDeleteArtifacts, self).setUp() self.artifact_mock.call.return_value = \ api_art.Controller(self.http, type_name='images') # Command to test self.cmd = osc_art.DeleteArtifact(self.app, None) def test_artifact_delete(self): arglist = ['images', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', '--id'] verify = [('type_name', 'images'), ('name', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba'), ('id', True)] parsed_args = self.check_parser(self.cmd, arglist, verify) self.assertIsNone(self.cmd.take_action(parsed_args)) class TestActivateArtifacts(TestArtifacts): def setUp(self): super(TestActivateArtifacts, self).setUp() self.artifact_mock.call.return_value = \ api_art.Controller(self.http, type_name='images') # Command to test self.cmd = osc_art.ActivateArtifact(self.app, None) def test_artifact_activate(self): arglist = ['images', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', '--id'] verify = [('type_name', 'images'), ('name', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba'), ('id', True)] parsed_args = self.check_parser(self.cmd, arglist, verify) columns, data = self.cmd.take_action(parsed_args) name_fields = set([column[0] for column in data]) # Check that columns are correct self.assertEqual(self.COLUMNS, name_fields) class TestDeactivateArtifacts(TestArtifacts): def setUp(self): super(TestDeactivateArtifacts, self).setUp() self.artifact_mock.call.return_value = \ api_art.Controller(self.http, type_name='images') # Command to test self.cmd = osc_art.DeactivateArtifact(self.app, None) def test_artifact_deactivate(self): arglist = ['images', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', '--id'] verify = [('type_name', 'images'), ('name', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba'), ('id', True)] parsed_args = self.check_parser(self.cmd, arglist, verify) columns, data = self.cmd.take_action(parsed_args) name_fields = set([column[0] for column in data]) # Check that columns are correct self.assertEqual(self.COLUMNS, name_fields) class TestReactivateArtifacts(TestArtifacts): def setUp(self): super(TestReactivateArtifacts, self).setUp() self.artifact_mock.call.return_value = \ api_art.Controller(self.http, type_name='images') # Command to test self.cmd = osc_art.ReactivateArtifact(self.app, None) def test_artifact_rectivate(self): arglist = ['images', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', '--id'] verify = [('type_name', 'images'), ('name', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba'), ('id', True)] parsed_args = self.check_parser(self.cmd, arglist, verify) columns, data = self.cmd.take_action(parsed_args) name_fields = set([column[0] for column in data]) # Check that columns are correct self.assertEqual(self.COLUMNS, name_fields) class TestAddTag(TestArtifacts): def setUp(self): super(TestAddTag, self).setUp() self.artifact_mock.call.return_value = \ api_art.Controller(self.http, type_name='images') # Command to test self.cmd = osc_art.AddTag(self.app, None) def test_artifact_add_tag(self): arglist = ['images', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', '--id', '123'] verify = [('type_name', 'images'), ('name', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba'), ('id', True), ('tag', '123')] parsed_args = self.check_parser(self.cmd, arglist, verify) columns, data = self.cmd.take_action(parsed_args) name_fields = set([column[0] for column in data]) # Check that columns are correct self.assertEqual(self.COLUMNS, name_fields) class TestRemoveTag(TestArtifacts): def setUp(self): super(TestRemoveTag, self).setUp() self.artifact_mock.call.return_value = \ api_art.Controller(self.http, type_name='images') # Command to test self.cmd = osc_art.RemoveTag(self.app, None) def test_artifact_add_tag(self): arglist = ['images', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', '--id', '123'] verify = [('type_name', 'images'), ('name', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba'), ('id', True), ('tag', '123')] parsed_args = self.check_parser(self.cmd, arglist, verify) columns, data = self.cmd.take_action(parsed_args) name_fields = set([column[0] for column in data]) # Check that columns are correct self.assertEqual(self.COLUMNS, name_fields) class TestPublishArtifacts(TestArtifacts): def setUp(self): super(TestPublishArtifacts, self).setUp() self.artifact_mock.call.return_value = \ api_art.Controller(self.http, type_name='images') # Command to test self.cmd = osc_art.PublishArtifact(self.app, None) def test_publish_delete(self): arglist = ['images', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', '--id'] verify = [('type_name', 'images'), ('name', 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba'), ('id', True)] parsed_args = self.check_parser(self.cmd, arglist, verify) columns, data = self.cmd.take_action(parsed_args) name_fields = set([column[0] for column in data]) # Check that columns are correct self.assertEqual(self.COLUMNS, name_fields) class TypeSchema(TestArtifacts): def setUp(self): super(TypeSchema, self).setUp() self.artifact_mock.call.return_value = \ api_art.Controller(self.http, type_name='images') # Command to test self.cmd = osc_art.TypeSchema(self.app, None) def test_get_schema(self): arglist = ['images'] verify = [('type_name', 'images')] parsed_args = self.check_parser(self.cmd, arglist, verify) columns, data = self.cmd.take_action(parsed_args) exp_columns = ['Name', 'Glare_type', 'Mutable', 'Required', 'Sortable', 'Filters', 'Available_values'] exp_data = [ (u'image', u'Blob', False, False, False, [], ''), (u'updated_at', u'DateTime', False, True, True, [u'eq', u'neq', u'in', u'gt', u'gte', u'lt', u'lte'], ''), (u'owner', u'String', False, False, True, [u'eq', u'neq', u'in'], ''), (u'provided_by', u'StringDict', False, False, False, [u'eq', u'neq', u'in'], ''), (u'id', u'String', False, True, True, [u'eq', u'neq', u'in'], ''), (u'environment', u'Blob', False, True, False, [], ''), (u'version', u'String', False, False, True, [u'eq', u'neq', u'in', u'gt', u'gte', u'lt', u'lte'], ''), (u'blob', u'Blob', True, False, False, [], ''), (u'template', u'Blob', False, True, False, [], ''), (u'metadata', u'StringDict', False, False, False, [u'eq', u'neq'], ''), (u'status', u'String', False, True, True, [u'eq', u'neq', u'in'], [u'drafted', u'active', u'deactivated', u'deleted']), (u'description', u'String', True, False, False, [u'eq', u'neq', u'in'], ''), (u'tags', u'StringList', True, False, False, [u'eq', u'neq', u'in'], ''), (u'activated_at', u'DateTime', False, False, True, [u'eq', u'neq', u'in', u'gt', u'gte', u'lt', u'lte'], ''), (u'supported_by', u'StringDict', False, False, False, [u'eq', u'neq', u'in'], ''), (u'visibility', u'String', False, True, True, [u'eq'], ''), (u'icon', u'Blob', False, False, False, [], ''), (u'name', u'String', False, False, True, [u'eq', u'neq', u'in'], ''), (u'license', u'String', False, False, False, [u'eq', u'neq', u'in'], ''), (u'package', u'Blob', False, False, False, [], ''), (u'created_at', u'DateTime', False, True, True, [u'eq', u'neq', u'in', u'gt', u'gte', u'lt', u'lte'], ''), (u'license_url', u'String', False, False, False, [u'eq', u'neq', u'in'], ''), (u'release', u'StringList', False, False, False, [u'eq', u'neq', u'in'], '')] data.sort(key=lambda x: x[0]) exp_data.sort(key=lambda x: x[0]) # Check that columns are correct self.assertEqual(exp_columns, columns) self.assertEqual(exp_data, data) python-glareclient-0.5.2/glareclient/tests/unit/osc/v1/fakes.py0000666000175100017510000001027513230110105024532 0ustar zuulzuul00000000000000# Copyright 2016 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import sys import mock from osc_lib.tests import utils from glareclient.common import utils as g_utils from glareclient.tests.unit.osc.v1 import fakes_schemas blob_fixture = { "status": "active", "url": "fake_url", "md5": "35d83e8eedfbdb87ff97d1f2761f8ebf", "sha1": "942854360eeec1335537702399c5aed940401602", "sha256": "d8a7834fc6652f316322d80196f6dcf2" "94417030e37c15412e4deb7a67a367dd", "external": False, "content_type": "application/octet-stream", "size": 594} def mock_list(*args, **kwargs): return [{'id': 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', 'name': 'art1', 'version': '0.0.0', 'owner': 'f649c77999e449e89627024f71b76603', 'visibility': 'private', 'status': 'active', 'type_name': 'images'}, {'id': '48d35c1d-6739-459b-bbda-e4dcba8a684a', 'name': 'art2', 'version': '0.0.0', 'owner': 'f649c77999e449e89627024f71b76603', 'visibility': 'private', 'status': 'active', 'type_name': 'heat_templates'}] def mock_get(*args, **kwargs): return {'id': 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', 'name': 'art1', 'version': '0.0.0', 'owner': 'f649c77999e449e89627024f71b76603', 'visibility': 'private', 'status': 'active', 'blob': blob_fixture, 'image': blob_fixture, 'package': blob_fixture, 'template': blob_fixture, 'environment': blob_fixture} def mock_g_servs(*args, **kwargs): return {'id': 'fc15c365-d4f9-4b8b-a090-d9e230f1f6ba', 'name': 'art1', 'version': '0.0.0', 'owner': 'f649c77999e449e89627024f71b76603', 'visibility': 'private', 'status': 'active'} def mock_g_schema(*args, **kwargs): return fakes_schemas.FIXTURE_SCHEMA def mock_get_data_file(*args, **kwargs): return 'data' class TestArtifacts(utils.TestCommand): def setUp(self): super(TestArtifacts, self).setUp() self.app.client_manager.artifact = mock.MagicMock() self.app.client_manager.artifact.artifacts.list = mock_list self.app.client_manager.artifact.artifacts.get = mock_get self.app.client_manager.artifact.artifacts.get_by_name = mock_get self.app.client_manager.artifact.artifacts.add_tag = mock_g_servs self.app.client_manager.artifact.artifacts.remove_tag = mock_g_servs self.app.client_manager.artifact.artifacts.create = mock_g_servs self.app.client_manager.artifact.artifacts.update = mock_g_servs self.app.client_manager.artifact.artifacts.delete = mock_g_servs self.app.client_manager.artifact.artifacts.activate = mock_g_servs self.app.client_manager.artifact.artifacts.deactivate = mock_g_servs self.app.client_manager.artifact.artifacts.reactivate = mock_g_servs self.app.client_manager.artifact.artifacts.publish = mock_g_servs self.app.client_manager.artifact.blobs.upload_blob = mock_g_servs self.app.client_manager.artifact.blobs.download_blob = mock_g_servs self.app.client_manager.artifact.blobs.add_external_location = \ mock_g_servs self.app.client_manager.artifact.artifacts.get_type_schema = \ mock_g_schema g_utils.get_data_file = mock.MagicMock() g_utils.get_data_file = mock_get_data_file g_utils.save_blob = mock.MagicMock() sys.stdout.isatty = mock.MagicMock() sys.stdout.isatty._mock_return_value = True python-glareclient-0.5.2/glareclient/tests/unit/__init__.py0000666000175100017510000000000013230110105024047 0ustar zuulzuul00000000000000python-glareclient-0.5.2/glareclient/tests/unit/base.py0000666000175100017510000000322413230110105023235 0ustar zuulzuul00000000000000 # Copyright 2011 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os import fixtures import six import testtools class TestCaseShell(testtools.TestCase): TEST_REQUEST_BASE = { 'verify': True, } def setUp(self): super(TestCaseShell, 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 TestAdditionalAsserts(testtools.TestCase): def check_dict_is_subset(self, dict1, dict2): # There is an assert for this in Python 2.7 but not 2.6 self.assertTrue(all(k in dict2 and dict2[k] == v for k, v in six.iteritems(dict1))) python-glareclient-0.5.2/glareclient/tests/unit/test_common_http.py0000666000175100017510000004175513230110105025724 0ustar zuulzuul00000000000000# -*- coding:utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import socket import mock import testtools from glareclient.common import exceptions as exc from glareclient.common import http from glareclient.common import utils from glareclient.tests.unit import fakes @mock.patch('glareclient.common.http.requests.request') class HttpClientTest(testtools.TestCase): # Patch os.environ to avoid required auth info. def setUp(self): super(HttpClientTest, self).setUp() def test_http_raw_request(self, mock_request): headers = {'User-Agent': 'python-glareclient'} mock_request.return_value = \ fakes.FakeHTTPResponse( 200, 'OK', {}, '') client = http.HTTPClient('http://example.com:9494') resp = client.request('', 'GET') self.assertEqual(200, resp.status_code) self.assertEqual('', ''.join([x for x in resp.content])) mock_request.assert_called_with('GET', 'http://example.com:9494', allow_redirects=False, headers=headers) def test_token_or_credentials(self, mock_request): # Record a 200 fake200 = fakes.FakeHTTPResponse( 200, 'OK', {}, '') mock_request.side_effect = [fake200, fake200, fake200] # Replay, create client, assert client = http.HTTPClient('http://example.com:9494') resp = client.request('', 'GET') self.assertEqual(200, resp.status_code) client.username = 'user' client.password = 'pass' resp = client.request('', 'GET') self.assertEqual(200, resp.status_code) client.auth_token = 'abcd1234' resp = client.request('', 'GET') self.assertEqual(200, resp.status_code) # no token or credentials mock_request.assert_has_calls([ mock.call('GET', 'http://example.com:9494', allow_redirects=False, headers={'User-Agent': 'python-glareclient'}), mock.call('GET', 'http://example.com:9494', allow_redirects=False, headers={'User-Agent': 'python-glareclient', 'X-Auth-Key': 'pass', 'X-Auth-User': 'user'}), mock.call('GET', 'http://example.com:9494', allow_redirects=False, headers={'User-Agent': 'python-glareclient', 'X-Auth-Token': 'abcd1234'}) ]) def test_region_name(self, mock_request): # Record a 200 fake200 = fakes.FakeHTTPResponse( 200, 'OK', {}, '') mock_request.return_value = fake200 client = http.HTTPClient('http://example.com:9494') client.region_name = 'RegionOne' resp = client.request('', 'GET') self.assertEqual(200, resp.status_code) mock_request.assert_called_once_with( 'GET', 'http://example.com:9494', allow_redirects=False, headers={'X-Region-Name': 'RegionOne', 'User-Agent': 'python-glareclient'}) def test_http_process_request(self, mock_request): # Record a 200 mock_request.return_value = \ fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, '{}') client = http.HTTPClient('http://example.com:9494') resp, body = client.process_request('', 'GET') self.assertEqual(200, resp.status_code) self.assertEqual({}, body) mock_request.assert_called_once_with( 'GET', 'http://example.com:9494', allow_redirects=False, headers={'User-Agent': 'python-glareclient'}) def test_http_process_request_argument_passed_to_requests( self, mock_request): """Check that we have sent the proper arguments to requests.""" # Record a 200 mock_request.return_value = \ fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, '{}') client = http.HTTPClient('http://example.com:9494') client.verify_cert = True client.cert_file = 'RANDOM_CERT_FILE' client.key_file = 'RANDOM_KEY_FILE' client.auth_url = 'http://AUTH_URL' resp, body = client.process_request('', 'GET', data='text') self.assertEqual(200, resp.status_code) self.assertEqual({}, body) mock_request.assert_called_once_with( 'GET', 'http://example.com:9494', allow_redirects=False, cert=('RANDOM_CERT_FILE', 'RANDOM_KEY_FILE'), verify=True, data='text', headers={'X-Auth-Url': 'http://AUTH_URL', 'User-Agent': 'python-glareclient'}) def test_http_process_request_w_req_body(self, mock_request): # Record a 200 mock_request.return_value = \ fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, '{}') client = http.HTTPClient('http://example.com:9494') resp, body = client.process_request('', 'GET', data='test-body') self.assertEqual(200, resp.status_code) self.assertEqual({}, body) mock_request.assert_called_once_with( 'GET', 'http://example.com:9494', data='test-body', allow_redirects=False, headers={'User-Agent': 'python-glareclient'}) def test_http_process_request_non_json_resp_cont_type( self, mock_request): # Record a 200 mock_request.return_value = \ fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'not/json'}, '{}') client = http.HTTPClient('http://example.com:9494') resp, body = client.process_request('', 'GET', data='test-data') self.assertEqual(200, resp.status_code) mock_request.assert_called_once_with( 'GET', 'http://example.com:9494', data='test-data', allow_redirects=False, headers={'User-Agent': 'python-glareclient'}) def test_http_process_request_invalid_json(self, mock_request): # Record a 200 mock_request.return_value = \ fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, 'invalid-json') client = http.HTTPClient('http://example.com:9494') resp, body = client.process_request('', 'GET') self.assertEqual(200, resp.status_code) self.assertIsNone(body) mock_request.assert_called_once_with( 'GET', 'http://example.com:9494', allow_redirects=False, headers={'User-Agent': 'python-glareclient'}) def test_http_manual_redirect_delete(self, mock_request): mock_request.side_effect = [ fakes.FakeHTTPResponse( 302, 'Found', {'location': 'http://example.com:9494/foo/bar'}, ''), fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, '{}')] client = http.HTTPClient('http://example.com:9494/foo') resp, body = client.process_request('', 'DELETE') self.assertEqual(200, resp.status_code) mock_request.assert_has_calls([ mock.call('DELETE', 'http://example.com:9494/foo', allow_redirects=False, headers={'User-Agent': 'python-glareclient'}), mock.call('DELETE', 'http://example.com:9494/foo/bar', allow_redirects=False, headers={'User-Agent': 'python-glareclient'}) ]) def test_http_manual_redirect_post(self, mock_request): mock_request.side_effect = [ fakes.FakeHTTPResponse( 302, 'Found', {'location': 'http://example.com:9494/foo/bar'}, ''), fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, '{}')] client = http.HTTPClient('http://example.com:9494/foo') resp, body = client.process_request('', 'POST', json={}) self.assertEqual(200, resp.status_code) mock_request.assert_has_calls([ mock.call('POST', 'http://example.com:9494/foo', allow_redirects=False, headers={'User-Agent': 'python-glareclient'}, json={}), mock.call('POST', 'http://example.com:9494/foo/bar', allow_redirects=False, headers={'User-Agent': 'python-glareclient'}, json={}) ]) def test_http_manual_redirect_put(self, mock_request): mock_request.side_effect = [ fakes.FakeHTTPResponse( 302, 'Found', {'location': 'http://example.com:9494/foo/bar'}, ''), fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, '{}')] client = http.HTTPClient('http://example.com:9494/foo') resp, body = client.process_request('', 'PUT', json={}) self.assertEqual(200, resp.status_code) mock_request.assert_has_calls([ mock.call('PUT', 'http://example.com:9494/foo', allow_redirects=False, headers={'User-Agent': 'python-glareclient'}, json={}), mock.call('PUT', 'http://example.com:9494/foo/bar', allow_redirects=False, headers={'User-Agent': 'python-glareclient'}, json={}) ]) def test_http_manual_redirect_prohibited(self, mock_request): mock_request.return_value = \ fakes.FakeHTTPResponse( 302, 'Found', {'location': 'http://example.com:9494/'}, '') client = http.HTTPClient('http://example.com:9494/foo') self.assertRaises(exc.InvalidEndpoint, client.process_request, '', 'DELETE') mock_request.assert_called_once_with( 'DELETE', 'http://example.com:9494/foo', allow_redirects=False, headers={'User-Agent': 'python-glareclient'}) def test_http_manual_redirect_error_without_location(self, mock_request): mock_request.return_value = \ fakes.FakeHTTPResponse( 302, 'Found', {}, '') client = http.HTTPClient('http://example.com:9494/foo') self.assertRaises(exc.InvalidEndpoint, client.process_request, '', 'DELETE') mock_request.assert_called_once_with( 'DELETE', 'http://example.com:9494/foo', allow_redirects=False, headers={'User-Agent': 'python-glareclient'}) def test_http_process_request_redirect(self, mock_request): # Record the 302 mock_request.side_effect = [ fakes.FakeHTTPResponse( 302, 'Found', {'location': 'http://example.com:9494'}, ''), fakes.FakeHTTPResponse( 200, 'OK', {}, '{}')] client = http.HTTPClient('http://example.com:9494') resp, body = client.process_request('', 'GET') self.assertEqual(200, resp.status_code) self.assertEqual({}, body) mock_request.assert_has_calls([ mock.call('GET', 'http://example.com:9494', allow_redirects=False, headers={'User-Agent': 'python-glareclient'}), mock.call('GET', 'http://example.com:9494', allow_redirects=False, headers={'User-Agent': 'python-glareclient'}) ]) def test_http_404_process_request(self, mock_request): mock_request.return_value = \ fakes.FakeHTTPResponse( 404, 'Not Found', {'content-type': 'application/json'}, '{}') client = http.HTTPClient('http://example.com:9494') e = self.assertRaises(exc.HTTPNotFound, client.process_request, '', 'GET') # Assert that the raised exception can be converted to string self.assertIsNotNone(str(e)) # Record a 404 mock_request.assert_called_once_with( 'GET', 'http://example.com:9494', allow_redirects=False, headers={'User-Agent': 'python-glareclient'}) def test_http_300_process_request(self, mock_request): mock_request.return_value = \ fakes.FakeHTTPResponse( 300, 'OK', {'content-type': 'application/json'}, '{}') client = http.HTTPClient('http://example.com:9494') e = self.assertRaises( exc.HTTPMultipleChoices, client.process_request, '', 'GET') # Assert that the raised exception can be converted to string self.assertIsNotNone(str(e)) # Record a 300 mock_request.assert_called_once_with( 'GET', 'http://example.com:9494', allow_redirects=False, headers={'User-Agent': 'python-glareclient'}) def test_fake_process_request(self, mock_request): headers = {'User-Agent': 'python-glareclient'} mock_request.side_effect = [socket.gaierror] client = http.HTTPClient('fake://example.com:9494') self.assertRaises(exc.InvalidEndpoint, client.request, "/", "GET") mock_request.assert_called_once_with('GET', 'fake://example.com:9494/', allow_redirects=False, headers=headers) def test_http_request_socket_error(self, mock_request): headers = {'User-Agent': 'python-glareclient'} mock_request.side_effect = [socket.gaierror] client = http.HTTPClient('http://example.com:9494') self.assertRaises(exc.InvalidEndpoint, client.request, "/", "GET") mock_request.assert_called_once_with('GET', 'http://example.com:9494/', allow_redirects=False, headers=headers) def test_http_request_socket_timeout(self, mock_request): headers = {'User-Agent': 'python-glareclient'} mock_request.side_effect = [socket.timeout] client = http.HTTPClient('http://example.com:9494') self.assertRaises(exc.CommunicationError, client.request, "/", "GET") mock_request.assert_called_once_with('GET', 'http://example.com:9494/', allow_redirects=False, headers=headers) def test_http_request_specify_timeout(self, mock_request): mock_request.return_value = \ fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, '{}') client = http.HTTPClient('http://example.com:9494', timeout='123') resp, body = client.process_request('', 'GET') self.assertEqual(200, resp.status_code) self.assertEqual({}, body) mock_request.assert_called_once_with( 'GET', 'http://example.com:9494', allow_redirects=False, headers={'User-Agent': 'python-glareclient'}, timeout=float(123)) def test_get_system_ca_file(self, mock_request): chosen = '/etc/ssl/certs/ca-certificates.crt' with mock.patch('os.path.exists') as mock_os: mock_os.return_value = chosen ca = utils.get_system_ca_file() self.assertEqual(chosen, ca) mock_os.assert_called_once_with(chosen) def test_insecure_verify_cert_None(self, mock_request): client = http.HTTPClient('https://foo', insecure=True) self.assertFalse(client.verify_cert) def test_passed_cert_to_verify_cert(self, mock_request): client = http.HTTPClient('https://foo', cacert="NOWHERE") self.assertEqual("NOWHERE", client.verify_cert) with mock.patch('glareclient.common.utils.get_system_ca_file') as gsf: gsf.return_value = "SOMEWHERE" client = http.HTTPClient('https://foo') self.assertEqual("SOMEWHERE", client.verify_cert) python-glareclient-0.5.2/glareclient/tests/unit/var/0000775000175100017510000000000013230110365022546 5ustar zuulzuul00000000000000python-glareclient-0.5.2/glareclient/tests/unit/var/wildcard-certificate.crt0000666000175100017510000000645413230110105027334 0ustar zuulzuul00000000000000#Certificate: # Data: # Version: 1 (0x0) # Serial Number: 13493453254446411258 (0xbb42603e589dedfa) # Signature Algorithm: sha1WithRSAEncryption # Issuer: C=US, ST=CA, L=State1, O=Openstack Test Org, OU=Openstack Test Unit, CN=*.pong.example.com/emailAddress=admin@example.com # Validity # Not Before: Aug 21 17:29:18 2013 GMT # Not After : Jul 28 17:29:18 2113 GMT # Subject: C=US, ST=CA, L=State1, O=Openstack Test Org, OU=Openstack Test Unit, CN=*.pong.example.com/emailAddress=admin@example.com # Subject Public Key Info: # Public Key Algorithm: rsaEncryption # Public-Key: (4096 bit) # Modulus: # 00:d4:bb:3a:c4:a0:06:54:31:23:5d:b0:78:5a:be: # 45:44:ae:a1:89:86:11:d8:ca:a8:33:b0:4f:f3:e1: # 46:1e:85:a3:2a:9c:a4:e0:c2:14:34:4f:91:df:dc: # . # . # . # Exponent: 65537 (0x10001) # Signature Algorithm: sha1WithRSAEncryption # 9f:cc:08:5d:19:ee:54:31:a3:57:d7:3c:89:89:c0:69:41:dd: # 46:f8:73:68:ec:46:b9:fa:f5:df:f6:d9:58:35:d8:53:94:88: # bd:36:a6:23:9e:0c:0d:89:62:35:91:49:b6:14:f4:43:69:3c: # . # . # . -----BEGIN CERTIFICATE----- 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 -----END CERTIFICATE----- python-glareclient-0.5.2/glareclient/tests/unit/var/wildcard-san-certificate.crt0000666000175100017510000000507713230110105030113 0ustar zuulzuul00000000000000#Certificate: # Data: # Version: 3 (0x2) # Serial Number: 11990626514780340979 (0xa66743493fdcc2f3) # Signature Algorithm: sha1WithRSAEncryption # Issuer: C=US, ST=CA, L=State1, O=Openstack Test Org, OU=Openstack Test Unit, CN=0.0.0.0 # Validity # Not Before: Dec 10 15:31:22 2013 GMT # Not After : Nov 16 15:31:22 2113 GMT # Subject: C=US, ST=CA, L=State1, O=Openstack Test Org, OU=Openstack Test Unit, CN=0.0.0.0 # Subject Public Key Info: # Public Key Algorithm: rsaEncryption # Public-Key: (2048 bit) # Modulus: # 00:ca:6b:07:73:53:24:45:74:05:a5:2a:27:bd:3e: # . # . # . # Exponent: 65537 (0x10001) # X509v3 extensions: # X509v3 Key Usage: # Key Encipherment, Data Encipherment # X509v3 Extended Key Usage: # TLS Web Server Authentication # X509v3 Subject Alternative Name: # DNS:foo.example.net, DNS:*.example.com # Signature Algorithm: sha1WithRSAEncryption # 7e:41:69:da:f4:3c:06:d6:83:c6:f2:db:df:37:f1:ac:fa:f5: # . # . # . -----BEGIN CERTIFICATE----- MIIDxDCCAqygAwIBAgIJAKZnQ0k/3MLzMA0GCSqGSIb3DQEBBQUAMHgxCzAJBgNV BAYTAlVTMQswCQYDVQQIEwJDQTEPMA0GA1UEBxMGU3RhdGUxMRswGQYDVQQKExJP cGVuc3RhY2sgVGVzdCBPcmcxHDAaBgNVBAsTE09wZW5zdGFjayBUZXN0IFVuaXQx EDAOBgNVBAMTBzAuMC4wLjAwIBcNMTMxMjEwMTUzMTIyWhgPMjExMzExMTYxNTMx MjJaMHgxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEPMA0GA1UEBxMGU3RhdGUx MRswGQYDVQQKExJPcGVuc3RhY2sgVGVzdCBPcmcxHDAaBgNVBAsTE09wZW5zdGFj ayBUZXN0IFVuaXQxEDAOBgNVBAMTBzAuMC4wLjAwggEiMA0GCSqGSIb3DQEBAQUA A4IBDwAwggEKAoIBAQDKawdzUyRFdAWlKie9Pn10j7frffN+z1gEMluK2CtDEwv9 kbD4uS/Kz4dujfTx03mdyNfiMVlOM+YJm/qeLLSdJyFyvZ9Y3WmJ+vT2RGlMMhLd /wEnMRrTYLL39pwI6z+gyw+4D78Pyv/OXy02IA6WtVEefYSx1vmVngb3pL+iBzhO 8CZXNI6lqrFhh+Hr4iMkYMtY1vTnwezAL6p64E/ZAFNPYCEJlacESTLQ4VZYniHc QTgnE1czlI1vxlIk1KDXAzUGeeopZecRih9qlTxtOpklqEciQEE+sHtPcvyvdRE9 Bdyx5rNSALLIcXs0ViJE1RPlw3fjdBoDIOygqvX1AgMBAAGjTzBNMAsGA1UdDwQE AwIEMDATBgNVHSUEDDAKBggrBgEFBQcDATApBgNVHREEIjAggg9mb28uZXhhbXBs ZS5uZXSCDSouZXhhbXBsZS5jb20wDQYJKoZIhvcNAQEFBQADggEBAH5Badr0PAbW g8by29838az69Raul5IkpZQ5V3O1NaNNWxvmF1q8zFFqqGK5ktXJAwGiwnYEBb30 Zfrr+eFIEERzBthSJkWlP8NG+2ooMyg50femp+asAvW+KYYefJW8KaXTsznMsAFy z1agcWVYVZ4H9PwunEYn/rM1krLEe4Cagsw5nmf8VqZg+hHtw930q8cRzgDsZdfA jVK6dWdmzmLCUTL1GKCeNriDw1jIeFvNufC+Q3orH7xBx4VL+NV5ORWdNY/B8q1b mFHdzbuZX6v39+2ww6aZqG2orfxUocc/5Ox6fXqenKPI3moeHS6Ktesq7sEQSJ6H QZFsTuT/124= -----END CERTIFICATE----- python-glareclient-0.5.2/glareclient/tests/unit/var/ca.crt0000666000175100017510000000410613230110105023636 0ustar zuulzuul00000000000000-----BEGIN CERTIFICATE----- MIIF7jCCA9YCCQDbl9qx7iIeJDANBgkqhkiG9w0BAQUFADCBuDEZMBcGA1UEChMQ T3BlbnN0YWNrIENBIE9yZzEaMBgGA1UECxMRT3BlbnN0YWNrIFRlc3QgQ0ExIzAh BgkqhkiG9w0BCQEWFGFkbWluQGNhLmV4YW1wbGUuY29tMREwDwYDVQQHEwhTdGF0 ZSBDQTELMAkGA1UECBMCQ0ExCzAJBgNVBAYTAkFVMS0wKwYDVQQDEyRPcGVuc3Rh Y2sgVGVzdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMTIxMTE2MTI1MDE2WhcN NDAwNDAzMTI1MDE2WjCBuDEZMBcGA1UEChMQT3BlbnN0YWNrIENBIE9yZzEaMBgG A1UECxMRT3BlbnN0YWNrIFRlc3QgQ0ExIzAhBgkqhkiG9w0BCQEWFGFkbWluQGNh LmV4YW1wbGUuY29tMREwDwYDVQQHEwhTdGF0ZSBDQTELMAkGA1UECBMCQ0ExCzAJ BgNVBAYTAkFVMS0wKwYDVQQDEyRPcGVuc3RhY2sgVGVzdCBDZXJ0aWZpY2F0ZSBB dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC94cpBjwj2 MD0w5j1Jlcy8Ljmk3r7CRaoV5vhWUrAWpT7Thxr/Ti0qAfZZRSIVpvBM0RlseH0Q toUJixuYMoNRPUQ74r/TRoO8HfjQDJfnXtWg2L7DRP8p4Zgj3vByBUCU+rKsbI/H Nssl/AronADbZXCoL5hJRN8euMYZGrt/Gh1ZotKE5gQlEjylDFlA3s3pn+ABLgzf 7L7iufwV3zLdPRHCb6Ve8YvUmKfI6gy+WwTRhNhLz4Nj0uBthnj6QhnRXtxkNT7A aAStqKH6TtYRnk2Owh8ITFbtLQ0/MSV8jHAxMXx9AloBhEKxv3cIpgLH6lOCnj// Ql+H6/QWtmTUHzP1kBfMhTQnWTfR92QTcgEMiZ7a07VyVtLh+kp/G5IUqpM6Pyz/ O6QDs7FF69bTpws7Ce916PPrGFZ9Gqvo/P0jXge8kYqO+a8QnTRldAxdUzPJCK9+ Dyi2LWeHf8nPFYdwW9Ov6Jw1CKDYxjJg6KIwnrMPa2eUdPB6/OKkqr9/KemOoKQu 4KSaYadFZbaJwt7JPZaHy6TpkGxW7Af8RqGrW6a6nWEFcfO2POuHcAHWL5LiRmni unm60DBF3b3itDTqCvER3mZE9pN8dqtxdpB8SUX8eq0UJJK2K8mJQS+oE9crbqYb 1kQbYjhhPLlvOQru+/m/abqZrC04u2OtYQIDAQABMA0GCSqGSIb3DQEBBQUAA4IC AQA8wGVBbzfpQ3eYpchiHyHF9N5LIhr6Bt4jYDKLz8DIbElLtoOlgH/v7hLGJ7wu R9OteonwQ1qr9umMmnp61bKXOEBJLBJbGKEt0MNLmmX89+M/h3rdMVZEz/Hht/xK Xm4di8pjkHfmdhqsbiFW81lAt9W1r74lnH7wQHr9ueALGKDx0hi8pAZ27itgQVHL eA1erhw0kjr9BqWpDIskVwePcD7pFoZ48GQlST0uIEq5U+1AWq7AbOABsqODygKi Ri5pmTasNFT7nEX3ti4VN214MNy0JnPzTRNWR2rD0I30AebM3KkzTprbLVfnGkm4 7hOPV+Wc8EjgbbrUAIp2YpOfO/9nbgljTOUsqfjqxzvHx/09XOo2M6NIE5UiHqIq TXN7CeGIhBoYbvBAH2QvtveFXv41IYL4zFFXo4wTBSzCCOUGeDDv0U4hhsNaCkDQ G2TcubNA4g/FAtqLvPj/6VbIIgFE/1/6acsT+W0O+kkVAb7ej2dpI7J+jKXDXuiA PDCMn9dVQ7oAcaQvVdvvRphLdIZ9wHgqKhxKsMwzIMExuDKL0lWe/3sueFyol6nv xRCSgzr5MqSObbO3EnWgcUocBvlPyYLnTM2T8C5wh3BGnJXqJSRETggNn8PXBVIm +c5o+Ic0mYu4v8P1ZSozFdgf+HLriVPwzJU5dHvvTEu7sw== -----END CERTIFICATE----- python-glareclient-0.5.2/glareclient/tests/unit/var/certificate.crt0000666000175100017510000000661413230110105025543 0ustar zuulzuul00000000000000# Certificate: # Data: # Version: 3 (0x2) # Serial Number: 1 (0x1) # Signature Algorithm: sha1WithRSAEncryption # Issuer: O=Openstack CA Org, OU=Openstack Test CA/emailAddress=admin@ca.example.com, # L=State CA, ST=CA, C=AU, CN=Openstack Test Certificate Authority # Validity # Not Before: Nov 16 12:50:19 2012 GMT # Not After : Apr 3 12:50:19 2040 GMT # Subject: O=Openstack Test Org, OU=Openstack Test Unit/emailAddress=admin@example.com, # L=State1, ST=CA, C=US, CN=0.0.0.0 # Subject Public Key Info: # Public Key Algorithm: rsaEncryption # RSA Public Key: (4096 bit) # Modulus (4096 bit): # 00:d4:bb:3a:c4:a0:06:54:31:23:5d:b0:78:5a:be: # 45:44:ae:a1:89:86:11:d8:ca:a8:33:b0:4f:f3:e1: # . # . # . # Exponent: 65537 (0x10001) # X509v3 extensions: # X509v3 Subject Alternative Name: # DNS:alt1.example.com, DNS:alt2.example.com # Signature Algorithm: sha1WithRSAEncryption # 2c:fc:5c:87:24:bd:4a:fa:40:d2:2e:35:a4:2a:f3:1c:b3:67: # b0:e4:8a:cd:67:6b:55:50:d4:cb:dd:2d:26:a5:15:62:90:a3: # . # . # . -----BEGIN CERTIFICATE----- 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 tdc5rw== -----END CERTIFICATE----- python-glareclient-0.5.2/glareclient/tests/unit/var/badcert.crt0000666000175100017510000000000013230110105024644 0ustar zuulzuul00000000000000python-glareclient-0.5.2/glareclient/tests/unit/var/privatekey.key0000666000175100017510000000625313230110105025443 0ustar zuulzuul00000000000000-----BEGIN RSA PRIVATE KEY----- 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 -----END RSA PRIVATE KEY----- python-glareclient-0.5.2/glareclient/tests/unit/var/expired-cert.crt0000666000175100017510000000417313230110105025652 0ustar zuulzuul00000000000000-----BEGIN CERTIFICATE----- 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 l3+dFHnWayuCdqcFwIl3m8i8FupFihz9+A== -----END CERTIFICATE----- python-glareclient-0.5.2/glareclient/tests/unit/v1/0000775000175100017510000000000013230110365022304 5ustar zuulzuul00000000000000python-glareclient-0.5.2/glareclient/tests/unit/v1/__init__.py0000666000175100017510000000000013230110105024375 0ustar zuulzuul00000000000000python-glareclient-0.5.2/glareclient/tests/unit/v1/test_artifacts.py0000666000175100017510000002650413230110105025676 0ustar zuulzuul00000000000000# Copyright 2016 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import io import os import tempfile import mock from oslo_serialization import jsonutils import testtools from glareclient.v1 import artifacts class TestController(testtools.TestCase): def setUp(self): self.mock_resp = mock.MagicMock() self.mock_body = mock.MagicMock() self.mock_http_client = mock.MagicMock() for method in ('get', 'post', 'patch', 'delete'): method = getattr(self.mock_http_client, method) method.return_value = (self.mock_resp, self.mock_body) self.c = artifacts.Controller(self.mock_http_client, 'test_name') self.c._check_type_name = mock.Mock(return_value='checked_name') super(TestController, self).setUp() def test_create(self): body = self.c.create('name', version='0.1.2', type_name='ok') self.assertEqual(self.mock_body, body) self.mock_http_client.post.assert_called_once_with( '/artifacts/checked_name', json={'version': '0.1.2', 'name': 'name'}) self.c._check_type_name.assert_called_once_with('ok') def test_update(self): remove_props = ['remove1', 'remove2'] body = self.c.update('test-id', type_name='test_name', remove_props=remove_props, update1=1, update2=2) self.assertEqual(self.mock_body, body) patch_kwargs = { 'headers': {'Content-Type': 'application/json-patch+json'}, 'json': [ {'path': '/remove1', 'value': None, 'op': 'replace'}, {'path': '/remove2', 'value': None, 'op': 'replace'}, {'path': '/update2', 'value': 2, 'op': 'add'}, {'path': '/update1', 'value': 1, 'op': 'add'} ] } self.mock_http_client.patch.assert_called_once_with( '/artifacts/checked_name/test-id', **patch_kwargs) self.c._check_type_name.assert_called_once_with('test_name') def test_get(self): body = self.c.get('test-id', type_name='test_name') self.assertEqual(self.mock_body, body) self.mock_http_client.get.assert_called_once_with( '/artifacts/checked_name/test-id') self.c._check_type_name.assert_called_once_with('test_name') def test_list_legacy(self): self.mock_http_client.get.side_effect = [ (None, {'checked_name': [10, 11, 12], "next": "next1"}), (None, {'checked_name': [13, 14, 15], "next": "next2"}), (None, {'checked_name': [16, 17, 18], "next": "next3"}), (None, {'checked_name': [19, 20, 21]}), ] data = list(self.c.list(type_name='test-type', limit=10, page_size=3)) expected = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19] self.assertEqual(expected, data) expected_calls = [ mock.call.get('/artifacts/checked_name?&limit=3'), mock.call.get('next1'), mock.call.get('next2'), mock.call.get('next3'), ] self.assertEqual(expected_calls, self.mock_http_client.mock_calls) def test_list(self): self.mock_http_client.get.side_effect = [ (None, {'artifacts': [10, 11, 12], "next": "next1"}), (None, {'artifacts': [13, 14, 15], "next": "next2"}), (None, {'artifacts': [16, 17, 18], "next": "next3"}), (None, {'artifacts': [19, 20, 21]}), ] data = list(self.c.list(type_name='test-type', limit=10, page_size=3)) expected = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19] self.assertEqual(expected, data) expected_calls = [ mock.call.get('/artifacts/checked_name?&limit=3'), mock.call.get('next1'), mock.call.get('next2'), mock.call.get('next3'), ] self.assertEqual(expected_calls, self.mock_http_client.mock_calls) def test_activate(self): self.c.update = mock.Mock() self.assertEqual(self.c.update.return_value, self.c.activate('test-id', type_name='test-type')) self.c.update.assert_called_once_with('test-id', 'test-type', status='active') def test_deactivate(self): self.c.update = mock.Mock() self.assertEqual(self.c.update.return_value, self.c.deactivate('test-id', type_name='test-type')) self.c.update.assert_called_once_with('test-id', 'test-type', status='deactivated') def test_reactivate(self): self.c.update = mock.Mock() self.assertEqual(self.c.update.return_value, self.c.reactivate('test-id', type_name='test-type')) self.c.update.assert_called_once_with('test-id', 'test-type', status='active') def test_delete(self): self.assertIsNone(self.c.delete('test-id', type_name='test-name')) self.mock_http_client.delete.assert_called_once_with( '/artifacts/checked_name/test-id') def test_upload_blob_str(self): self.c.upload_blob('test-id', 'blob-prop', 'data', type_name='test-type', content_type='application/test') self.mock_http_client.put.assert_called_once_with( '/artifacts/checked_name/test-id/blob-prop', data='data', headers={'Content-Length': '4', 'Content-Type': 'application/test'}) def test_upload_blob_file(self): tfd, path = tempfile.mkstemp() try: os.write(tfd, b'data') tfile = open(path, "rb") self.c.upload_blob('test-id', 'blob-prop', tfile, type_name='test-type', content_type='application/test') self.mock_http_client.put.assert_called_once_with( '/artifacts/checked_name/test-id/blob-prop', data=tfile, headers={'Content-Length': '4', 'Content-Type': 'application/test'}) finally: os.remove(path) def test_upload_blob_stream(self): data = io.BytesIO(b'data') self.c.upload_blob('test-id', 'blob-prop', data, type_name='test-type', content_type='application/test') self.mock_http_client.put.assert_called_once_with( '/artifacts/checked_name/test-id/blob-prop', data=data, headers={'Content-Type': 'application/test'}) def test_get_type_list(self): schemas = {'schemas': {'a': {'version': 1}, 'b': {'version': 2}}} self.mock_http_client.get.return_value = (None, schemas) expected_types = [('a', 1), ('b', 2)] self.assertEqual(expected_types, self.c.get_type_list()) def test_get_type_schema(self): test_schema = {'schemas': {'checked_name': 'test-schema'}} self.mock_http_client.get.return_value = (None, test_schema) self.assertEqual('test-schema', self.c.get_type_schema(type_name='test-type')) self.mock_http_client.get.assert_called_once_with( '/schemas/checked_name') def test_add_external_location(self): art_id = '3a4560a1-e585-443e-9b39-553b46ec92a8' data = { 'url': 'http://fake_url', 'md5': '7CA772EE98D5CAF99F3674085D5E4124', 'sha1': None, 'sha256': None}, resp = self.c.add_external_location( art_id, 'image', data=data, type_name='images') self.c.http_client.put.assert_called_once_with( '/artifacts/checked_name/' '3a4560a1-e585-443e-9b39-553b46ec92a8/image', data=jsonutils.dumps(data), headers={'Content-Type': 'application/vnd+openstack.glare-custom-location+json'}) self.assertIsNone(resp) def test_remove_external_location(self): art_id = '3a4560a1-e585-443e-9b39-553b46ec92a8' resp = self.c.remove_external_location( art_id, 'image', type_name='images') self.c.http_client.delete.assert_called_once_with( '/artifacts/checked_name/' '3a4560a1-e585-443e-9b39-553b46ec92a8/image') self.assertIsNone(resp) def test_add_tag(self): art_id = '07a679d8-d0a8-45ff-8d6e-2f32f2097b7c' d = {'tags': ['a', 'b', 'c']} self.mock_body.__getitem__.side_effect = d.__getitem__ data = self.c.add_tag( art_id, tag_value="123", type_name='images') self.c.http_client.get.assert_called_once_with( '/artifacts/checked_name/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c') self.c.http_client.patch.assert_called_once_with( '/artifacts/checked_name/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c', headers={'Content-Type': 'application/json-patch+json'}, json=[{'path': '/tags', 'value': ['a', 'b', 'c', '123'], 'op': 'add'}]) self.assertIsNotNone(data) def test_add_existing_tag(self): art_id = '07a679d8-d0a8-45ff-8d6e-2f32f2097b7c' d = {'tags': ['a', 'b', 'c']} self.mock_body.__getitem__.side_effect = d.__getitem__ data = self.c.add_tag( art_id, tag_value="a", type_name='images') self.c.http_client.get.assert_called_once_with( '/artifacts/checked_name/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c') self.assertEqual(0, self.c.http_client.patch.call_count) self.assertIsNotNone(data) def test_remove_tag(self): art_id = '07a679d8-d0a8-45ff-8d6e-2f32f2097b7c' d = {'tags': ['a', 'b', 'c']} self.mock_body.__getitem__.side_effect = d.__getitem__ data = self.c.remove_tag( art_id, tag_value="a", type_name='images') self.c.http_client.get.assert_called_once_with( '/artifacts/checked_name/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c') self.c.http_client.patch.assert_called_once_with( '/artifacts/checked_name/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c', headers={'Content-Type': 'application/json-patch+json'}, json=[{'path': '/tags', 'value': ['b', 'c'], 'op': 'add'}]) self.assertIsNotNone(data) def test_remove_nonexisting_tag(self): art_id = '07a679d8-d0a8-45ff-8d6e-2f32f2097b7c' d = {'tags': ['a', 'b', 'c']} self.mock_body.__getitem__.side_effect = d.__getitem__ data = self.c.remove_tag( art_id, tag_value="123", type_name='images') self.c.http_client.get.assert_called_once_with( '/artifacts/checked_name/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c') self.assertEqual(0, self.c.http_client.patch.call_count) self.assertIsNotNone(data) python-glareclient-0.5.2/glareclient/tests/unit/test_progressbar.py0000666000175100017510000000555413230110105025723 0ustar zuulzuul00000000000000# 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. import mock from six import StringIO import testtools from glareclient.common import progressbar MOD = 'glareclient.common.progressbar' class TestProgressBar(testtools.TestCase): @mock.patch(MOD + '.os') def test_totalsize_fileno(self, mock_os): mock_os.fstat.return_value.st_size = 43 fake_file = mock.Mock() del fake_file.len fake_file.fileno.return_value = 42 pb = progressbar.VerboseFileWrapper(fake_file) self.assertEqual(43, pb._totalsize) mock_os.fstat.assert_called_once_with(42) @mock.patch(MOD + '.sys') def test__display_progress_bar(self, mock_sys): fake_file = StringIO('test') # 4 bytes fake_file.len = 4 pb = progressbar.VerboseFileWrapper(fake_file) pb._display_progress_bar(2) # 2 of 4 bytes = 50% pb._display_progress_bar(1) # 3 of 4 bytes = 75% pb._display_progress_bar(1) # 4 of 4 bytes = 100% expected = [ mock.call('\r[===============> ] 50%'), mock.call('\r[======================> ] 75%'), mock.call('\r[=============================>] 100%'), ] self.assertEqual(expected, mock_sys.stdout.write.mock_calls) @mock.patch(MOD + '.sys') def test__display_progress_bar_unknown_len(self, mock_sys): fake_file = StringIO('') fake_file.len = 0 pb = progressbar.VerboseFileWrapper(fake_file) for i in range(6): pb._display_progress_bar(1) expected = [ mock.call('\r[-] 1 bytes'), mock.call('\r[\\] 2 bytes'), mock.call('\r[|] 3 bytes'), mock.call('\r[/] 4 bytes'), mock.call('\r[-] 5 bytes'), mock.call('\r[\\] 6 bytes'), ] self.assertEqual(expected, mock_sys.stdout.write.mock_calls) @mock.patch(MOD + '._ProgressBarBase.__init__') @mock.patch(MOD + '._ProgressBarBase._display_progress_bar') def test_read(self, mock_display_progress_bar, mock_init): mock_init.return_value = None pb = progressbar.VerboseFileWrapper() pb._wrapped = mock.Mock(len=42) pb._wrapped.read.return_value = 'ok' pb.read(2) mock_display_progress_bar.assert_called_once_with(2) python-glareclient-0.5.2/glareclient/tests/unit/fakes.py0000666000175100017510000000245513230110105023421 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from oslo_serialization import jsonutils class FakeRaw(object): version = 110 class FakeHTTPResponse(object): version = 1.1 def __init__(self, status_code, reason, headers, content): self.headers = headers self.content = content self.text = content self.status_code = status_code self.reason = reason self.raw = FakeRaw() def getheader(self, name, default=None): return self.headers.get(name, default) def getheaders(self): return self.headers.items() def read(self, amt=None): b = self.content self.content = None return b def iter_content(self, chunksize): return self.content def json(self): return jsonutils.loads(self.content) python-glareclient-0.5.2/doc/0000775000175100017510000000000013230110365016111 5ustar zuulzuul00000000000000python-glareclient-0.5.2/doc/source/0000775000175100017510000000000013230110365017411 5ustar zuulzuul00000000000000python-glareclient-0.5.2/doc/source/index.rst0000666000175100017510000000103213230110105021240 0ustar zuulzuul00000000000000Python Bindings for the Glare Artifact Repository ================================================= This is a client for the Glare Artifact Repository. There's :doc:`a Python API ` (the :mod:`glareclient` module) and a :doc:`command-line script` (installed as :program:`glare`). Python API ---------- Python API Reference ~~~~~~~~~~~~~~~~~~~~ Command-line Tool Reference ~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. toctree:: :maxdepth: 1 man/glare Command-line Tool ----------------- Release Notes ============= python-glareclient-0.5.2/doc/source/conf.py0000666000175100017510000001031213230110105020677 0ustar zuulzuul00000000000000# Copyright 2015 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. # -*- coding: utf-8 -*- # import os import sys sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))) BASE_DIR = os.path.dirname(os.path.abspath(__file__)) ROOT = os.path.abspath(os.path.join(BASE_DIR, "..", "..")) def gen_ref(ver, title, names): refdir = os.path.join(BASE_DIR, "ref") pkg = "glareclient" 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(None, "API", ["client", "exc"]) gen_ref("v1", "Glare Artifact Repository Version 1 Client Reference", ["client", "artifacts"]) # -- 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', 'oslosphinx'] # 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. project = 'python-glareclient' copyright = u'OpenStack Foundation' # 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' # Grouping the document tree for man pages. # List of tuples 'sourcefile', 'target', u'title', u'Authors name', 'manual' man_pages = [ ('man/glare', 'glare', u'Client for Glare Artifact Repository', [u'OpenStack Foundation'], 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 = '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 Foundation', 'manual' ), ] python-glareclient-0.5.2/doc/source/man/0000775000175100017510000000000013230110365020164 5ustar zuulzuul00000000000000python-glareclient-0.5.2/doc/source/man/glare.rst0000666000175100017510000000112013230110105021774 0ustar zuulzuul00000000000000============================= :program:`glare` CLI man page ============================= .. program:: glare .. highlight:: bash SYNOPSIS ======== :program:`glare` [options] [command-options] :program:`glare help` :program:`glare help` DESCRIPTION =========== OPTIONS ======= To get a list of available commands and options run:: glare help To get usage and options of a command:: glare help EXAMPLES ======== BUGS ==== Glare client is hosted in Launchpad so you can view current bugs at https://bugs.launchpad.net/python-glareclient/. python-glareclient-0.5.2/ChangeLog0000664000175100017510000000500413230110364017114 0ustar zuulzuul00000000000000CHANGES ======= 0.5.2 ----- * Adopt new api for artifact listing * Rename -s option to -S 0.5.1 ----- * Look the list of artifacts in 'artifacts' section * Avoid tox\_install.sh for constraints support * Remove setting of version/release from releasenotes * Updated from global requirements * Updated from global requirements 0.5.0 ----- * Don't create client for help and bash completion * Allow to work in secure mode without certificate 0.4.2 ----- * Add content\_length to header in upload\_blob * Allow to delete blobs with external urls * Add ssl options to keycloak auth module * Add ssl options to glare cli 0.4.1 ----- * Return missing 'return' in build\_option\_parser * Updated from global requirements 0.4.0 ----- * Remove unecessary cli option "--glare-url" * Add missing 'activate' command to native glare cli * Remove unnecessary code from shell 0.3.1 ----- * Raise error if there are more than 1 artifact with same name/version * Fix a typo in bash\_complition file name * Rename deprecated 'warn' to 'warning' in logging * Add 'list-types' and 'schema' operation in native shell * Use assertIsNone(value) instead of assertEqual(None, value) 0.3.0 ----- * Add keycloak auth support * [Fix gate]Update test requirement * Updated from global requirements * HTTP Client refactoring * Parse blob dict upload output right * Add tag support for Glare client * Update CLI options for blobs * Allow to specify dict and list properties in cli * Updated from global requirements * Fix updating image by name * Allowed to use the artifacts by name * Add short arguments and fix small errors * Add the possibility to specify glare url in cli * Enable release notes translation * Add setting of external locations 0.2.0 ----- * Rename 'sample\_artifact' to 'images' in tests * Add osc command "schema" * Add column 'glareType' in show artifact * Show type name when list 'all' artifacts * Add command 'type-list' * Allow to remove non-compound properties * Updated from global requirements * Updated from global requirements * Allow to set content-type for uploading blobs * Fix small typos and remove unused code * Add new common/http * Change --remove-property format * Updated from global requirements * Fix blob upload output * Rename 'active' method to 'activate' * Rename --blob into --file in blob upload * Updated from global requirements * Added unit tests for test\_artifacts.py * add unit tests for osc * Fix glareclient download blob method * Fix "Invalid content type" * Fix several errors: * Glare client code * Added .gitreview python-glareclient-0.5.2/setup.cfg0000666000175100017510000000433013230110365017167 0ustar zuulzuul00000000000000[metadata] name = python-glareclient summary = Glare Artifact Repository description-file = README.rst license = Apache License, Version 2.0 author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = http://docs.openstack.org/developer/python-glareclient classifier = Development Status :: 5 - Production/Stable Environment :: Console Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 [files] packages = glareclient [global] setup-hooks = pbr.hooks.setup_hook [entry_points] console_scripts = glare = glareclient.shell:main openstack.cli.extension = artifact = glareclient.osc.plugin openstack.artifact.v1 = artifact_list = glareclient.osc.v1.artifacts:ListArtifacts artifact_show = glareclient.osc.v1.artifacts:ShowArtifact artifact_create = glareclient.osc.v1.artifacts:CreateArtifact artifact_update = glareclient.osc.v1.artifacts:UpdateArtifact artifact_delete = glareclient.osc.v1.artifacts:DeleteArtifact artifact_activate = glareclient.osc.v1.artifacts:ActivateArtifact artifact_deactivate = glareclient.osc.v1.artifacts:DeactivateArtifact artifact_reactivate = glareclient.osc.v1.artifacts:ReactivateArtifact artifact_publish = glareclient.osc.v1.artifacts:PublishArtifact artifact_upload = glareclient.osc.v1.blobs:UploadBlob artifact_location = glareclient.osc.v1.blobs:AddLocation artifact_remove-location = glareclient.osc.v1.blobs:RemoveLocation artifact_download = glareclient.osc.v1.blobs:DownloadBlob artifact_type-list = glareclient.osc.v1.artifacts:TypeList artifact_schema = glareclient.osc.v1.artifacts:TypeSchema artifact_add_tag = glareclient.osc.v1.artifacts:AddTag artifact_remove_tag = glareclient.osc.v1.artifacts:RemoveTag [build_sphinx] source-dir = doc/source build-dir = doc/build all_files = 1 [upload_sphinx] upload-dir = doc/build/html [wheel] universal = 1 [egg_info] tag_build = tag_date = 0 python-glareclient-0.5.2/README.rst0000666000175100017510000000014413230110105017024 0ustar zuulzuul00000000000000Python bindings to the Glare Artifact Repository ================================================ python-glareclient-0.5.2/.coveragerc0000666000175100017510000000014713230110105017461 0ustar zuulzuul00000000000000[run] branch = True source = glareclient omit = glareclient/openstack/* [report] ignore_errors = True python-glareclient-0.5.2/tools/0000775000175100017510000000000013230110365016504 5ustar zuulzuul00000000000000python-glareclient-0.5.2/tools/with_venv.sh0000777000175100017510000000030313230110105021042 0ustar zuulzuul00000000000000#!/bin/bash command -v tox > /dev/null 2>&1 if [ $? -ne 0 ]; then echo 'This script requires "tox" to run.' echo 'You can install it with "pip install tox".' exit 1; fi tox -evenv -- $@ python-glareclient-0.5.2/tools/glare.bash_completion0000666000175100017510000000154313230110105022663 0ustar zuulzuul00000000000000_glare_opts="" # lazy init _glare_flags="" # lazy init _glare_opts_exp="" # lazy init _glare() { local cur prev nbc cflags COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" if [ "x$_glare_opts" == "x" ] ; then nbc="`glare bash-completion | sed -e "s/ *-h */ /" -e "s/ *-i */ /"`" _glare_opts="`echo "$nbc" | sed -e "s/--[a-z0-9_-]*//g" -e "s/ */ /g"`" _glare_flags="`echo " $nbc" | sed -e "s/ [^-][^-][a-z0-9_-]*//g" -e "s/ */ /g"`" _glare_opts_exp="`echo "$_glare_opts" | sed 's/^ *//' | tr ' ' '|'`" fi if [[ " ${COMP_WORDS[@]} " =~ " "($_glare_opts_exp)" " && "$prev" != "help" ]] ; then COMPREPLY=($(compgen -W "${_glare_flags}" -- ${cur})) else COMPREPLY=($(compgen -W "${_glare_opts}" -- ${cur})) fi return 0 } complete -F _glare glarepython-glareclient-0.5.2/HACKING.rst0000666000175100017510000000035313230110105017135 0ustar zuulzuul00000000000000Glare Style Commandments ======================== - Step 1: Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/ - Step 2: Read on Glare Specific Commandments --------------------------- None so far python-glareclient-0.5.2/requirements.txt0000666000175100017510000000076513230110105020632 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr!=2.1.0,>=2.0.0 # Apache-2.0 Babel!=2.4.0,>=2.3.4 # BSD PrettyTable<0.8,>=0.7.1 # BSD keystoneauth1>=3.2.0 # Apache-2.0 requests>=2.14.2 # Apache-2.0 six>=1.9.0 # MIT oslo.utils>=3.31.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.log>=3.30.0 # Apache-2.0 osc-lib>=1.7.0 # Apache-2.0 python-glareclient-0.5.2/run_tests.sh0000777000175100017510000000211413230110105017721 0ustar zuulzuul00000000000000#!/bin/bash function usage { echo "Usage: $0 [OPTION]..." echo "Run python-glareclient's test suite(s)" echo "" echo " -p, --pep8 Just run flake8" 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 flake8 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 python-glareclient-0.5.2/tox.ini0000666000175100017510000000315113230110105016651 0ustar zuulzuul00000000000000[tox] envlist = py35,py34,py27,pep8 minversion = 1.6 skipsdist = True [testenv] usedevelop = True install_command = pip install {opts} {packages} setenv = VIRTUAL_ENV={envdir} OS_STDOUT_NOCAPTURE=False OS_STDERR_NOCAPTURE=False PYTHONHASHSEED=0 PYTHONDONTWRITEBYTECODE = 1 deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = python setup.py testr --testr-args='{posargs}' [testenv:pep8] commands = flake8 [testenv:venv] # NOTE(NiallBunting) Infra does not support constraints for the venv # job. install_command = pip install -U {opts} {packages} commands = {posargs} [pbr] warnerror = True [testenv:functional] # See glareclient/tests/functional/README.rst # for information on running the functional tests. setenv = OS_TEST_PATH = ./glareclient/tests/functional [testenv:cover] install_command = pip install -U {opts} {packages} commands = coverage erase python setup.py testr --coverage --testr-args='{posargs}' [testenv:docs] commands= python setup.py build_sphinx [testenv:releasenotes] # NOTE(Niall Bunting) Does not support constraints. install_command = pip install -U {opts} {packages} commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [flake8] ignore = F403,F812,F821 show-source = True exclude = .venv*,.tox,dist,*egg,build,.git,doc,*openstack/common*,*lib/python*,.update-venv [hacking] import_exceptions = six.moves,glareclient._i18n python-glareclient-0.5.2/test-requirements.txt0000666000175100017510000000114113230110105021574 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 mock>=2.0.0 # BSD ordereddict>=1.1 # MIT os-client-config>=1.28.0 # Apache-2.0 oslosphinx>=4.7.0 # Apache-2.0 reno>=2.5.0 # Apache-2.0 sphinx>=1.6.2 # BSD testrepository>=0.0.18 # Apache-2.0/BSD testtools>=1.4.0 # MIT testscenarios>=0.4 # Apache-2.0/BSD fixtures>=3.0.0 # Apache-2.0/BSD requests-mock>=1.1.0 # Apache-2.0 python-glareclient-0.5.2/AUTHORS0000664000175100017510000000066713230110364016424 0ustar zuulzuul00000000000000Andreas Jaeger Darja Malyavkina Idan Narotzki IlyaMenkov Mike Fedosin Mike Fedosin Mike Fedosin Mike Fedosin Rui Yuan Dou Sergey Skripnick Zuul ricolin python-glareclient-0.5.2/.testr.conf0000666000175100017510000000027713230110105017432 0ustar zuulzuul00000000000000[DEFAULT] test_command=${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./glareclient/tests/unit} $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list