python-muranoclient-1.3.0/0000775000175000017500000000000013523272337015563 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/python_muranoclient.egg-info/0000775000175000017500000000000013523272337023356 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/python_muranoclient.egg-info/pbr.json0000664000175000017500000000005613523272336025034 0ustar zuulzuul00000000000000{"git_version": "2f50177", "is_release": true}python-muranoclient-1.3.0/python_muranoclient.egg-info/PKG-INFO0000664000175000017500000000733113523272336024456 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: python-muranoclient Version: 1.3.0 Summary: python-muranoclient Home-page: https://docs.openstack.org/python-muranoclient/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: Apache License, Version 2.0 Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-muranoclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on Murano ====== .. image:: https://img.shields.io/pypi/v/python-muranoclient.svg :target: https://pypi.org/project/python-muranoclient/ :alt: Latest Version Murano Project introduces an application catalog, which allows application developers and cloud administrators to publish various cloud-ready applications in a browsable categorised catalog, which may be used by the cloud users (including the inexperienced ones) to pick-up the needed applications and services and composes the reliable environments out of them in a "push-the-button" manner. * `PyPi`_ - package installation * `Launchpad project`_ - release management * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ * `Specs`_ * `How to Contribute`_ .. _PyPi: https://pypi.org/project/python-muranoclient .. _Launchpad project: https://launchpad.net/python-muranoclient .. _Blueprints: https://blueprints.launchpad.net/python-muranoclient .. _Bugs: https://bugs.launchpad.net/python-muranoclient .. _Source: https://opendev.org/openstack/python-muranoclient .. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html .. _Specs: https://specs.openstack.org/openstack/murano-specs/ .. _Release Notes: https://docs.openstack.org/releasenotes/python-muranoclient Python Muranoclient ------------------- python-muranoclient is a client library for Murano built on the Murano API. It provides a Python API (the ``muranoclient`` module) and a command-line tool (``murano``). Project Resources ----------------- Project status, bugs, and blueprints are tracked on Launchpad: * Client bug tracker * https://launchpad.net/python-muranoclient * Murano bug tracker * https://launchpad.net/murano Developer documentation can be found here: https://docs.openstack.org/murano/latest/ Additional resources are linked from the project wiki page: https://wiki.openstack.org/wiki/Murano License ------- Apache License Version 2.0 http://www.apache.org/licenses/LICENSE-2.0 Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Console Classifier: Environment :: OpenStack Classifier: Intended Audience :: Developers 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.6 Classifier: Programming Language :: Python :: 3.7 python-muranoclient-1.3.0/python_muranoclient.egg-info/dependency_links.txt0000664000175000017500000000000113523272336027423 0ustar zuulzuul00000000000000 python-muranoclient-1.3.0/python_muranoclient.egg-info/requires.txt0000664000175000017500000000052113523272336025753 0ustar zuulzuul00000000000000pbr!=2.1.0,>=2.0.0 PrettyTable<0.8,>=0.7.2 python-glanceclient>=2.8.0 python-keystoneclient>=3.8.0 iso8601>=0.1.11 six>=1.10.0 Babel!=2.4.0,>=2.3.4 pyOpenSSL>=17.1.0 requests>=2.14.2 PyYAML>=3.12 yaql>=1.1.3 osc-lib>=1.8.0 murano-pkg-check>=0.3.0 oslo.serialization!=2.19.1,>=2.18.0 oslo.utils>=3.33.0 oslo.log>=3.36.0 oslo.i18n>=3.15.3 python-muranoclient-1.3.0/python_muranoclient.egg-info/entry_points.txt0000664000175000017500000000337513523272336026663 0ustar zuulzuul00000000000000[console_scripts] murano = muranoclient.shell:main [openstack.application_catalog.v1] bundle_import = muranoclient.osc.v1.package:ImportBundle category_create = muranoclient.osc.v1.category:CreateCategory category_delete = muranoclient.osc.v1.category:DeleteCategory category_list = muranoclient.osc.v1.category:ListCategories category_show = muranoclient.osc.v1.category:ShowCategory class-schema = muranoclient.osc.v1.schema:ShowSchema deployment_list = muranoclient.osc.v1.deployment:ListDeployment environment_apps_edit = muranoclient.osc.v1.environment:EnvironmentAppsEdit environment_create = muranoclient.osc.v1.environment:EnvironmentCreate environment_delete = muranoclient.osc.v1.environment:EnvironmentDelete environment_deploy = muranoclient.osc.v1.environment:EnvironmentDeploy environment_list = muranoclient.osc.v1.environment:ListEnvironments environment_model_edit = muranoclient.osc.v1.environment:EnvironmentModelEdit environment_model_show = muranoclient.osc.v1.environment:EnvironmentModelShow environment_rename = muranoclient.osc.v1.environment:RenameEnvironment environment_session_create = muranoclient.osc.v1.environment:EnvironmentSessionCreate environment_show = muranoclient.osc.v1.environment:ShowEnvironment package_create = muranoclient.osc.v1.package:CreatePackage package_delete = muranoclient.osc.v1.package:DeletePackage package_download = muranoclient.osc.v1.package:DownloadPackage package_import = muranoclient.osc.v1.package:ImportPackage package_list = muranoclient.osc.v1.package:ListPackages package_show = muranoclient.osc.v1.package:ShowPackage package_update = muranoclient.osc.v1.package:UpdatePackage static-action_call = muranoclient.osc.v1.action:StaticActionCall [openstack.cli.extension] application_catalog = muranoclient.osc.plugin python-muranoclient-1.3.0/python_muranoclient.egg-info/SOURCES.txt0000664000175000017500000001650313523272337025247 0ustar zuulzuul00000000000000.coveragerc .stestr.conf .zuul.yaml AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE README.rst babel.cfg lower-constraints.txt requirements.txt run_tests.sh setup-centos.sh setup.cfg setup.py setup.sh test-requirements.txt tox.ini doc/requirements.txt doc/source/conf.py doc/source/index.rst doc/source/cli/index.rst doc/source/cli/murano.rst muranoclient/__init__.py muranoclient/client.py muranoclient/i18n.py muranoclient/shell.py muranoclient/version.py muranoclient/apiclient/__init__.py muranoclient/apiclient/auth.py muranoclient/apiclient/base.py muranoclient/apiclient/client.py muranoclient/apiclient/exceptions.py muranoclient/apiclient/fake_client.py muranoclient/common/__init__.py muranoclient/common/base.py muranoclient/common/exceptions.py muranoclient/common/http.py muranoclient/common/utils.py muranoclient/common/yaqlexpression.py muranoclient/common/yaqlexpression_legacy.py muranoclient/data/heat_logo.png muranoclient/data/mpl_logo.png muranoclient/glance/__init__.py muranoclient/glance/artifacts.py muranoclient/glance/client.py muranoclient/locale/en_GB/LC_MESSAGES/muranoclient.po muranoclient/osc/__init__.py muranoclient/osc/plugin.py muranoclient/osc/v1/__init__.py muranoclient/osc/v1/action.py muranoclient/osc/v1/category.py muranoclient/osc/v1/deployment.py muranoclient/osc/v1/environment.py muranoclient/osc/v1/package.py muranoclient/osc/v1/schema.py muranoclient/tests/__init__.py muranoclient/tests/functional/__init__.py muranoclient/tests/functional/muranoclient.py muranoclient/tests/functional/cli/__init__.py muranoclient/tests/functional/cli/murano_test_utils.py muranoclient/tests/functional/cli/test_murano.py muranoclient/tests/functional/cli/utils.py muranoclient/tests/functional/cli/MockApp/Classes/mock_muranopl.yaml muranoclient/tests/functional/hooks/post_test_hook.sh muranoclient/tests/unit/__init__.py muranoclient/tests/unit/base.py muranoclient/tests/unit/fakes.py muranoclient/tests/unit/test_base.py muranoclient/tests/unit/test_common_http.py muranoclient/tests/unit/test_exc.py muranoclient/tests/unit/test_methods.py muranoclient/tests/unit/test_package_creator.py muranoclient/tests/unit/test_shell.py muranoclient/tests/unit/test_utils.py muranoclient/tests/unit/fixture_data/heat-template.yaml muranoclient/tests/unit/fixture_data/logo.png muranoclient/tests/unit/fixture_data/empty-app/manifest.yaml muranoclient/tests/unit/fixture_data/test-app/ui.yaml muranoclient/tests/unit/fixture_data/test-app/Classes/testapp.yaml muranoclient/tests/unit/fixture_data/test-app/Resources/Deploy.template muranoclient/tests/unit/fixture_data/test-app/Resources/scripts/common.sh muranoclient/tests/unit/fixture_data/test-app/Resources/scripts/deploy.sh muranoclient/tests/unit/fixture_data/test-app/Resources/scripts/installer.sh muranoclient/tests/unit/osc/__init__.py muranoclient/tests/unit/osc/test_plugin.py muranoclient/tests/unit/osc/v1/__init__.py muranoclient/tests/unit/osc/v1/fakes.py muranoclient/tests/unit/osc/v1/test_action.py muranoclient/tests/unit/osc/v1/test_category.py muranoclient/tests/unit/osc/v1/test_deployment.py muranoclient/tests/unit/osc/v1/test_environment.py muranoclient/tests/unit/osc/v1/test_package.py muranoclient/tests/unit/osc/v1/test_schema.py muranoclient/tests/unit/osc/v1/fixture_data/heat-template.yaml muranoclient/tests/unit/osc/v1/fixture_data/logo.png muranoclient/tests/unit/osc/v1/fixture_data/test-app/ui.yaml muranoclient/tests/unit/osc/v1/fixture_data/test-app/Classes/testapp.yaml muranoclient/tests/unit/osc/v1/fixture_data/test-app/Resources/Deploy.template muranoclient/tests/unit/osc/v1/fixture_data/test-app/Resources/scripts/common.sh muranoclient/tests/unit/osc/v1/fixture_data/test-app/Resources/scripts/deploy.sh muranoclient/tests/unit/osc/v1/fixture_data/test-app/Resources/scripts/installer.sh muranoclient/v1/__init__.py muranoclient/v1/actions.py muranoclient/v1/artifact_packages.py muranoclient/v1/categories.py muranoclient/v1/client.py muranoclient/v1/deployments.py muranoclient/v1/environments.py muranoclient/v1/instance_statistics.py muranoclient/v1/packages.py muranoclient/v1/request_statistics.py muranoclient/v1/schemas.py muranoclient/v1/services.py muranoclient/v1/sessions.py muranoclient/v1/shell.py muranoclient/v1/static_actions.py muranoclient/v1/templates.py muranoclient/v1/package_creator/__init__.py muranoclient/v1/package_creator/hot_package.py muranoclient/v1/package_creator/mpl_package.py playbooks/legacy/muranoclient-functional-test-mysql-backend/post.yaml playbooks/legacy/muranoclient-functional-test-mysql-backend/run.yaml python_muranoclient.egg-info/PKG-INFO python_muranoclient.egg-info/SOURCES.txt python_muranoclient.egg-info/dependency_links.txt python_muranoclient.egg-info/entry_points.txt python_muranoclient.egg-info/not-zip-safe python_muranoclient.egg-info/pbr.json python_muranoclient.egg-info/requires.txt python_muranoclient.egg-info/top_level.txt releasenotes/notes/.placeholder releasenotes/notes/action-arguments-06a554f76783f3ed.yaml releasenotes/notes/add-environment-support-to-OSC-173b86ec631283b7.yaml releasenotes/notes/added-category-command-01cb9dab21ab4a7b.yaml releasenotes/notes/added-environment-command-85b31973e101fd1f.yaml releasenotes/notes/added-schemas-support-e4a6e44c7bac2751.yaml releasenotes/notes/added-static-actions-3d45f5cdc5491770.yaml releasenotes/notes/bug-1527045-97993f04757b4901.yaml releasenotes/notes/bug-1536121-51c42f77a9e97db1.yaml releasenotes/notes/bug-1629221-e7f1766eb9878f23.yaml releasenotes/notes/dep-exists-action-9af18bebcc0ef053.yaml releasenotes/notes/deprecate-murano-packages-service-glance-7d0052a5256adace.yaml releasenotes/notes/enable-openstack-client-support-a273e33d6c31e36e.yaml releasenotes/notes/environment-edit-7faf5c8e8a3d44ac.yaml releasenotes/notes/fix-owned-flag-e8b718c074c1c314.yaml releasenotes/notes/glare-endpoint-cdba1b2351c19592.yaml releasenotes/notes/global-inherits-fix-6da007ec44a774f2.yaml releasenotes/notes/import-package-from-directory-8f2b5e393004ef97.yaml releasenotes/notes/improved-cli-outputs-a3e75cf0224a1993.yaml releasenotes/notes/list-environments-of-a-given-project-e407dd5271649ad2.yaml releasenotes/notes/multi-class-yamls-support-914b3d155324214f.yaml releasenotes/notes/multiple-packages-glare-fix-b82a473ad976028f.yaml releasenotes/notes/osc-murano-url-507932234b49cf9f.yaml releasenotes/notes/osc-package-list-command-23e39dc92ead8834.yaml releasenotes/notes/reauth-fix-e03ad966c3178167.yaml releasenotes/notes/repair-package-update-command-72e4c85da1b2c897.yaml releasenotes/notes/requirements-order-19ecc40ca6d34739.yaml releasenotes/notes/resources-dir-for-hot-c557c1472bbc79fa.yaml releasenotes/notes/safeloader-cve-2016-4972-e3f7ad9b234655ca.yaml releasenotes/notes/support-endpoint-type-d7c6b32098b67eeb.yaml releasenotes/notes/yamlloader-glare-d7d0506f6711b650.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/liberty.rst releasenotes/source/mitaka.rst releasenotes/source/newton.rst releasenotes/source/ocata.rst releasenotes/source/pike.rst releasenotes/source/queens.rst releasenotes/source/rocky.rst releasenotes/source/stein.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po tools/install_venv.py tools/install_venv_common.py tools/murano.bash_completion tools/with_venv.shpython-muranoclient-1.3.0/python_muranoclient.egg-info/top_level.txt0000664000175000017500000000001513523272336026103 0ustar zuulzuul00000000000000muranoclient python-muranoclient-1.3.0/python_muranoclient.egg-info/not-zip-safe0000664000175000017500000000000113523272336025603 0ustar zuulzuul00000000000000 python-muranoclient-1.3.0/tools/0000775000175000017500000000000013523272337016723 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/tools/murano.bash_completion0000664000175000017500000000155113523272253023313 0ustar zuulzuul00000000000000_murano_opts="" # lazy init _murano_flags="" # lazy init _murano_opts_exp="" # lazy init _murano() { local cur prev kbc COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" if [ "x$_murano_opts" == "x" ] ; then kbc="$(murano bash-completion | sed -e "s/ -h / /")" _murano_opts="$(echo "$kbc" | sed -e "s/--[a-z0-9_-]*//g" -e "s/[ ][ ]*/ /g")" _murano_flags="$(echo " $kbc" | sed -e "s/ [^-][^-][a-z0-9_-]*//g" -e "s/[ ][ ]*/ /g")" _murano_opts_exp="$(echo "$_murano_opts" | sed -e "s/[ ]/|/g")" fi if [[ " ${COMP_WORDS[@]} " =~ " "($_murano_opts_exp)" " && "$prev" != "help" ]] ; then COMPREPLY=($(compgen -W "${_murano_flags}" -- "${cur}")) else COMPREPLY=($(compgen -W "${_murano_opts}" -- "${cur}")) fi return 0 } complete -o default -F _murano murano python-muranoclient-1.3.0/tools/install_venv_common.py0000664000175000017500000001631513523272253023354 0ustar zuulzuul00000000000000# Copyright 2013 OpenStack, LLC # Copyright 2013 IBM Corp. # # 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. """Provides methods needed by installation script for OpenStack development virtual environments. Synced in from openstack-common """ from __future__ import print_function import argparse import os import subprocess import sys class InstallVenv(object): def __init__(self, root, venv, requirements, test_requirements, py_version, project): self.root = root self.venv = venv self.requirements = requirements self.test_requirements = test_requirements self.py_version = py_version self.project = project def die(self, message, *args): print(message % args, file=sys.stderr) sys.exit(1) def check_python_version(self): if sys.version_info < (2, 6): self.die("Need Python Version >= 2.6") def run_command_with_code(self, cmd, redirect_output=True, check_exit_code=True): """Runs a command in an out-of-process shell. Returns the output of that command. Working directory is self.root. """ if redirect_output: stdout = subprocess.PIPE else: stdout = None proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout) output = proc.communicate()[0] if check_exit_code and proc.returncode != 0: self.die('Command "%s" failed.\n%s', ' '.join(cmd), output) return (output, proc.returncode) def run_command(self, cmd, redirect_output=True, check_exit_code=True): return self.run_command_with_code(cmd, redirect_output, check_exit_code)[0] def get_distro(self): if (os.path.exists('/etc/fedora-release') or os.path.exists('/etc/redhat-release')): return Fedora( self.root, self.venv, self.requirements, self.test_requirements, self.py_version, self.project) else: return Distro( self.root, self.venv, self.requirements, self.test_requirements, self.py_version, self.project) def check_dependencies(self): self.get_distro().install_virtualenv() def create_virtualenv(self, no_site_packages=True): """Creates the virtual environment and installs PIP. Creates the virtual environment and installs PIP only into the virtual environment. """ if not os.path.isdir(self.venv): print('Creating venv...'), if no_site_packages: self.run_command(['virtualenv', '-q', '--no-site-packages', self.venv]) else: self.run_command(['virtualenv', '-q', self.venv]) print('done.') else: print("venv already exists...") pass def pip_install(self, *args): self.run_command(['tools/with_venv.sh', 'pip', 'install', '--upgrade'] + list(args), redirect_output=False) def install_dependencies(self): print('Installing dependencies with pip (this can take a while)...') # First things first, make sure our venv has the latest pip and # distribute. # NOTE: we keep pip at version 1.1 since the most recent version causes # the .venv creation to fail. See: # https://bugs.launchpad.net/nova/+bug/1047120 self.pip_install('pip==1.1') self.pip_install('setuptools') self.pip_install('-r', self.requirements) self.pip_install('-r', self.test_requirements) def post_process(self): self.get_distro().post_process() def parse_args(self, argv): """Parses command-line arguments.""" parser = argparse.ArgumentParser() parser.add_argument('-n', '--no-site-packages', action='store_true', help="Do not inherit packages from global Python " "install") return parser.parse_args(argv[1:]) class Distro(InstallVenv): def check_cmd(self, cmd): return bool(self.run_command(['which', cmd], check_exit_code=False).strip()) def install_virtualenv(self): if self.check_cmd('virtualenv'): return if self.check_cmd('easy_install'): print('Installing virtualenv via easy_install...'), if self.run_command(['easy_install', 'virtualenv']): print('Succeeded') return else: print('Failed') self.die('ERROR: virtualenv not found.\n\n%s development' ' requires virtualenv, please install it using your' ' favorite package management tool' % self.project) def post_process(self): """Any distribution-specific post-processing gets done here. In particular, this is useful for applying patches to code inside the venv. """ pass class Fedora(Distro): """This covers all Fedora-based distributions. Includes: Fedora, RHEL, CentOS, Scientific Linux """ def check_pkg(self, pkg): return self.run_command_with_code(['rpm', '-q', pkg], check_exit_code=False)[1] == 0 def yum_install(self, pkg, **kwargs): print("Attempting to install '%s' via yum" % pkg) self.run_command(['sudo', 'yum', 'install', '-y', pkg], **kwargs) def apply_patch(self, originalfile, patchfile): self.run_command(['patch', originalfile, patchfile]) def install_virtualenv(self): if self.check_cmd('virtualenv'): return if not self.check_pkg('python-virtualenv'): self.yum_install('python-virtualenv', check_exit_code=False) super(Fedora, self).install_virtualenv() def post_process(self): """Workaround for a bug in eventlet. This currently affects RHEL6.1, but the fix can safely be applied to all RHEL and Fedora distributions. This can be removed when the fix is applied upstream. Nova: https://bugs.launchpad.net/nova/+bug/884915 Upstream: https://bitbucket.org/which_linden/eventlet/issue/89 """ # Install "patch" program if it's not there if not self.check_pkg('patch'): self.yum_install('patch') # Apply the eventlet patch self.apply_patch(os.path.join(self.venv, 'lib', self.py_version, 'site-packages', 'eventlet/green/subprocess.py'), 'contrib/redhat-eventlet.patch') python-muranoclient-1.3.0/tools/with_venv.sh0000775000175000017500000000012413523272253021265 0ustar zuulzuul00000000000000#!/bin/bash TOOLS=`dirname $0` VENV=$TOOLS/../.venv source $VENV/bin/activate && $@ python-muranoclient-1.3.0/tools/install_venv.py0000664000175000017500000000504013523272253021775 0ustar zuulzuul00000000000000# Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. # # Copyright 2010 OpenStack Foundation # Copyright 2013 IBM Corp. # # 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. """ Installation script for python-muranoclient's development virtualenv """ from __future__ import print_function import os import sys from six.moves import configparser import install_venv_common as install_venv def print_help(project, venv, root): help = """ %(project)s development environment setup is complete. %(project)s development uses virtualenv to track and manage Python dependencies while in development and testing. To activate the %(project)s virtualenv for the extent of your current shell session you can run: $ . %(venv)s/bin/activate Or, if you prefer, you can run commands in the virtualenv on a case by case basis by running: $ %(root)s/tools/with_venv.sh """ print(help % dict(project=project, venv=venv, root=root)) def main(argv): root = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) if os.environ.get('tools_path'): root = os.environ['tools_path'] venv = os.path.join(root, '.venv') if os.environ.get('venv'): venv = os.environ['venv'] pip_requires = os.path.join(root, 'requirements.txt') test_requires = os.path.join(root, 'test-requirements.txt') py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1]) setup_cfg = configparser.ConfigParser() setup_cfg.read('setup.cfg') project = setup_cfg.get('metadata', 'name') install = install_venv.InstallVenv( root, venv, pip_requires, test_requires, py_version, project) options = install.parse_args(argv) install.check_python_version() install.check_dependencies() install.create_virtualenv(no_site_packages=options.no_site_packages) install.install_dependencies() install.post_process() print_help(project, venv, root) if __name__ == '__main__': sys.exit(main(sys.argv)) python-muranoclient-1.3.0/releasenotes/0000775000175000017500000000000013523272337020254 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/releasenotes/source/0000775000175000017500000000000013523272337021554 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/releasenotes/source/locale/0000775000175000017500000000000013523272337023013 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/releasenotes/source/locale/en_GB/0000775000175000017500000000000013523272337023765 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/releasenotes/source/locale/en_GB/LC_MESSAGES/0000775000175000017500000000000013523272337025552 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po0000664000175000017500000003401713523272253030605 0ustar zuulzuul00000000000000# Andi Chandler , 2017. #zanata # Andi Chandler , 2018. #zanata msgid "" msgstr "" "Project-Id-Version: python-muranoclient\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-08-08 06:14+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2018-08-09 08:51+0000\n" "Last-Translator: Andi Chandler \n" "Language-Team: English (United Kingdom)\n" "Language: en_GB\n" "X-Generator: Zanata 4.3.3\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" msgid "0.10.0" msgstr "0.10.0" msgid "0.11.1" msgstr "0.11.1" msgid "0.12.0" msgstr "0.12.0" msgid "0.7.3" msgstr "0.7.3" msgid "0.8.1" msgstr "0.8.1" msgid "0.8.2" msgstr "0.8.2" msgid "0.8.4" msgstr "0.8.4" msgid "0.8.5" msgstr "0.8.5" msgid "0.8.6" msgstr "0.8.6" msgid "0.9.0" msgstr "0.9.0" msgid "" "Ability to load package from directory was added. If specified directory " "contains all the needed files then package will be imported as usual." msgstr "" "Ability to load package from directory was added. If specified directory " "contains all the needed files then package will be imported as usual." msgid "" "Add an ability to use arguments --resources-dir and --template for the " "command package-create simultaneously. It allows to create HOT-packages with " "Resources folder." msgstr "" "Add an ability to use arguments --resources-dir and --template for the " "command package-create simultaneously. It allows to create HOT-packages with " "Resources folder." msgid "" "Added '--dep-exists-action' option to murano command-line interface. It " "allows to apply different default actions for existing main package and " "existing dependencies. In case of specifying just '--exists-action', action " "for dependencies equals to that like it was previously." msgstr "" "Added '--dep-exists-action' option to Murano command-line interface. It " "allows to apply different default actions for existing main package and " "existing dependencies. In case of specifying just '--exists-action', action " "for dependencies equals to that like it was previously." msgid "" "Added fallback from token to username/password if both are provided and " "token expires or is invalid." msgstr "" "Added fallback from token to username/password if both are provided and " "token expires or is invalid." msgid "" "Added python-muranoclient support in openstack-client by setting entry " "points and implementing interface functions." msgstr "" "Added python-muranoclient support in openstack-client by setting entry " "points and implementing interface functions." msgid "Bug Fixes" msgstr "Bug Fixes" msgid "" "CLI command :command:``env-templat-create-env`` now supports ``--region`` " "flag" msgstr "" "CLI command :command:``env-templat-create-env`` now supports ``--region`` " "flag" msgid "" "Changed murano-client CLI outputs to show only items affected by current " "operation instead of full item list." msgstr "" "Changed murano-client CLI outputs to show only items affected by current " "operation instead of full item list." msgid "Current Series Release Notes" msgstr "Current Series Release Notes" msgid "Deprecation Notes" msgstr "Deprecation Notes" msgid "" "Fixed a bug when a package containing multi-class yamls could not be added " "to glare-based catalog." msgstr "" "Fixed a bug when a package containing multi-class yamls could not be added " "to glare-based catalogue." msgid "" "Fixed order of packages import - main package can not be imported before all " "its dependencies are imported. Cyclic requirements are imported in random " "order." msgstr "" "Fixed order of packages import - main package can not be imported before all " "its dependencies are imported. Cyclic requirements are imported in random " "order." msgid "" "Fixes use murano package-import with version parameter, It always shows " "version of client. Rename the parameter 'version' to 'package-version'" msgstr "" "Fixes use Murano package-import with version parameter, It always shows " "version of client. Rename the parameter 'version' to 'package-version'" msgid "" "Importing a package into glare, now fills 'inherited' field with full " "inheritance info (previously it only contained immediate parent classes)." msgstr "" "Importing a package into glare, now fills 'inherited' field with full " "inheritance info (previously it only contained immediate parent classes)." msgid "" "It is now possible to import packages with '!yaql' tag, when glare is used " "as backend. Before this fix, importing such package caused a parsing error." msgstr "" "It is now possible to import packages with '!yaql' tag, when Glare is used " "as backend. Before this fix, importing such package caused a parsing error." msgid "" "It was impossible to use `--owned` flag when Glare was used to filter " "packages. This issue is fixed now." msgstr "" "It was impossible to use `--owned` flag when Glare was used to filter " "packages. This issue is fixed now." msgid "" "It was possible to import the same murano package from the CLI into the same " "project multiple times if glare was used and the package was imported as a " "private one. The issue is now fixed." msgstr "" "It was possible to import the same Murano package from the CLI into the same " "project multiple times if Glare was used and the package was imported as a " "private one. The issue is now fixed." msgid "Liberty Series Release Notes" msgstr "Liberty Series Release Notes" msgid "Mitaka Series Release Notes" msgstr "Mitaka Series Release Notes" msgid "Murano Client Release Notes" msgstr "Murano Client Release Notes" msgid "New Features" msgstr "New Features" msgid "" "New Murano CLI command ``murano environment-model-edit --session-" "id ``" msgstr "" "New Murano CLI command ``murano environment-model-edit --session-" "id ``" msgid "" "New Murano CLI command ``murano environment-model-show [--path ] " "[--session-id ]``" msgstr "" "New Murano CLI command ``murano environment-model-show [--path ] " "[--session-id ]``" msgid "New OSC command ``category create ``" msgstr "New OSC command ``category create ``" msgid "New OSC command ``category delete [ ...]``" msgstr "New OSC command ``category delete [ ...]``" msgid "New OSC command ``category list``" msgstr "New OSC command ``category list``" msgid "New OSC command ``category show ``" msgstr "New OSC command ``category show ``" msgid "" "New OSC command ``environment create [--join-net-id ] [--join-subnet-" "id ] [--region ] ``" msgstr "" "New OSC command ``environment create [--join-net-id ] [--join-subnet-" "id ] [--region ] ``" msgid "" "New OSC command ``environment delete [--abandon] [ ...]``" msgstr "" "New OSC command ``environment delete [--abandon] [ ...]``" msgid "New OSC command ``environment list [--all-tenants]``" msgstr "New OSC command ``environment list [--all-tenants]``" msgid "New OSC command ``environment rename ``" msgstr "New OSC command ``environment rename ``" msgid "New OSC command ``environment session create ``" msgstr "New OSC command ``environment session create ``" msgid "" "New OSC command ``environment show [--session-id ] " "[--only-apps]``" msgstr "" "New OSC command ``environment show [--session-id ] " "[--only-apps]``" msgid "" "New OSC command ``openstack class-schema [--package-name PACKAGE_NAME] [--" "class-version PACKAGE_VERSION] [ [ ...]]``" msgstr "" "New OSC command ``openstack class-schema [--package-name PACKAGE_NAME] [--" "class-version PACKAGE_VERSION] [ [ ...]]``" msgid "" "New OSC command ``openstack environment model edit --session-id " "``" msgstr "" "New OSC command ``openstack environment model edit --session-id " "``" msgid "" "New OSC command ``openstack environment model show [--path ] [--" "session-id ]``" msgstr "" "New OSC command ``openstack environment model show [--path ] [--" "session-id ]``" msgid "New OSC command ``package list``." msgstr "New OSC command ``package list``." msgid "" "New murano CLI command ``murano class-schema [--package-name PACKAGE_NAME] " "[--class-version PACKAGE_VERSION] [ [ ...]]``" msgstr "" "New Murano CLI command ``murano class-schema [--package-name PACKAGE_NAME] " "[--class-version PACKAGE_VERSION] [ [ ...]]``" msgid "Newton Series Release Notes" msgstr "Newton Series Release Notes" msgid "" "OSC plugin now honores ``--murano-url`` and ``MURANO_URL``, to allow using " "custom murano endpoint" msgstr "" "OSC plugin now honores ``--murano-url`` and ``MURANO_URL``, to allow using " "custom murano endpoint" msgid "Ocata Series Release Notes" msgstr "Ocata Series Release Notes" msgid "Pike Series Release Notes" msgstr "Pike Series Release Notes" msgid "Queens Series Release Notes" msgstr "Queens Series Release Notes" msgid "Rocky Series Release Notes" msgstr "Rocky Series Release Notes" msgid "Security Issues" msgstr "Security Issues" msgid "" "Since glare has been moved to a separate service muranoclient CLI now " "distinguishes between --glance-url and --glare-url. If --glare-url is not " "supplied muranoclient requests an endpoint of type 'artifact' from keystone." msgstr "" "Since Glare has been moved to a separate service muranoclient CLI now " "distinguishes between --glance-url and --glare-url. If --glare-url is not " "supplied muranoclient requests an endpoint of type 'artifact' from keystone." msgid "" "Since glare is a separate project now usage of 'glance' value for --murano-" "packages-service has been deprecated and is scheduled to be removed in P " "cycle" msgstr "" "Since Glare is a separate project now usage of 'glance' value for --murano-" "packages-service has been deprecated and is scheduled to be removed in P " "cycle" msgid "Support for class/method JSON-schema API was added" msgstr "Support for class/method JSON-schema API was added" msgid "Support for environment model edit API was added" msgstr "Support for environment model edit API was added" msgid "" "Support for static actions API was added - New Murano CLI command ``murano " "static-action-call [--arguments [ [ ...]]] [--package-" "name ] [--class-version CLASS_VERSION] `` - New OSC " "command ``openstack static-action call [--arguments [ " "[ ...]]] [--package-name ] [--class-version " "CLASS_VERSION] ``" msgstr "" "Support for static actions API was added - New Murano CLI command ``murano " "static-action-call [--arguments [ [ ...]]] [--package-" "name ] [--class-version CLASS_VERSION] `` - New OSC " "command ``openstack static-action call [--arguments [ " "[ ...]]] [--package-name ] [--class-version " "CLASS_VERSION] ``" msgid "" "The client now is able to consume the updated `List Environments` API call, " "which may be used to filter environments by an owner project (tenant)." msgstr "" "The client now is able to consume the updated `List Environments` API call, " "which may be used to filter environments by an owner project (tenant)." msgid "Upgrade Notes" msgstr "Upgrade Notes" msgid "" "cli now makes use of type of the endpoint (defined either as a --os-endpoint-" "type command line parameter or OS_ENDPOINT_TYPE environment variable). This " "type defines which interface will be used when connecting to murano, glance " "and glare APIs." msgstr "" "CLI now makes use of type of the endpoint (defined either as a --os-endpoint-" "type command line parameter or OS_ENDPOINT_TYPE environment variable). This " "type defines which interface will be used when connecting to Murano, Glance " "and Glare APIs." msgid "" "command 'package_update' was broken since murano start support of keystone " "v3. The error occurred because murano tried to pass custom content type " "parameter in the request kwargs, not headers." msgstr "" "command 'package_update' was broken since Murano start support of keystone " "v3. The error occurred because Murano tried to pass custom content type " "parameter in the request kwargs, not headers." msgid "" "cve-2016-4972 has been addressed. In ceveral places Murano used loaders " "inherited directly from yaml.Loader when parsing MuranoPL and UI files from " "packages. This is unsafe, because this loader is capable of creating custom " "python objects from specifically constructed yaml files. With this change " "all yaml loading operations are done using safe loaders instead." msgstr "" "cve-2016-4972 has been addressed. In several places Murano used loaders " "inherited directly from yaml.Loader when parsing MuranoPL and UI files from " "packages. This is unsafe, because this loader is capable of creating custom " "python objects from specifically constructed yaml files. With this change " "all yaml loading operations are done using safe loaders instead." msgid "" "environment-action-call command now accepts action argument values of any " "type in JSON format, for example environment-action-call $ENV_ID --action-id " "$ACT_ID --arguments foo=bar listArg='[\"item1\", \"item2\", \"item3\"]' " "nullArg=null stringArg='\"null\"' intArg=5" msgstr "" "environment-action-call command now accepts action argument values of any " "type in JSON format, for example environment-action-call $ENV_ID --action-id " "$ACT_ID --arguments foo=bar listArg='[\"item1\", \"item2\", \"item3\"]' " "nullArg=null stringArg='\"null\"' intArg=5" msgid "" "fixes no attribute error caused by the new raw_request return, by let the " "SessionClient raw_request only return response without data." msgstr "" "fixes no attribute error caused by the new raw_request return, by let the " "SessionClient raw_request only return response without data." python-muranoclient-1.3.0/releasenotes/source/locale/fr/0000775000175000017500000000000013523272337023422 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/releasenotes/source/locale/fr/LC_MESSAGES/0000775000175000017500000000000013523272337025207 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po0000664000175000017500000000265713523272253030247 0ustar zuulzuul00000000000000# Gérald LONLAS , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: Murano Client Release Notes 0.12.1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-03-14 12:38+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2016-10-22 06:11+0000\n" "Last-Translator: Gérald LONLAS \n" "Language-Team: French\n" "Language: fr\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" msgid "0.10.0" msgstr "0.10.0" msgid "0.11.1" msgstr "0.11.1" msgid "0.7.3" msgstr "0.7.3" msgid "0.8.1" msgstr "0.8.1" msgid "0.8.2" msgstr "0.8.2" msgid "0.8.4" msgstr "0.8.4" msgid "0.8.5" msgstr "0.8.5" msgid "0.8.6" msgstr "0.8.6" msgid "0.9.0" msgstr "0.9.0" msgid "Bug Fixes" msgstr "Corrections de bugs" msgid "Current Series Release Notes" msgstr "Note de la release actuelle" msgid "Deprecation Notes" msgstr "Notes dépréciées " msgid "Liberty Series Release Notes" msgstr "Note de release pour Liberty" msgid "Mitaka Series Release Notes" msgstr "Note de release pour Mitaka" msgid "Murano Client Release Notes" msgstr "Note de release du Client Murano" msgid "New Features" msgstr "Nouvelles fonctionnalités" msgid "Newton Series Release Notes" msgstr "Note de release pour Newton" msgid "Security Issues" msgstr "Problèmes de sécurités" msgid "Upgrade Notes" msgstr "Notes de mises à jours" python-muranoclient-1.3.0/releasenotes/source/_templates/0000775000175000017500000000000013523272337023711 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/releasenotes/source/_templates/.placeholder0000664000175000017500000000000013523272253026157 0ustar zuulzuul00000000000000python-muranoclient-1.3.0/releasenotes/source/liberty.rst0000664000175000017500000000022213523272253023751 0ustar zuulzuul00000000000000============================== Liberty Series Release Notes ============================== .. release-notes:: :branch: origin/stable/liberty python-muranoclient-1.3.0/releasenotes/source/newton.rst0000664000175000017500000000023213523272253023612 0ustar zuulzuul00000000000000=================================== Newton Series Release Notes =================================== .. release-notes:: :branch: origin/stable/newton python-muranoclient-1.3.0/releasenotes/source/queens.rst0000664000175000017500000000022313523272253023600 0ustar zuulzuul00000000000000=================================== Queens Series Release Notes =================================== .. release-notes:: :branch: stable/queens python-muranoclient-1.3.0/releasenotes/source/pike.rst0000664000175000017500000000021713523272253023233 0ustar zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike python-muranoclient-1.3.0/releasenotes/source/mitaka.rst0000664000175000017500000000023213523272253023546 0ustar zuulzuul00000000000000=================================== Mitaka Series Release Notes =================================== .. release-notes:: :branch: origin/stable/mitaka python-muranoclient-1.3.0/releasenotes/source/ocata.rst0000664000175000017500000000023013523272253023365 0ustar zuulzuul00000000000000=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: origin/stable/ocata python-muranoclient-1.3.0/releasenotes/source/index.rst0000664000175000017500000000032213523272253023407 0ustar zuulzuul00000000000000============================= Murano Client Release Notes ============================= .. toctree:: :maxdepth: 2 unreleased stein rocky queens pike ocata newton mitaka liberty python-muranoclient-1.3.0/releasenotes/source/rocky.rst0000664000175000017500000000022113523272253023425 0ustar zuulzuul00000000000000=================================== Rocky Series Release Notes =================================== .. release-notes:: :branch: stable/rocky python-muranoclient-1.3.0/releasenotes/source/_static/0000775000175000017500000000000013523272337023202 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/releasenotes/source/_static/.placeholder0000664000175000017500000000000013523272253025450 0ustar zuulzuul00000000000000python-muranoclient-1.3.0/releasenotes/source/unreleased.rst0000664000175000017500000000016013523272253024427 0ustar zuulzuul00000000000000============================== Current Series Release Notes ============================== .. release-notes:: python-muranoclient-1.3.0/releasenotes/source/stein.rst0000664000175000017500000000022113523272253023420 0ustar zuulzuul00000000000000=================================== Stein Series Release Notes =================================== .. release-notes:: :branch: stable/stein python-muranoclient-1.3.0/releasenotes/source/conf.py0000664000175000017500000002124113523272253023050 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. # Murano 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 = [ 'reno.sphinxext', 'openstackdocstheme', ] # 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. copyright = u'2015, Murano 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 = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # html_theme_path = [openstackdocstheme.get_html_theme_path()] # openstackdocstheme options repository_name = 'openstack/python-muranoclient' bug_project = 'python-muranoclient' bug_tag = '' # 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 = 'MuranoClientReleaseNotesdoc' # -- Options for LaTeX output --------------------------------------------- # 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', 'MuranoClientReleaseNotes.tex', u'Murano Client Release Notes Documentation', u'Murano 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', 'muranoclientreleasenotes', u'Murano Client Release Notes Documentation', [u'Murano 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', 'MuranoClientReleaseNotes', u'Murano Client Release Notes Documentation', u'Murano Developers', 'MuranoClientReleaseNotes', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # 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-muranoclient-1.3.0/releasenotes/notes/0000775000175000017500000000000013523272337021404 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/releasenotes/notes/fix-owned-flag-e8b718c074c1c314.yaml0000664000175000017500000000017413523272253027227 0ustar zuulzuul00000000000000--- fixes: - It was impossible to use `--owned` flag when Glare was used to filter packages. This issue is fixed now. ././@LongLink0000000000000000000000000000015400000000000011215 Lustar 00000000000000python-muranoclient-1.3.0/releasenotes/notes/deprecate-murano-packages-service-glance-7d0052a5256adace.yamlpython-muranoclient-1.3.0/releasenotes/notes/deprecate-murano-packages-service-glance-7d0052a5256ada0000664000175000017500000000027013523272253033167 0ustar zuulzuul00000000000000--- deprecations: - Since glare is a separate project now usage of 'glance' value for --murano-packages-service has been deprecated and is scheduled to be removed in P cycle python-muranoclient-1.3.0/releasenotes/notes/reauth-fix-e03ad966c3178167.yaml0000664000175000017500000000017013523272253026422 0ustar zuulzuul00000000000000--- fixes: - Added fallback from token to username/password if both are provided and token expires or is invalid. python-muranoclient-1.3.0/releasenotes/notes/osc-murano-url-507932234b49cf9f.yaml0000664000175000017500000000016413523272253027240 0ustar zuulzuul00000000000000--- features: - OSC plugin now honores ``--murano-url`` and ``MURANO_URL``, to allow using custom murano endpoint python-muranoclient-1.3.0/releasenotes/notes/bug-1536121-51c42f77a9e97db1.yaml0000664000175000017500000000024613523272253026112 0ustar zuulzuul00000000000000--- fixes: - Fixes use murano package-import with version parameter, It always shows version of client. Rename the parameter 'version' to 'package-version' python-muranoclient-1.3.0/releasenotes/notes/import-package-from-directory-8f2b5e393004ef97.yaml0000664000175000017500000000025013523272253032277 0ustar zuulzuul00000000000000--- features: - Ability to load package from directory was added. If specified directory contains all the needed files then package will be imported as usual.python-muranoclient-1.3.0/releasenotes/notes/multiple-packages-glare-fix-b82a473ad976028f.yaml0000664000175000017500000000032413523272253031712 0ustar zuulzuul00000000000000--- fixes: - It was possible to import the same murano package from the CLI into the same project multiple times if glare was used and the package was imported as a private one. The issue is now fixed. python-muranoclient-1.3.0/releasenotes/notes/bug-1527045-97993f04757b4901.yaml0000664000175000017500000000023713523272253025630 0ustar zuulzuul00000000000000--- fixes: - fixes no attribute error caused by the new raw_request return, by let the SessionClient raw_request only return response without data. python-muranoclient-1.3.0/releasenotes/notes/osc-package-list-command-23e39dc92ead8834.yaml0000664000175000017500000000006413523272253031255 0ustar zuulzuul00000000000000--- features: - New OSC command ``package list``. python-muranoclient-1.3.0/releasenotes/notes/added-category-command-01cb9dab21ab4a7b.yaml0000664000175000017500000000030613523272253031163 0ustar zuulzuul00000000000000--- features: - New OSC command ``category list`` - New OSC command ``category show `` - New OSC command ``category create `` - New OSC command ``category delete [ ...]`` python-muranoclient-1.3.0/releasenotes/notes/added-static-actions-3d45f5cdc5491770.yaml0000664000175000017500000000065513523272253030422 0ustar zuulzuul00000000000000--- features: - Support for static actions API was added - New Murano CLI command ``murano static-action-call [--arguments [ [ ...]]] [--package-name ] [--class-version CLASS_VERSION] `` - New OSC command ``openstack static-action call [--arguments [ [ ...]]] [--package-name ] [--class-version CLASS_VERSION] `` python-muranoclient-1.3.0/releasenotes/notes/resources-dir-for-hot-c557c1472bbc79fa.yaml0000664000175000017500000000030413523272253030724 0ustar zuulzuul00000000000000--- fixes: - Added ability to use arguments ``--resources-dir`` and ``--template`` for ``package-create`` command simultaneously. It allows to create HOT-packages with Resources folder. python-muranoclient-1.3.0/releasenotes/notes/dep-exists-action-9af18bebcc0ef053.yaml0000664000175000017500000000050213523272253030252 0ustar zuulzuul00000000000000--- features: - Added '--dep-exists-action' option to murano ``package-import`` and ``bundle-import`` commands. It allows to specify a different default action for existing main package and existing dependencies. In case of specifying just '--exists-action', action for dependencies also defaults to it. python-muranoclient-1.3.0/releasenotes/notes/.placeholder0000664000175000017500000000000013523272253023652 0ustar zuulzuul00000000000000python-muranoclient-1.3.0/releasenotes/notes/action-arguments-06a554f76783f3ed.yaml0000664000175000017500000000044113523272253027715 0ustar zuulzuul00000000000000--- feature: - environment-action-call command now accepts action argument values of any type in JSON format, for example - ``environment-action-call $ENV_ID --action-id $ACT_ID --arguments foo=bar listArg='["item1", "item2", "item3"]' nullArg=null stringArg='"null"' intArg=5`` python-muranoclient-1.3.0/releasenotes/notes/requirements-order-19ecc40ca6d34739.yaml0000664000175000017500000000026513523272253030342 0ustar zuulzuul00000000000000--- fixes: - Fixed order of packages import - main package can not be imported before all its dependencies are imported. Cyclic requirements are imported in random order. python-muranoclient-1.3.0/releasenotes/notes/enable-openstack-client-support-a273e33d6c31e36e.yaml0000664000175000017500000000021213523272253032672 0ustar zuulzuul00000000000000--- features: - Added python-muranoclient support in openstack-client by setting entry points and implementing interface functions. python-muranoclient-1.3.0/releasenotes/notes/added-environment-command-85b31973e101fd1f.yaml0000664000175000017500000000040413523272253031435 0ustar zuulzuul00000000000000--- features: - New OSC command ``environment create [--join-net-id ] [--join-subnet-id ] [--region ] `` - New OSC command ``environment delete [--abandon] [ ...]`` python-muranoclient-1.3.0/releasenotes/notes/safeloader-cve-2016-4972-e3f7ad9b234655ca.yaml0000664000175000017500000000062513523272253030440 0ustar zuulzuul00000000000000--- security: - cve-2016-4972 has been addressed. In ceveral places Murano used loaders inherited directly from yaml.Loader when parsing MuranoPL and UI files from packages. This is unsafe, because this loader is capable of creating custom python objects from specifically constructed yaml files. With this change all yaml loading operations are done using safe loaders instead. python-muranoclient-1.3.0/releasenotes/notes/environment-edit-7faf5c8e8a3d44ac.yaml0000664000175000017500000000075513523272253030225 0ustar zuulzuul00000000000000--- features: - Support for environment model edit API was added - New Murano CLI command ``murano environment-model-show [--path ] [--session-id ]`` - New Murano CLI command ``murano environment-model-edit --session-id `` - New OSC command ``openstack environment model show [--path ] [--session-id ]`` - New OSC command ``openstack environment model edit --session-id `` python-muranoclient-1.3.0/releasenotes/notes/multi-class-yamls-support-914b3d155324214f.yaml0000664000175000017500000000016613523272253031347 0ustar zuulzuul00000000000000--- fixes: - Fixed a bug when a package containing multi-class yamls could not be added to glare-based catalog. ././@LongLink0000000000000000000000000000015000000000000011211 Lustar 00000000000000python-muranoclient-1.3.0/releasenotes/notes/list-environments-of-a-given-project-e407dd5271649ad2.yamlpython-muranoclient-1.3.0/releasenotes/notes/list-environments-of-a-given-project-e407dd5271649ad2.y0000664000175000017500000000025113523272253033021 0ustar zuulzuul00000000000000--- features: - The client now is able to consume the updated `List Environments` API call, which may be used to filter environments by an owner project (tenant). python-muranoclient-1.3.0/releasenotes/notes/repair-package-update-command-72e4c85da1b2c897.yaml0000664000175000017500000000032213523272253032255 0ustar zuulzuul00000000000000fixes: - command 'package_update' was broken since murano start support of keystone v3. The error occurred because murano tried to pass custom content type parameter in the request kwargs, not headers. python-muranoclient-1.3.0/releasenotes/notes/bug-1629221-e7f1766eb9878f23.yaml0000664000175000017500000000014213523272253026050 0ustar zuulzuul00000000000000--- features: - CLI command :command:``env-templat-create-env`` now supports ``--region`` flag python-muranoclient-1.3.0/releasenotes/notes/added-schemas-support-e4a6e44c7bac2751.yaml0000664000175000017500000000057513523272253030761 0ustar zuulzuul00000000000000--- features: - Support for class/method JSON-schema API was added - New murano CLI command ``murano class-schema [--package-name PACKAGE_NAME] [--class-version PACKAGE_VERSION] [ [ ...]]`` - New OSC command ``openstack class-schema [--package-name PACKAGE_NAME] [--class-version PACKAGE_VERSION] [ [ ...]]`` python-muranoclient-1.3.0/releasenotes/notes/yamlloader-glare-d7d0506f6711b650.yaml0000664000175000017500000000025613523272253027562 0ustar zuulzuul00000000000000--- fixes: - It is now possible to import packages with '!yaql' tag, when glare is used as backend. Before this fix, importing such package caused a parsing error. python-muranoclient-1.3.0/releasenotes/notes/glare-endpoint-cdba1b2351c19592.yaml0000664000175000017500000000037313523272253027402 0ustar zuulzuul00000000000000--- features: - Since glare has been moved to a separate service muranoclient CLI now distinguishes between --glance-url and --glare-url. If --glare-url is not supplied muranoclient requests an endpoint of type 'artifact' from keystone. python-muranoclient-1.3.0/releasenotes/notes/improved-cli-outputs-a3e75cf0224a1993.yaml0000664000175000017500000000020113523272253030523 0ustar zuulzuul00000000000000--- fixes: - Changed murano-client CLI outputs to show only items affected by current operation instead of full item list. python-muranoclient-1.3.0/releasenotes/notes/add-environment-support-to-OSC-173b86ec631283b7.yaml0000664000175000017500000000043613523272253032216 0ustar zuulzuul00000000000000--- features: - New OSC command ``environment list [--all-tenants]`` - New OSC command ``environment show [--session-id ] [--only-apps]`` - New OSC command ``environment rename `` - New OSC command ``environment session create `` python-muranoclient-1.3.0/releasenotes/notes/support-endpoint-type-d7c6b32098b67eeb.yaml0000664000175000017500000000041413523272253031101 0ustar zuulzuul00000000000000fixes: - cli now makes use of type of the endpoint (defined either as a --os-endpoint-type command line parameter or OS_ENDPOINT_TYPE environment variable). This type defines which interface will be used when connecting to murano, glance and glare APIs. python-muranoclient-1.3.0/releasenotes/notes/global-inherits-fix-6da007ec44a774f2.yaml0000664000175000017500000000024313523272253030345 0ustar zuulzuul00000000000000--- fixes: - Importing a package into glare, now fills 'inherited' field with full inheritance info (previously it only contained immediate parent classes). python-muranoclient-1.3.0/lower-constraints.txt0000664000175000017500000000325313523272253022021 0ustar zuulzuul00000000000000alabaster==0.7.10 appdirs==1.3.0 asn1crypto==0.23.0 Babel==2.3.4 cffi==1.7.0 cliff==2.8.0 cmd2==0.8.0 coverage==4.0 cryptography==2.1 debtcollector==1.2.0 decorator==3.4.0 deprecation==1.0 docutils==0.11 dogpile.cache==0.6.2 dulwich==0.15.0 extras==1.0.0 fasteners==0.7.0 fixtures==3.0.0 flake8==2.5.5 future==0.16.0 hacking==0.12.0 idna==2.6 imagesize==0.7.1 iso8601==0.1.11 Jinja2==2.10 jmespath==0.9.0 jsonpatch==1.16 jsonpointer==1.13 jsonschema==2.6.0 keystoneauth1==3.4.0 linecache2==1.0.0 MarkupSafe==1.0 mccabe==0.2.1 mock==2.0.0 monotonic==0.6 mox3==0.20.0 msgpack-python==0.4.0 munch==2.1.0 murano-pkg-check==0.3.0 netaddr==0.7.18 netifaces==0.10.4 openstackdocstheme==1.20.0 openstacksdk==0.11.2 os-client-config==1.28.0 os-service-types==1.2.0 os-testr==1.0.0 osc-lib==1.8.0 oslo.concurrency==3.25.0 oslo.config==5.2.0 oslo.context==2.19.2 oslo.i18n==3.15.3 oslo.log==3.36.0 oslo.serialization==2.18.0 oslo.utils==3.33.0 oslotest==3.2.0 paramiko==2.0.0 pbr==2.0.0 pep8==1.5.7 ply==3.10 positional==1.2.1 prettytable==0.7.2 pyasn1==0.1.8 pycparser==2.18 pyflakes==0.8.1 Pygments==2.2.0 pyinotify==0.9.6 pyOpenSSL==17.1.0 pyparsing==2.1.0 pyperclip==1.5.27 python-dateutil==2.5.3 python-glanceclient==2.8.0 python-keystoneclient==3.8.0 python-mimeparse==1.6.0 python-subunit==1.0.0 pytz==2013.6 PyYAML==3.12 reno==2.5.0 requests==2.14.2 requests-mock==1.1.0 requestsexceptions==1.2.0 rfc3986==0.3.1 semantic-version==2.3.1 simplejson==3.5.1 six==1.10.0 snowballstemmer==1.2.1 Sphinx==1.6.2 sphinxcontrib-websupport==1.0.1 stevedore==1.20.0 tempest==17.1.0 stestr==2.0.0 testscenarios==0.4 testtools==2.2.0 traceback2==1.4.0 unittest2==1.1.0 urllib3==1.21.1 warlock==1.2.0 wrapt==1.7.0 yaql==1.1.3 python-muranoclient-1.3.0/tox.ini0000664000175000017500000000472413523272253017102 0ustar zuulzuul00000000000000[tox] envlist = py37,py27,pep8,docs minversion = 2.0 skipsdist = True [testenv] usedevelop = True whitelist_externals = bash find install_command = pip install {opts} {packages} setenv = VIRTUAL_ENV={envdir} passenv = http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = stestr run {posargs} [testenv:pep8] basepython = python3 commands = flake8 {posargs} [testenv:py27-queens] install_command = pip install {opts} {packages} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/queens} [testenv:venv] basepython = python3 commands = {posargs} [testenv:functional] setenv = OS_TEST_PATH = ./muranoclient/tests/functional passenv = OS_* MURANO_PACKAGES_SERVICE [testenv:uitests] basepython = python3 commands = stestr run --slowest --concurrency 1 {posargs} [testenv:cover] basepython = python3 setenv = {[testenv]setenv} PYTHON=coverage run --source muranoclient --parallel-mode commands = stestr run {posargs} coverage combine coverage html -d cover coverage xml -o cover/coverage.xml [testenv:debug] basepython = python3 commands = find . -type f -name "*.pyc" -delete oslo_debug_helper -t muranoclient/tests {posargs} [testenv:pyflakes] basepython = python3 deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} flake8 commands = flake8 [testenv:docs] basepython = python3 deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = sphinx-build -W -b html doc/source doc/build/html [testenv:releasenotes] basepython = python3 deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [flake8] show-source = true builtins = _ exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,tools,build [testenv:lower-constraints] basepython = python3 deps = -c{toxinidir}/lower-constraints.txt -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt python-muranoclient-1.3.0/setup.sh0000664000175000017500000001255613523272253017265 0ustar zuulzuul00000000000000#!/bin/sh # Copyright (c) 2013 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. # # Ubuntu script. LOGLVL=1 SERVICE_CONTENT_DIRECTORY=`cd $(dirname "$0") && pwd` PREREQ_PKGS="wget make git python-pip python-dev python-mysqldb libxml2-dev libxslt-dev" SERVICE_SRV_NAME="python-muranoclient" GIT_CLONE_DIR=`echo $SERVICE_CONTENT_DIRECTORY | sed -e "s/$SERVICE_SRV_NAME//"` # Functions # Loger function log() { MSG=$1 if [ $LOGLVL -gt 0 ]; then echo "LOG:> $MSG" fi } # Check or install package in_sys_pkg() { PKG=$1 dpkg -s $PKG > /dev/null 2>&1 if [ $? -eq 0 ]; then log "Package \"$PKG\" already installed" else log "Installing \"$PKG\"..." apt-get install $PKG --yes > /dev/null 2>&1 if [ $? -ne 0 ];then log "installation fails, exiting!!!" exit fi fi } # git clone gitclone() { FROM=$1 CLONEROOT=$2 log "Cloning from \"$FROM\" repo to \"$CLONEROOT\"" cd $CLONEROOT && git clone $FROM > /dev/null 2>&1 if [ $? -ne 0 ];then log "cloning from \"$FROM\" fails, exiting!!!" exit fi } # install inst() { CLONE_FROM_GIT=$1 # Checking packages for PKG in $PREREQ_PKGS do in_sys_pkg $PKG done # If clone from git set if [ ! -z $CLONE_FROM_GIT ]; then # Preparing clone root directory if [ ! -d $GIT_CLONE_DIR ];then log "Creating $GIT_CLONE_DIR directory..." mkdir -p $GIT_CLONE_DIR if [ $? -ne 0 ];then log "Can't create $GIT_CLONE_DIR, exiting!!!" exit fi fi # Cloning from GIT GIT_WEBPATH_PRFX="https://opendev.org/openstack/" gitclone "$GIT_WEBPATH_PRFX$SERVICE_SRV_NAME.git" $GIT_CLONE_DIR # End clone from git section fi # Setupping... log "Running setup.py" #MRN_CND_SPY=$GIT_CLONE_DIR/$SERVICE_SRV_NAME/setup.py MRN_CND_SPY=$SERVICE_CONTENT_DIRECTORY/setup.py if [ -e $MRN_CND_SPY ]; then chmod +x $MRN_CND_SPY log "$MRN_CND_SPY output:_____________________________________________________________" #cd $GIT_CLONE_DIR/$SERVICE_SRV_NAME && $MRN_CND_SPY install #if [ $? -ne 0 ]; then # log "\"$MRN_CND_SPY\" python setup FAILS, exiting!" # exit 1 #fi ## Setup through pip # Creating tarball #cd $GIT_CLONE_DIR/$SERVICE_SRV_NAME && $MRN_CND_SPY sdist rm -rf $SERVICE_CONTENT_DIRECTORY/*.egg-info cd $SERVICE_CONTENT_DIRECTORY && python $MRN_CND_SPY egg_info if [ $? -ne 0 ];then log "\"$MRN_CND_SPY\" egg info creation FAILS, exiting!!!" exit 1 fi rm -rf $SERVICE_CONTENT_DIRECTORY/dist/* cd $SERVICE_CONTENT_DIRECTORY && python $MRN_CND_SPY sdist if [ $? -ne 0 ];then log "\"$MRN_CND_SPY\" tarball creation FAILS, exiting!!!" exit 1 fi # Running tarball install #TRBL_FILE=$(basename `ls $GIT_CLONE_DIR/$SERVICE_SRV_NAME/dist/*.tar.gz`) #pip install $GIT_CLONE_DIR/$SERVICE_SRV_NAME/dist/$TRBL_FILE TRBL_FILE=$(basename `ls $SERVICE_CONTENT_DIRECTORY/dist/*.tar.gz`) pip install $SERVICE_CONTENT_DIRECTORY/dist/$TRBL_FILE if [ $? -ne 0 ];then log "pip install \"$TRBL_FILE\" FAILS, exiting!!!" exit 1 fi else log "$MRN_CND_SPY not found!" fi } # uninstall uninst() { # Uninstall trough pip # looking up for python package installed #PYPKG=`echo $SERVICE_SRV_NAME | tr -d '-'` PYPKG="muranoclient" pip freeze | grep $PYPKG if [ $? -eq 0 ]; then log "Removing package \"$PYPKG\" with pip" pip uninstall $PYPKG --yes else log "Python package \"$PYPKG\" not found" fi } # Command line args' COMMAND="$1" case $COMMAND in install ) inst ;; installfromgit ) inst "yes" ;; uninstall ) log "Uninstalling muranoclient \"$SERVICE_SRV_NAME\" from system..." uninst ;; * ) echo "Usage: $(basename "$0") command \nCommands:\n\tinstall - Install $SERVICE_SRV_NAME software\n\tuninstall - Uninstall $SERVICE_SRV_NAME software" exit 1 ;; esac python-muranoclient-1.3.0/requirements.txt0000664000175000017500000000134613523272253021050 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 PrettyTable<0.8,>=0.7.2 # BSD python-glanceclient>=2.8.0 # Apache-2.0 python-keystoneclient>=3.8.0 # Apache-2.0 iso8601>=0.1.11 # MIT six>=1.10.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD pyOpenSSL>=17.1.0 # Apache-2.0 requests>=2.14.2 # Apache-2.0 PyYAML>=3.12 # MIT yaql>=1.1.3 # Apache 2.0 License osc-lib>=1.8.0 # Apache-2.0 murano-pkg-check>=0.3.0 # Apache-2.0 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 oslo.log>=3.36.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 python-muranoclient-1.3.0/.zuul.yaml0000664000175000017500000000211313523272253017516 0ustar zuulzuul00000000000000- project: templates: - openstack-cover-jobs - openstack-lower-constraints-jobs - openstack-python-jobs - openstack-python3-train-jobs - check-requirements - release-notes-jobs-python3 - publish-openstack-docs-pti - openstackclient-plugin-jobs check: jobs: - muranoclient-functional-test-mysql-backend gate: jobs: - muranoclient-functional-test-mysql-backend - job: name: muranoclient-functional-test-mysql-backend parent: legacy-dsvm-base run: playbooks/legacy/muranoclient-functional-test-mysql-backend/run.yaml post-run: playbooks/legacy/muranoclient-functional-test-mysql-backend/post.yaml timeout: 4200 irrelevant-files: - ^(test-|)requirements.txt$ - ^setup.cfg$ - ^doc/.*$ - ^.*\.rst$ - ^releasenotes/.*$ - ^muranoclient/tests/.*$ required-projects: - openstack/devstack-gate - openstack/heat - openstack/murano - openstack/murano-dashboard - openstack/python-heatclient - openstack/python-muranoclient python-muranoclient-1.3.0/test-requirements.txt0000664000175000017500000000104213523272253022016 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 fixtures>=3.0.0 # Apache-2.0/BSD mock>=2.0.0 # BSD requests-mock>=1.1.0 # Apache-2.0 tempest>=17.1.0 # Apache-2.0 stestr>=2.0.0 # Apache-2.0 testscenarios>=0.4 # Apache-2.0/BSD testtools>=2.2.0 # MIT oslotest>=3.2.0 # Apache-2.0 os-testr>=1.0.0 # Apache-2.0 python-muranoclient-1.3.0/PKG-INFO0000664000175000017500000000733113523272337016664 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: python-muranoclient Version: 1.3.0 Summary: python-muranoclient Home-page: https://docs.openstack.org/python-muranoclient/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: Apache License, Version 2.0 Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-muranoclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on Murano ====== .. image:: https://img.shields.io/pypi/v/python-muranoclient.svg :target: https://pypi.org/project/python-muranoclient/ :alt: Latest Version Murano Project introduces an application catalog, which allows application developers and cloud administrators to publish various cloud-ready applications in a browsable categorised catalog, which may be used by the cloud users (including the inexperienced ones) to pick-up the needed applications and services and composes the reliable environments out of them in a "push-the-button" manner. * `PyPi`_ - package installation * `Launchpad project`_ - release management * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ * `Specs`_ * `How to Contribute`_ .. _PyPi: https://pypi.org/project/python-muranoclient .. _Launchpad project: https://launchpad.net/python-muranoclient .. _Blueprints: https://blueprints.launchpad.net/python-muranoclient .. _Bugs: https://bugs.launchpad.net/python-muranoclient .. _Source: https://opendev.org/openstack/python-muranoclient .. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html .. _Specs: https://specs.openstack.org/openstack/murano-specs/ .. _Release Notes: https://docs.openstack.org/releasenotes/python-muranoclient Python Muranoclient ------------------- python-muranoclient is a client library for Murano built on the Murano API. It provides a Python API (the ``muranoclient`` module) and a command-line tool (``murano``). Project Resources ----------------- Project status, bugs, and blueprints are tracked on Launchpad: * Client bug tracker * https://launchpad.net/python-muranoclient * Murano bug tracker * https://launchpad.net/murano Developer documentation can be found here: https://docs.openstack.org/murano/latest/ Additional resources are linked from the project wiki page: https://wiki.openstack.org/wiki/Murano License ------- Apache License Version 2.0 http://www.apache.org/licenses/LICENSE-2.0 Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Console Classifier: Environment :: OpenStack Classifier: Intended Audience :: Developers 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.6 Classifier: Programming Language :: Python :: 3.7 python-muranoclient-1.3.0/.stestr.conf0000664000175000017500000000011313523272253020024 0ustar zuulzuul00000000000000[DEFAULT] test_path=${OS_TEST_PATH:-./muranoclient/tests/unit} top_dir=./ python-muranoclient-1.3.0/LICENSE0000664000175000017500000002363713523272253016600 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-muranoclient-1.3.0/playbooks/0000775000175000017500000000000013523272337017566 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/playbooks/legacy/0000775000175000017500000000000013523272337021032 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/playbooks/legacy/muranoclient-functional-test-mysql-backend/0000775000175000017500000000000013523272337031377 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/playbooks/legacy/muranoclient-functional-test-mysql-backend/run.yaml0000664000175000017500000000501213523272253033062 0ustar zuulzuul00000000000000- hosts: all name: Autoconverted job legacy-muranoclient-dsvm-functional-mysql-backend from old job gate-muranoclient-dsvm-functional-mysql-backend-ubuntu-xenial tasks: - name: Ensure legacy workspace directory file: path: '{{ ansible_user_dir }}/workspace' state: directory - shell: cmd: | set -e set -x cat > clonemap.yaml << EOF clonemap: - name: openstack/devstack-gate dest: devstack-gate EOF /usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \ https://opendev.org \ openstack/devstack-gate executable: /bin/bash chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' - shell: cmd: | set -e set -x export PYTHONUNBUFFERED=true export DEVSTACK_GATE_NEUTRON=1 export DEVSTACK_PROJECT_FROM_GIT=python-muranoclient export BRANCH_OVERRIDE=default export ENABLED_SERVICES=tempest export PROJECTS="openstack/heat $PROJECTS" export PROJECTS="openstack/python-heatclient $PROJECTS" export PROJECTS="openstack/murano $PROJECTS" export PROJECTS="openstack/murano-dashboard $PROJECTS" export KEEP_LOCALRC=1 # Enable murano devstack plugin. Provided repo should be cloned by zuul before devstack run # and below provided link should not be used. export DEVSTACK_LOCAL_CONFIG="enable_plugin heat https://opendev.org/openstack/heat" export DEVSTACK_LOCAL_CONFIG+=$'\n'"enable_plugin murano https://opendev.org/openstack/murano" if [ "mysql-backend" = "glare-backend" ]; then export DEVSTACK_LOCAL_CONFIG+=$'\n'"enable_service g-glare" export DEVSTACK_LOCAL_CONFIG+=$'\n'"MURANO_USE_GLARE=True" fi if [ "$BRANCH_OVERRIDE" != "default" ] ; then export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE fi function post_test_hook { # Configure and run functional tests /opt/stack/new/python-muranoclient/muranoclient/tests/functional/hooks/post_test_hook.sh } export -f post_test_hook cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh executable: /bin/bash chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' python-muranoclient-1.3.0/playbooks/legacy/muranoclient-functional-test-mysql-backend/post.yaml0000664000175000017500000000455113523272253033252 0ustar zuulzuul00000000000000- hosts: primary tasks: - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=**/*nose_results.html - --include=*/ - --exclude=* - --prune-empty-dirs - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=**/*testr_results.html.gz - --include=*/ - --exclude=* - --prune-empty-dirs - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=/.testrepository/tmp* - --include=*/ - --exclude=* - --prune-empty-dirs - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=**/*testrepository.subunit.gz - --include=*/ - --exclude=* - --prune-empty-dirs - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}/tox' mode: pull copy_links: true verify_host: true rsync_opts: - --include=/.tox/*/log/* - --include=*/ - --exclude=* - --prune-empty-dirs - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=/logs/** - --include=*/ - --exclude=* - --prune-empty-dirs python-muranoclient-1.3.0/README.rst0000664000175000017500000000433213523272253017251 0ustar zuulzuul00000000000000======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-muranoclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on Murano ====== .. image:: https://img.shields.io/pypi/v/python-muranoclient.svg :target: https://pypi.org/project/python-muranoclient/ :alt: Latest Version Murano Project introduces an application catalog, which allows application developers and cloud administrators to publish various cloud-ready applications in a browsable categorised catalog, which may be used by the cloud users (including the inexperienced ones) to pick-up the needed applications and services and composes the reliable environments out of them in a "push-the-button" manner. * `PyPi`_ - package installation * `Launchpad project`_ - release management * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ * `Specs`_ * `How to Contribute`_ .. _PyPi: https://pypi.org/project/python-muranoclient .. _Launchpad project: https://launchpad.net/python-muranoclient .. _Blueprints: https://blueprints.launchpad.net/python-muranoclient .. _Bugs: https://bugs.launchpad.net/python-muranoclient .. _Source: https://opendev.org/openstack/python-muranoclient .. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html .. _Specs: https://specs.openstack.org/openstack/murano-specs/ .. _Release Notes: https://docs.openstack.org/releasenotes/python-muranoclient Python Muranoclient ------------------- python-muranoclient is a client library for Murano built on the Murano API. It provides a Python API (the ``muranoclient`` module) and a command-line tool (``murano``). Project Resources ----------------- Project status, bugs, and blueprints are tracked on Launchpad: * Client bug tracker * https://launchpad.net/python-muranoclient * Murano bug tracker * https://launchpad.net/murano Developer documentation can be found here: https://docs.openstack.org/murano/latest/ Additional resources are linked from the project wiki page: https://wiki.openstack.org/wiki/Murano License ------- Apache License Version 2.0 http://www.apache.org/licenses/LICENSE-2.0 python-muranoclient-1.3.0/HACKING.rst0000664000175000017500000000017013523272253017354 0ustar zuulzuul00000000000000Style Commandments ================== Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ python-muranoclient-1.3.0/doc/0000775000175000017500000000000013523272337016330 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/doc/source/0000775000175000017500000000000013523272337017630 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/doc/source/cli/0000775000175000017500000000000013523272337020377 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/doc/source/cli/index.rst0000664000175000017500000000263613523272253022244 0ustar zuulzuul00000000000000================= Murano API Client ================= In order to use the python api directly, you must first obtain an auth token and identify which endpoint you wish to speak to. Once you have done so, you can use the API like so:: >>> from muranoclient import Client >>> murano = Client('1', endpoint=MURANO_URL, token=OS_AUTH_TOKEN) ... Command-line Tool ================= In order to use the CLI, you must provide your OpenStack username, password, tenant, and auth endpoint. Use the corresponding configuration options (:option:``--os-username``, :option:``--os-password``, :option:``--os-tenant-id``, and :option:``--os-auth-url``) or set them in environment variables:: export OS_USERNAME=user export OS_PASSWORD=pass export OS_TENANT_ID=b363706f891f48019483f8bd6503c54b export OS_AUTH_URL=http://auth.example.com:5000/v2.0 The command line tool will attempt to reauthenticate using your provided credentials for every request. You can override this behavior by manually supplying an auth token using :option:``--os-image-url`` and :option:``--os-auth-token``. You can alternatively set these environment variables:: export MURANO_URL=http://murano.example.org:8082/ export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155 Once you've configured your authentication parameters, you can run :command:`murano help` to see a complete listing of available commands. .. toctree:: murano python-muranoclient-1.3.0/doc/source/cli/murano.rst0000664000175000017500000006745513523272253022450 0ustar zuulzuul00000000000000======================================================== Application Catalog service (murano) command-line client ======================================================== The murano client is the command-line interface (CLI) for the Application Catalog service (murano) API and its extensions. This chapter documents :command:`murano` version ``0.13.0``. For help on a specific :command:`murano` command, enter: .. code-block:: console $ murano help COMMAND .. _murano_command_usage: murano usage ~~~~~~~~~~~~ .. code-block:: console usage: murano [--version] [-d] [-v] [--cert-file OS_CERT] [--key-file OS_KEY] [--ca-file OS_CACERT] [--api-timeout API_TIMEOUT] [--os-tenant-id OS_TENANT_ID] [--os-tenant-name OS_TENANT_NAME] [--os-region-name OS_REGION_NAME] [--os-auth-token OS_AUTH_TOKEN] [--os-no-client-auth] [--murano-url MURANO_URL] [--glance-url GLANCE_URL] [--glare-url GLARE_URL] [--murano-api-version MURANO_API_VERSION] [--os-service-type OS_SERVICE_TYPE] [--os-endpoint-type OS_ENDPOINT_TYPE] [--include-password] [--murano-repo-url MURANO_REPO_URL] [--murano-packages-service {murano,glance,glare}] [--insecure] [--os-cacert ] [--os-cert ] [--os-key ] [--timeout ] [--os-auth-url OS_AUTH_URL] [--os-domain-id OS_DOMAIN_ID] [--os-domain-name OS_DOMAIN_NAME] [--os-project-id OS_PROJECT_ID] [--os-project-name OS_PROJECT_NAME] [--os-project-domain-id OS_PROJECT_DOMAIN_ID] [--os-project-domain-name OS_PROJECT_DOMAIN_NAME] [--os-trust-id OS_TRUST_ID] [--os-user-id OS_USER_ID] [--os-username OS_USERNAME] [--os-user-domain-id OS_USER_DOMAIN_ID] [--os-user-domain-name OS_USER_DOMAIN_NAME] [--os-password OS_PASSWORD] ... **Subcommands:** ``app-show`` List applications, added to specified environment. ``bundle-import`` Import a bundle. ``bundle-save`` Save a bundle. ``category-create`` Create a category. ``category-delete`` Delete a category. ``category-list`` List all available categories. ``category-show`` Display category details. ``class-schema`` Display class schema ``deployment-list`` List deployments for an environment or multiple environments. ``env-template-add-app`` Add application to the environment template. ``env-template-clone`` Create a new template, cloned from template. ``env-template-create`` Create an environment template. ``env-template-create-env`` Create a new environment from template. ``env-template-del-app`` Delete application from the environment template. ``env-template-delete`` Delete an environment template. ``env-template-list`` List the environments templates. ``env-template-show`` Display environment template details. ``env-template-update`` Update an environment template. ``environment-action-call`` Call action \`ACTION\` in environment \`ID\`. ``environment-action-get-result`` Get result of \`TASK\` in environment \`ID\`. ``environment-apps-edit`` Edit environment's object model. ``environment-create`` Create an environment. ``environment-delete`` Delete an environment. ``environment-deploy`` Start deployment of a murano environment session. ``environment-list`` List the environments. ``environment-model-edit`` Edit an environment's object model. ``environment-model-show`` Display an environment's object model. ``environment-rename`` Rename an environment. ``environment-session-create`` Creates a new configuration session for environment ID. ``environment-show`` Display environment details. ``package-create`` Create an application package. ``package-delete`` Delete a package. ``package-download`` Download a package to a filename or stdout. ``package-import`` Import a package. ``package-list`` List available packages. ``package-save`` Save a package. ``package-show`` Display details for a package. ``package-update`` Update an existing package. ``static-action-call`` Call static method \`METHOD\` of the class \`CLASS\` with \`ARGUMENTS\`. ``bash-completion`` Prints all of the commands and options to stdout. ``help`` Display help about this program or one of its subcommands. .. _murano_command_options: murano optional arguments ~~~~~~~~~~~~~~~~~~~~~~~~~ ``--version`` Show program's version number and exit. ``-d, --debug`` Defaults to ``env[MURANOCLIENT_DEBUG]``. ``-v, --verbose`` Print more verbose output. ``--cert-file OS_CERT`` **DEPRECATED!** Use --os-cert. ``--key-file OS_KEY`` **DEPRECATED!** Use --os-key. ``--ca-file OS_CACERT`` **DEPRECATED!** Use --os-cacert. ``--api-timeout API_TIMEOUT`` Number of seconds to wait for an API response, defaults to system socket timeout. ``--os-tenant-id OS_TENANT_ID`` Defaults to ``env[OS_TENANT_ID]``. ``--os-tenant-name OS_TENANT_NAME`` Defaults to ``env[OS_TENANT_NAME]``. ``--os-region-name OS_REGION_NAME`` Defaults to ``env[OS_REGION_NAME]``. ``--os-auth-token OS_AUTH_TOKEN`` Defaults to ``env[OS_AUTH_TOKEN]``. ``--os-no-client-auth`` Do not contact keystone for a token. Defaults to ``env[OS_NO_CLIENT_AUTH]``. ``--murano-url MURANO_URL`` Defaults to ``env[MURANO_URL]``. ``--glance-url GLANCE_URL`` Defaults to ``env[GLANCE_URL]``. ``--glare-url GLARE_URL`` Defaults to ``env[GLARE_URL]``. ``--murano-api-version MURANO_API_VERSION`` Defaults to ``env[MURANO_API_VERSION]`` or 1. ``--os-service-type OS_SERVICE_TYPE`` Defaults to ``env[OS_SERVICE_TYPE]``. ``--os-endpoint-type OS_ENDPOINT_TYPE`` Defaults to ``env[OS_ENDPOINT_TYPE]``. ``--include-password`` Send os-username and os-password to murano. ``--murano-repo-url MURANO_REPO_URL`` Defaults to ``env[MURANO_REPO_URL]`` or http://apps.openstack.org/api/v1/murano_repo/liberty/ ``--murano-packages-service {murano,glance,glare}`` Specifies if murano-api ("murano") or Glance Artifact Repository ("glare") should be used to store murano packages. Defaults to ``env[MURANO_PACKAGES_SERVICE]`` or to "murano" ``--insecure`` Explicitly allow client to perform "insecure" TLS (https) requests. The server's certificate will not be verified against any certificate authorities. This option should be used with caution. ``--os-cacert `` Specify a CA bundle file to use in verifying a TLS (https) server certificate. Defaults to ``env[OS_CACERT]``. ``--os-cert `` Defaults to ``env[OS_CERT]``. ``--os-key `` Defaults to ``env[OS_KEY]``. ``--timeout `` Set request timeout (in seconds). ``--os-auth-url OS_AUTH_URL`` Authentication URL ``--os-domain-id OS_DOMAIN_ID`` Domain ID to scope to ``--os-domain-name OS_DOMAIN_NAME`` Domain name to scope to ``--os-project-id OS_PROJECT_ID`` Project ID to scope to ``--os-project-name OS_PROJECT_NAME`` Project name to scope to ``--os-project-domain-id OS_PROJECT_DOMAIN_ID`` Domain ID containing project ``--os-project-domain-name OS_PROJECT_DOMAIN_NAME`` Domain name containing project ``--os-trust-id OS_TRUST_ID`` Trust ID ``--os-user-id OS_USER_ID`` User ID ``--os-username OS_USERNAME, --os-user-name OS_USERNAME, --os-user_name OS_USERNAME`` Username ``--os-user-domain-id OS_USER_DOMAIN_ID`` User's domain id ``--os-user-domain-name OS_USER_DOMAIN_NAME`` User's domain name ``--os-password OS_PASSWORD`` User's password .. _murano_app-show: murano app-show --------------- .. code-block:: console usage: murano app-show [-p ] List applications, added to specified environment. **Positional arguments:** ```` Environment ID to show applications from. **Optional arguments:** ``-p , --path `` Level of detalization to show. Leave empty to browse all applications in the environment. .. _murano_bundle-import: murano bundle-import -------------------- .. code-block:: console usage: murano bundle-import [--is-public] [--exists-action {a,s,u}] [ ...] Import a bundle. \`FILE\` can be either a path to a zip file, URL, or name from repo. If \`FILE\` is a local file, treat names of packages in a bundle as file names, relative to location of the bundle file. Requirements are first searched in the same directory. **Positional arguments:** ```` Bundle URL, bundle name, or path to the bundle file. **Optional arguments:** ``--is-public`` Make packages available to users from other tenants. ``--exists-action {a,s,u}`` Default action when a package already exists. .. _murano_bundle-save: murano bundle-save ------------------ .. code-block:: console usage: murano bundle-save [-p ] [--no-images] Save a bundle. This will download a bundle of packages with all dependencies to specified path. If path doesn't exist it will be created. **Positional arguments:** ```` Bundle URL, bundle name, or path to the bundle file. **Optional arguments:** ``-p , --path `` Path to the directory to store packages. If not set will use current directory. ``--no-images`` If set will skip images downloading. .. _murano_category-create: murano category-create ---------------------- .. code-block:: console usage: murano category-create Create a category. **Positional arguments:** ```` Category name. .. _murano_category-delete: murano category-delete ---------------------- .. code-block:: console usage: murano category-delete [ ...] Delete a category. **Positional arguments:** ```` ID of a category(ies) to delete. .. _murano_category-list: murano category-list -------------------- .. code-block:: console usage: murano category-list List all available categories. .. _murano_category-show: murano category-show -------------------- .. code-block:: console usage: murano category-show Display category details. **Positional arguments:** ```` ID of a category(s) to show. .. _murano_class-schema: murano class-schema ------------------- .. code-block:: console usage: murano class-schema [--package-name PACKAGE_NAME] [--class-version CLASS_VERSION] [ [ ...]] Display class schema **Positional arguments:** ```` Class FQN ```` Method name **Optional arguments:** ``--package-name PACKAGE_NAME`` FQN of the package where the class is located ``--class-version CLASS_VERSION`` Class version or version range (version spec) .. _murano_deployment-list: murano deployment-list ---------------------- .. code-block:: console usage: murano deployment-list [--all-environments] [] List deployments for an environment or multiple environments. **Positional arguments:** ```` Environment ID for which to list deployments. **Optional arguments:** ``--all-environments`` Lists all deployments for all environments in user's tenant. .. _murano_env-template-add-app: murano env-template-add-app --------------------------- .. code-block:: console usage: murano env-template-add-app Add application to the environment template. **Positional arguments:** ```` Environment template ID. ```` Path to the template. .. _murano_env-template-clone: murano env-template-clone ------------------------- .. code-block:: console usage: murano env-template-clone Create a new template, cloned from template. **Positional arguments:** ```` Environment template ID. ```` New environment template name. .. _murano_env-template-create: murano env-template-create -------------------------- .. code-block:: console usage: murano env-template-create [--is-public] Create an environment template. **Positional arguments:** ```` Environment template name. **Optional arguments:** ``--is-public`` Make the template available for users from other tenants. .. _murano_env-template-create-env: murano env-template-create-env ------------------------------ .. code-block:: console usage: murano env-template-create-env [--region ] Create a new environment from template. **Positional arguments:** ```` Environment template ID. ```` New environment name. **Optional arguments:** ``--region `` Name of the target OpenStack region. .. _murano_env-template-del-app: murano env-template-del-app --------------------------- .. code-block:: console usage: murano env-template-del-app Delete application from the environment template. **Positional arguments:** ```` Environment template ID. ```` Application ID. .. _murano_env-template-delete: murano env-template-delete -------------------------- .. code-block:: console usage: murano env-template-delete [ ...] Delete an environment template. **Positional arguments:** ```` ID of environment(s) template to delete. .. _murano_env-template-list: murano env-template-list ------------------------ .. code-block:: console usage: murano env-template-list List the environments templates. .. _murano_env-template-show: murano env-template-show ------------------------ .. code-block:: console usage: murano env-template-show Display environment template details. **Positional arguments:** ```` Environment template ID. .. _murano_env-template-update: murano env-template-update -------------------------- .. code-block:: console usage: murano env-template-update Update an environment template. **Positional arguments:** ```` Environment template ID. ```` Environment template name. .. _murano_environment-action-call: murano environment-action-call ------------------------------ .. code-block:: console usage: murano environment-action-call --action-id [--arguments [ [ ...]]] id Call action \`ACTION\` in environment \`ID\`. Returns id of an asynchronous task, that executes the action. Actions can only be called on a \`deployed\` environment. To view actions available in a given environment use \`environment-show\` command. **Positional arguments:** ``id`` ID of Environment to call action against. **Optional arguments:** ``--action-id `` ID of action to run. ``--arguments [ [ ...]]`` Action arguments. .. _murano_environment-action-get-result: murano environment-action-get-result ------------------------------------ .. code-block:: console usage: murano environment-action-get-result --task-id Get result of \`TASK\` in environment \`ID\`. **Positional arguments:** ```` ID of Environment where task is being executed. **Optional arguments:** ``--task-id `` ID of action to run. .. _murano_environment-apps-edit: murano environment-apps-edit ---------------------------- .. code-block:: console usage: murano environment-apps-edit --session-id [FILE] Edit environment's object model. \`FILE\` is path to a file, that contains jsonpatch, that describes changes to be made to environment's object-model. [ { "op": "add", "path": "/-", "value": { ... your-app object model here ... } }, { "op": "replace", "path": "/0/?/name", "value": "new_name" }, ] NOTE: Values '===id1===', '===id2===', etc. in the resulting object-model will be substituted with uuids. For more info on jsonpatch see RFC 6902 **Positional arguments:** ```` ID of Environment to edit. ``FILE`` File to read jsonpatch from (defaults to stdin). **Optional arguments:** ``--session-id `` Id of a config session. .. _murano_environment-create: murano environment-create ------------------------- .. code-block:: console usage: murano environment-create [--join-net-id ] [--join-subnet-id ] [--region ] Create an environment. **Positional arguments:** ```` Environment name. **Optional arguments:** ``--join-net-id `` Network id to join. ``--join-subnet-id `` Subnetwork id to join. ``--region `` Name of the target OpenStack region. .. _murano_environment-delete: murano environment-delete ------------------------- .. code-block:: console usage: murano environment-delete [--abandon] [ ...] Delete an environment. **Positional arguments:** ```` Id or name of environment(s) to delete. **Optional arguments:** ``--abandon`` If set will abandon environment without deleting any of its resources. .. _murano_environment-deploy: murano environment-deploy ------------------------- .. code-block:: console usage: murano environment-deploy --session-id Start deployment of a murano environment session. **Positional arguments:** ```` ID of Environment to deploy. **Optional arguments:** ``--session-id `` ID of configuration session to deploy. .. _murano_environment-list: murano environment-list ----------------------- .. code-block:: console usage: murano environment-list [--all-tenants] [--tenant ] List the environments. **Optional arguments:** ``--all-tenants`` Allows to list environments from all tenants (admin only). ``--tenant `` Allows to list environments for a given tenant (admin only). .. _murano_environment-model-edit: murano environment-model-edit ----------------------------- .. code-block:: console usage: murano environment-model-edit --session-id [] Edit an environment's object model. **Positional arguments:** ```` ID of Environment to edit. ```` File to read JSON-patch from (defaults to stdin). **Optional arguments:** ``--session-id `` Id of a config session. .. _murano_environment-model-show: murano environment-model-show ----------------------------- .. code-block:: console usage: murano environment-model-show [--path ] [--session-id ] Display an environment's object model. **Positional arguments:** ```` ID of Environment to show. **Optional arguments:** ``--path `` Path to Environment model section. Defaults to '/'. ``--session-id `` Id of a config session. .. _murano_environment-rename: murano environment-rename ------------------------- .. code-block:: console usage: murano environment-rename Rename an environment. **Positional arguments:** ```` Environment ID or name. ```` A name to which the environment will be renamed. .. _murano_environment-session-create: murano environment-session-create --------------------------------- .. code-block:: console usage: murano environment-session-create Creates a new configuration session for environment ID. **Positional arguments:** ```` ID of Environment to add session to. .. _murano_environment-show: murano environment-show ----------------------- .. code-block:: console usage: murano environment-show [--session-id ] [--only-apps] Display environment details. **Positional arguments:** ```` Environment ID or name. **Optional arguments:** ``--session-id `` Id of a config session. ``--only-apps`` Only print apps of the environment (useful for automation). .. _murano_package-create: murano package-create --------------------- .. code-block:: console usage: murano package-create [-t ] [-c ] [-r ] [-n ] [-f ] [-a ] [--tags [ [ ...]]] [-d ] [-o ] [-u ] [--type TYPE] [-l ] Create an application package. **Optional arguments:** ``-t , --template `` Path to the Heat template to import as an Application Definition. ``-c , --classes-dir `` Path to the directory containing application classes. ``-r , --resources-dir `` Path to the directory containing application resources. ``-n , --name `` Display name of the Application in Catalog. ``-f , --full-name `` Fully-qualified name of the Application in Catalog. ``-a , --author `` Name of the publisher. ``--tags [ [ ...]]`` A list of keywords connected to the application. ``-d , --description `` Detailed description for the Application in Catalog. ``-o , --output `` The name of the output file archive to save locally. ``-u , --ui `` Dynamic UI form definition. ``--type TYPE`` Package type. Possible values: Application or Library. ``-l , --logo `` Path to the package logo. .. _murano_package-delete: murano package-delete --------------------- .. code-block:: console usage: murano package-delete [ ...] Delete a package. **Positional arguments:** ```` Package ID to delete. .. _murano_package-download: murano package-download ----------------------- .. code-block:: console usage: murano package-download [file] Download a package to a filename or stdout. **Positional arguments:** ```` Package ID to download. ``file`` Filename to save package to. If it is not specified and there is no stdout redirection the package won't be saved. .. _murano_package-import: murano package-import --------------------- .. code-block:: console usage: murano package-import [-c [ [ ...]]] [--is-public] [--package-version PACKAGE_VERSION] [--exists-action {a,s,u}] [--dep-exists-action {a,s,u}] [ ...] Import a package. \`FILE\` can be either a path to a zip file, url or a FQPN. You can use \`--\` to separate \`FILE\`s from other arguments. Categories have to be separated with a space and have to be already present in murano. **Positional arguments:** ```` URL of the murano zip package, FQPN, path to zip package or path to directory with package. **Optional arguments:** ``-c [ [ ...]], --categories [ [ ...]]`` Category list to attach. ``--is-public`` Make the package available for users from other tenants. ``--package-version PACKAGE_VERSION`` Version of the package to use from repository (ignored when importing with multiple packages). ``--exists-action {a,s,u}`` Default action when a package already exists: (s)kip, (u)pdate, (a)bort. ``--dep-exists-action {a,s,u}`` Default action when a dependency package already exists: (s)kip, (u)pdate, (a)bort. .. _murano_package-list: murano package-list ------------------- .. code-block:: console usage: murano package-list [--limit LIMIT] [--marker MARKER] [--include-disabled] [--owned] [--search ] [--name ] [--fqn ] [--type ] [--category ] [--class_name ] [--tag ] List available packages. **Optional arguments:** ``--limit LIMIT`` Show limited number of packages ``--marker MARKER`` Show packages starting from package with id excluding it ``--include-disabled`` ``--owned`` ``--search `` Show packages, that match search keys fuzzily ``--name `` Show packages, whose name match parameter exactly ``--fqn `` Show packages, whose fully qualified name match parameter exactly ``--type `` Show packages, whose type match parameter exactly ``--category `` Show packages, whose categories include parameter ``--class_name `` Show packages, whose class name match parameter exactly ``--tag `` Show packages, whose tags include parameter .. _murano_package-save: murano package-save ------------------- .. code-block:: console usage: murano package-save [-p ] [--package-version PACKAGE_VERSION] [--no-images] [ ...] Save a package. This will download package(s) with all dependencies to specified path. If path doesn't exist it will be created. **Positional arguments:** ```` Package URL or name. **Optional arguments:** ``-p , --path `` Path to the directory to store package. If not set will use current directory. ``--package-version PACKAGE_VERSION`` Version of the package to use from repository (ignored when saving with multiple packages). ``--no-images`` If set will skip images downloading. .. _murano_package-show: murano package-show ------------------- .. code-block:: console usage: murano package-show Display details for a package. **Positional arguments:** ```` Package ID to show. .. _murano_package-update: murano package-update --------------------- .. code-block:: console usage: murano package-update [--is-public {true|false}] [--enabled {true|false}] [--name NAME] [--description DESCRIPTION] [--tags [ [ ...]]] Update an existing package. **Positional arguments:** ```` Package ID to update. **Optional arguments:** ``--is-public {true|false}`` Make package available to users from other tenants. ``--enabled {true|false}`` Make package active and available for deployments. ``--name NAME`` New name for the package. ``--description DESCRIPTION`` New package description. ``--tags [ [ ...]]`` A list of keywords connected to the application. .. _murano_static-action-call: murano static-action-call ------------------------- .. code-block:: console usage: murano static-action-call [--arguments [ [ ...]]] [--package-name ] [--class-version CLASS_VERSION] Call static method \`METHOD\` of the class \`CLASS\` with \`ARGUMENTS\`. Returns the result of the method execution. \`PACKAGE\` and \`CLASS_VERSION\` can be specified optionally to find class in a particular package and to look for the specific version of a class respectively. **Positional arguments:** ```` FQN of the class with static method ```` Static method to run **Optional arguments:** ``--arguments [ [ ...]]`` Method arguments. No arguments by default ``--package-name `` Optional FQN of the package to look for the class in ``--class-version CLASS_VERSION`` Optional version of the class, otherwise version =0 is used python-muranoclient-1.3.0/doc/source/index.rst0000664000175000017500000000052613523272253021471 0ustar zuulzuul00000000000000================================= python-muranoclient documentation ================================= This is a client for the OpenStack Application Catalog API. There's a Python API (the :mod:`muranoclient` module) and a :doc:`command-line script ` (installed as :program:`murano`). .. toctree:: :maxdepth: 2 cli/index python-muranoclient-1.3.0/doc/source/conf.py0000664000175000017500000000534713523272253021135 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 os # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'openstackdocstheme',] # autodoc generation is a bit aggressive and a nuisance when doing heavy # text edit cycles. # execute "export SPHINX_DEBUG=1" in your terminal to disable # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. copyright = u'OpenStack Foundation' exclude_trees = ['api'] # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'openstackdocs' # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = ['_theme'] #html_theme_path = [openstackdocstheme.get_html_theme_path()] # openstackdocstheme options repository_name = 'openstack/python-muranoclient' bug_project = 'python-muranoclient' bug_tag = '' # Output file base name for HTML help builder. htmlhelp_basename = 'python-muranoclientdoc' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ( 'index', 'python-muranoclient.tex', u'python-muranoclient Documentation', u'OpenStack Foundation', 'manual' ), ] # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'http://docs.python.org/': None} python-muranoclient-1.3.0/doc/requirements.txt0000664000175000017500000000062613523272253021615 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. openstackdocstheme>=1.20.0 # Apache-2.0 sphinx!=1.6.6,!=1.6.7,>=1.6.2,<2.0.0;python_version=='2.7' # BSD sphinx!=1.6.6,!=1.6.7,!=2.1.0,>=1.6.2;python_version>='3.4' # BSD reno>=2.5.0 # Apache-2.0 python-muranoclient-1.3.0/setup-centos.sh0000664000175000017500000001237613523272253020556 0ustar zuulzuul00000000000000#!/bin/sh # Copyright (c) 2013 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. # # CentOS script. LOGLVL=1 SERVICE_CONTENT_DIRECTORY=`cd $(dirname "$0") && pwd` PREREQ_PKGS="wget make git python-pip python-devel" PIPAPPS="pip python-pip pip-python" PIPCMD="" SERVICE_SRV_NAME="python-muranoclient" GIT_CLONE_DIR=`echo $SERVICE_CONTENT_DIRECTORY | sed -e "s/$SERVICE_SRV_NAME//"` # Functions # Loger function log() { MSG=$1 if [ $LOGLVL -gt 0 ]; then echo "LOG:> $MSG" fi } # Check or install package in_sys_pkg() { PKG=$1 rpm -q $PKG > /dev/null 2>&1 if [ $? -eq 0 ]; then log "Package \"$PKG\" already installed" else log "Installing \"$PKG\"..." yum install $PKG --assumeyes > /dev/null 2>&1 if [ $? -ne 0 ];then log "installation fails, exiting!!!" exit fi fi } # find pip find_pip() { for cmd in $PIPAPPS do _cmd=$(which $cmd 2>/dev/null) if [ $? -eq 0 ];then break fi done if [ -z $_cmd ];then echo "Can't find \"pip\" in system, please install it first, exiting!" exit 1 else PIPCMD=$_cmd fi } # git clone gitclone() { FROM=$1 CLONEROOT=$2 log "Cloning from \"$FROM\" repo to \"$CLONEROOT\"" cd $CLONEROOT && git clone $FROM > /dev/null 2>&1 if [ $? -ne 0 ];then log "cloning from \"$FROM\" fails, exiting!!!" exit fi } # install inst() { CLONE_FROM_GIT=$1 # Checking packages for PKG in $PREREQ_PKGS do in_sys_pkg $PKG done # Find python pip find_pip # If clone from git set if [ ! -z $CLONE_FROM_GIT ]; then # Preparing clone root directory if [ ! -d $GIT_CLONE_DIR ];then log "Creating $GIT_CLONE_DIR directory..." mkdir -p $GIT_CLONE_DIR if [ $? -ne 0 ];then log "Can't create $GIT_CLONE_DIR, exiting!!!" exit fi fi # Cloning from GIT GIT_WEBPATH_PRFX="https://opendev.org/openstack/" gitclone "$GIT_WEBPATH_PRFX$SERVICE_SRV_NAME.git" $GIT_CLONE_DIR # End clone from git section fi # Setupping... log "Running setup.py" MRN_CND_SPY=$SERVICE_CONTENT_DIRECTORY/setup.py if [ -e $MRN_CND_SPY ]; then chmod +x $MRN_CND_SPY log "$MRN_CND_SPY output:_____________________________________________________________" ## Setup through pip # Creating tarball rm -rf $SERVICE_CONTENT_DIRECTORY/*.egg-info cd $SERVICE_CONTENT_DIRECTORY && python $MRN_CND_SPY egg_info if [ $? -ne 0 ];then log "\"$MRN_CND_SPY\" egg info creation FAILS, exiting!!!" exit 1 fi rm -rf $SERVICE_CONTENT_DIRECTORY/dist/* cd $SERVICE_CONTENT_DIRECTORY && python $MRN_CND_SPY sdist if [ $? -ne 0 ];then log "\"$MRN_CND_SPY\" tarball creation FAILS, exiting!!!" exit 1 fi # Running tarball install TRBL_FILE=$(basename `ls $SERVICE_CONTENT_DIRECTORY/dist/*.tar.gz`) $PIPCMD install $SERVICE_CONTENT_DIRECTORY/dist/$TRBL_FILE if [ $? -ne 0 ];then log "$PIPCMD install \"$TRBL_FILE\" FAILS, exiting!!!" exit 1 fi else log "$MRN_CND_SPY not found!" fi } # uninstall uninst() { # Uninstall trough pip find_pip # looking up for python package installed PYPKG="muranoclient" _pkg=$($PIPCMD freeze | grep $PYPKG) if [ $? -eq 0 ]; then log "Removing package \"$PYPKG\" with pip" $PIPCMD uninstall $_pkg --yes else log "Python package \"$PYPKG\" not found" fi } # Command line args' COMMAND="$1" case $COMMAND in install ) inst ;; installfromgit ) inst "yes" ;; uninstall ) log "Uninstalling muranoclient \"$SERVICE_SRV_NAME\" from system..." uninst ;; * ) echo -e "Usage: $(basename "$0") command \nCommands:\n\tinstall - Install $SERVICE_SRV_NAME software\n\tuninstall - Uninstall $SERVICE_SRV_NAME software" exit 1 ;; esac python-muranoclient-1.3.0/setup.cfg0000664000175000017500000000611713523272337017411 0ustar zuulzuul00000000000000[metadata] name = python-muranoclient summary = python-muranoclient description-file = README.rst license = Apache License, Version 2.0 author = OpenStack author-email = openstack-discuss@lists.openstack.org home-page = https://docs.openstack.org/python-muranoclient/latest/ classifier = Development Status :: 4 - Beta Environment :: Console Environment :: OpenStack Intended Audience :: Developers 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.6 Programming Language :: Python :: 3.7 [files] packages = muranoclient [entry_points] console_scripts = murano = muranoclient.shell:main openstack.cli.extension = application_catalog = muranoclient.osc.plugin openstack.application_catalog.v1 = environment_list = muranoclient.osc.v1.environment:ListEnvironments environment_show = muranoclient.osc.v1.environment:ShowEnvironment environment_rename = muranoclient.osc.v1.environment:RenameEnvironment environment_session_create = muranoclient.osc.v1.environment:EnvironmentSessionCreate environment_create = muranoclient.osc.v1.environment:EnvironmentCreate environment_delete = muranoclient.osc.v1.environment:EnvironmentDelete environment_deploy = muranoclient.osc.v1.environment:EnvironmentDeploy environment_apps_edit = muranoclient.osc.v1.environment:EnvironmentAppsEdit environment_model_show = muranoclient.osc.v1.environment:EnvironmentModelShow environment_model_edit = muranoclient.osc.v1.environment:EnvironmentModelEdit category_list = muranoclient.osc.v1.category:ListCategories category_show = muranoclient.osc.v1.category:ShowCategory category_create = muranoclient.osc.v1.category:CreateCategory category_delete = muranoclient.osc.v1.category:DeleteCategory deployment_list = muranoclient.osc.v1.deployment:ListDeployment package_create = muranoclient.osc.v1.package:CreatePackage package_list = muranoclient.osc.v1.package:ListPackages package_delete = muranoclient.osc.v1.package:DeletePackage package_import = muranoclient.osc.v1.package:ImportPackage package_show = muranoclient.osc.v1.package:ShowPackage package_update = muranoclient.osc.v1.package:UpdatePackage package_download = muranoclient.osc.v1.package:DownloadPackage bundle_import = muranoclient.osc.v1.package:ImportBundle static-action_call = muranoclient.osc.v1.action:StaticActionCall class-schema = muranoclient.osc.v1.schema:ShowSchema [global] setup-hooks = pbr.hooks.setup_hook [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 [wheel] universal = 1 [compile_catalog] directory = muranoclient/locale domain = muranoclient [update_catalog] domain = muranoclient output_dir = muranoclient/locale input_file = muranoclient/locale/muranoclient.pot [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg output_file = muranoclient/locale/muranoclient.pot python-muranoclient-1.3.0/AUTHORS0000664000175000017500000001200213523272336016625 0ustar zuulzuul0000000000000098k <18552437190@163.com> Aaron-DH Alexander Koryagin Alexander Shlykov Alexander Tivelkov Anastasia Kuznetsova Andreas Jaeger Andrew Pashkin Andy Botting Anh Tran Ankur Rishi Artem Tiumentcev AvnishPal Bertrand Lallau Cao Xuan Hoang Chen Christian Berendt Corey Bryant Dai Dang Van Dmitry Teselkin Dmytro Dovbii Doug Hellmann Ekaterina Chernova Ekaterina Fedorova Ellen Batbouta Felipe Monteiro Feng Shengqin Filip Blaha Flavio Percoco ForestLee Georgiy Okrokvertskhov Georgy Dyuldin Georgy Okrokvertskhov Ghanshyam Mann Hangdong Zhang Henar Muñoz Frutos Hidekazu Nakamura Ian Wienand Igor Yozhikov Ilya Popov Ivan Udovichenko Jacek Tomasiak Janonymous Jeremy Stanley Jesus Perez Jose Phillips Junyuan Leng KATO Tomoyuki Kirill Zaitsev Konstantin Snihyr Lin Yang LiuNanke Longgeek M V P Nitesh MStolyarenko Maria Zlatkova Nam Nguyen Hoai Nguyen Hai Nikolay Mahotkin Nikolay Starodubtsev OlehBaran Oleksii Chuprykov Olivier Lemasle Omar Shykhkerimov OpenStack Release Bot Ravi Shekhar Jethani Rui Chen Ruslan Kamaldinov Ryan Peters Sascha Peilicke Serg Melikyan Serg Melikyan Sergey Melikyan Sergey Murashov Sergey Turivnyi Sergey Vilgelm Stan Lagun Stan Lagun Stanislav Lagun Steve Martinelli Steve Martinelli Steve McLellan Steve McLellan Swapnil Kulkarni (coolsvap) Tang Chen Tatyana Kuterina Tetiana Lashchova Timur Nurlygayanov Timur Sufiev TimurNurlygayanov Tovin Seven Valerii Kovalchuk Victor Ryzhenkin Vu Cong Tuan XiaojueGuan Yosef Hoffman bhagyashris bharaththiruveedula chenaidong1 enthurohini gecong1973 howardlee hparekh huang.zhiping huangsm jacky06 leizhang lidong lingyongxu liyingjun liyingjun ljhuang luqitao malei melissaml pawnesh.kumar pengyuesheng qingszhao ricolin ricolin srushti sslypushenko venkatamahesh visitor wu.chunyang xiangxinyong yuyafei zhangyanxian zhu.rong zhurong python-muranoclient-1.3.0/.coveragerc0000664000175000017500000000014013523272253017674 0ustar zuulzuul00000000000000[run] source = muranoclient omit = .tox/* muranoclient/tests/* [report] ignore_errors = True python-muranoclient-1.3.0/setup.py0000664000175000017500000000200613523272253017270 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-muranoclient-1.3.0/ChangeLog0000664000175000017500000010746313523272336017347 0ustar zuulzuul00000000000000CHANGES ======= 1.3.0 ----- * Bump the openstackdocstheme extension to 1.20 * Blacklist sphinx 2.1.0 (autodoc bug) * Add Python 3 Train unit tests * Update contraints url * Bump the sphinx to 1.6.2 for lower-constraints.txt * Add irrelevant-files for muranoclient-functional-test-mysql-backend job * Fix sphinx requirements * Replace git.openstack.org URLs with opendev.org URLs * OpenDev Migration Patch * Dropping the py35 testing * Replace openstack.org git:// URLs with https:// * Update master for stable/stein 1.2.0 ----- * add python 3.7 unit test job * Change openstack-dev to openstack-discuss * Add Python 3.6 classifier to setup.cfg * Update min tox version to 2.0 * Use standard cover tox env * Use templates for cover and lower-constraints * add python 3.6 unit test job * switch documentation job to new PTI * import zuul job settings from project-config * Imported Translations from Zanata * Remove the export pre\_test\_hook from murano repo * Update py27-ocata to py27-queens * Move legacy-muranoclient-dsvm-functional-mysql-backend job to muranoclient * Update reno for stable/rocky 1.1.1 ----- * Switch to stestr * Add release note link in README 1.1.0 ----- * Remove PyPI downloads * fix tox python3 overrides * Trivial: update url to new url * Ignore default values for deprecated security parameters * Trivial: Update pypi url to new url * Follow the new PTI for document build * Fix incompatible requirement * Updated from global requirements * add lower-constraints job * Updated from global requirements * Updated from global requirements * Imported Translations from Zanata * Add os-testr to test-requirements.txt * Updated from global requirements * Imported Translations from Zanata * Updated from global requirements * Update reno for stable/queens 1.0.1 ----- * Updated from global requirements * Updated from global requirements 1.0.0 ----- * Updated from global requirements * Avoid tox\_install.sh for constraints support * Remove setting of version/release from releasenotes * Updated from global requirements * Updated from global requirements * Imported Translations from Zanata * Use generic user for both zuul v2 and v3 * update env template output * Updated from global requirements * Fix to use "." to source script files * Updated from global requirements * Update links in README * Add package download to openstack CLI * Add package update to openstack CLI * Add package show to openstack CLI * Updated from global requirements * Updated from global requirements * Add bundle import to openstack CLI * Skip two test due to apps.openstack.org is retired * Updated from global requirements * Update the documentation link for doc migration * Update reno for stable/pike * Updated from global requirements 0.14.0 ------ * Updated from global requirements * Update the documentation link for doc migration * Updated from global requirements * Updated from global requirements * Using the latest openstackdocstheme settings * Remove long-ago deprecated show\_categories * Updated from global requirements * import content from cli-reference in openstack-manuals * move existing content into the new standard structure * Turn on warning-is-error in sphinx build * switch to openstackdocstheme * Updated from global requirements 0.13.0 ------ * Updated from global requirements * Updated from global requirements * Updated from global requirements * Modify Default Domain * Updated from global requirements * Updated from global requirements * Add package import to openstack CLI * delete bash\_completion in subcommand * Updated from global requirements * Optimize the link address * Correct the unit test name * Updated from global requirements * Replace six.iteritems() with .items() * Updated from global requirements * Imported Translations from Zanata * Replaces uuid.uuid4 with uuidutils.generate\_uuid() * Remove log translations * Make method import\_versioned\_module work * Updated from global requirements * Update test requirement * Updated from global requirements * Updated from global requirements * Remove support for py34 * Allows fetching of deployments from all environments * Update reno for stable/ocata 0.12.0 ------ * Exclude build dir for flake8 test * Add debug to tox enviroment * Remove white space between print () * Fixes filtering applications by name with glare * Remove the data assert to pass the gate * Add package delete to openstack CLI * Using sys.exit(main()) instead of main() * Return error code when a error occurred during package-import * Fix Murano client to use V3 and MultiDomain Authentication * Use assertGreater() and assertLess() * Support i18n for LOG.warning * Update author in setup.cfg * Updated from global requirements * Show team and repo badges on README * Support for environment list filtering by project id * Delete python bytecode file * Fix typos in cover.sh * Fix removes date\_time items from dictionaries * Fix removes date\_time items from dictionaries * Updated from global requirements * move old apiclient code out of openstack/common * Updated from global requirements * Add validation to package import * Updated from global requirements * Updated from global requirements * Add plug-in summary for osc doc * Updated from global requirements * Add functional test for environment-model-show * Fix osc plugin gives the error when using keystone v3 * Add docstrings for environment-model-edit and environment-model-show commands * Fix OpenStack Licensing * OSC plugin should be using region/interface * Enable release notes translation * Add support for environment edit API * Fixing environment creation from template * Unskip the test due to bugfix * Skip some tests when using glare in a right way * Mark .testr.conf as non-executable * Make some OSC tests more clean * Updated from global requirements * TrivialFix: Fix typo in the bash shell file * Add package list to openstack CLI * Updated from global requirements * Updated from global requirements * TrivialFix: Using assertTrue() instead of assertEqual(True) * Remove unnecessary setUp * Updated from global requirements * Make OSC plugin be able to use glare backend * Updated from global requirements * Make --limit argument in package-list command robust * Add opportunity to import package from directory * Add Glare to python-muranloclient tests * Fix 'owned' flag when Glare is used * Populate tenant information in client * Cleanup, clarify newton release-notes * Update reno for stable/newton * Deprecate usage of 'glance' for --murano-pcakages-service 0.11.0 ------ * Sync tools/tox\_install.sh * Increase yaql quotas * [docs] Update Readme with correct Doc URL * Replace functions 'Dict.get' and 'del' with 'Dict.pop' * Updated from global requirements * Updated from global requirements * Decode PrettyTable output before printing for Python3 * Use upper constraints for all jobs in tox.ini * Updated from global requirements * Updated from global requirements * Add script for unit test coverage job * Trivial: clean up oslo-incubator related stuff * Add package create to openstack CLI 0.10.0 ------ * Correct default class version for static action call * Support for Schemas API was added * Support for multi-class yamls in client * Add Python 3.5 classifier and venv * Updated from global requirements * Change image for package in the code taken from Glance * Remove discover from test-requirements * Make environment-action-call command accept JSON arguments * Updated from global requirements * Add support for static actions API * Updated from global requirements * Add deployment list to openstack CLI * Add environment apps edit to openstack CLI * Add py27-mitaka tox target * Updated from global requirements * Use upper-constraints in tox test environments * Add \_\_ne\_\_ built-in function * Updated from global requirements * Use osc\_lib instead of cliff * Add 'description\_text' filed to test\_table\_struct\_env\_show * Use osc-lib instead of openstackclient 0.9.0 ----- * Updated from global requirements * Use DummyYaqlYamlLoader to load classes during upload * Use yaml.SafeLoader instead of yaml.Loader * Replace print statment with print function * Updated from global requirements * Fix python 2,3 compatibility issue with six * Fix client could not init glance client * Import package and dependencies in correct order * Fix typo in env-template-create-env arg description * Replace tempest\_lib with tempest.lib * Add environment deploy to openstack CLI * Support resources-dir for hot-packages * Reorder releasenotes and increase toctree depth * Add '--dep-exists-action' argument to murano CLI * Utilize enpoint\_type argument for client creation * Modified docstrings to comply with pep8 H405 style check * Store transitive inheritance information in glare * Updated from global requirements * Updated from global requirements * Remove unused httplib2 requirement * Updated from global requirements * Further refactor of Glare API urls * Improve muranoclient install scripts syntax * Updated from global requirements * Updated from global requirements * Refactor urls to Glare API * Fall back to glance API v1 * Correct variable name in code adopted from Glance * Updated from global requirements * Fix test case of test\_category\_create\_long\_name * Typo fix for python-muranoclient * GetPackageUI API is now called even if Glare is used * Updated from global requirements * Correct check for artifact package visibility * Move OpenStack client to test-requirements * Make use of version passed to glare artifacts\_client * Fix displaying packages in category-show subcommand * Improve dictionary representation of package from Glare * Set "glare" as valid choice for "--murano-packages-service" option * Refactor request methods in HTTP/SessionClient classes * Fix authentication in glare-api via keystone * Adds improvements for verification of values from created and listed package * Fix env-template-add-app command failed * Do not log contents of the download request * Updated from global requirements * Add test to check error message for bundle-import with invalid file format * Add test to check delete environment by environment ID * Add test to check error message for bundle-import with non-existing name * Add Category support for openstack CLI * Add environment create/delete to openstack CLI * Add ability to override MURANO\_URL for murano OSC plugin * Add Environment support for openstack CLI * Update reno for stable/mitaka * Updated from global requirements * Add reauth if token is expired and username/password are available * Update dummy application for testing after changes in murano * Distinguish between glance and glare endpoints 0.8.3 ----- * Remove unused pngmath Sphinx extension * Add test to check error message for category-create with long category name * Import bundle with a not regular-bundle json give no error * Add test to check error message for bundle-import with non-existed package * Add "Version" column in the package-list output * Add test to check error message for bundle-import without bundle name * Fix version in requests to GLARE * Updated from global requirements * Add test to check category-show error message * Add test to check category-delete error message 0.8.2 ----- * Remove unexpected kwargs when authenticating using token * Updated from global requirements * Updated from global requirements * Deletes redundant line in shell.py file * Make CLI outputs consistent with other OpenStack clients * Rename the package-import version parameter * Fix reading of app template file when adding to env template * Fix inconsistent argument name/placeholder in EnvTemplateManager.delete\_app * Add Status field\_labels for environment list * Fix checks for Project/Tenant command line arguments * Improving python3 compatibility * Improve is-public argument in env-template-create * environment-action-get-result now calls correct get\_result API * Adds region parameter to environment-create * Fixes incorrect endpoint\_override handling * Fixes TypeError in Client constructor * Updated from global requirements * Fix first argument of murano env-template-add-app usage * Add filter for do\_package\_list * Fix spellings for some words * Fix package delete on update when using Glare * Update translation setup * Updated from global requirements * CLI shell can now properly interact with Glance Artifacts * Fixed visibility parameter for create package glare call * Fix module's import order * Add is-public for environment-template list * Add environment template clone * Initial commit for openstack-client support in python-muranoclient * Add the cover case for 'body' kwarg in http json\_request * Updated from global requirements * Add python 3 support * Updated from global requirements 0.8.1 ----- * Updated from global requirements * Delete unuse variable in shell.py * Repair package-update command * Remove unnecessary statements for default argument * Updated from global requirements * Updated from global requirements * use keystoneclient exceptions instead of oslo-incubator code * Add machinery for translations via babel * Remove argparse dependency from requirements.txt file * Put py34 first in the envlist order of tox * Change LOG.warn to LOG.warning * Updated from global requirements * Delete the special character in README.rst * Remove arguments "{}" and "[]" in function definitions * Add Type field\_labels for package list * Make package-update help more clear * Let the SessionClient raw\_request only return response * Delete the extra space in delete package print * Updated from global requirements * Use oslo\_i18n instend of gettextutils * Updated from global requirements * Pass environment variables of proxy to tox * Change logging level in SessionClient * Fix Resource.\_\_eq\_\_ mismatch semantics of object equal * Update setup.cfg entries * Updated from global requirements 0.8.0 ----- * Remove py26 support * Add limit param for package-list * Catch correct exceptions in shell * Add missing space to error message * Fixed an incorrect call to the artifacts client * Fixed a download method wrapper in glare adaptor * 'to\_dict' method added to PackageWrapper class * Glare client now properly filters by class name * Updated from global requirements * Add reno for RElease NOtes * Replacing application\_catalog with application-catalog * New operation env-template-create-env * Show detailed info about app * Add python 3 support * Updated from global requirements * Rename folder with CLI tests * Add keystone v3 support to client * Add CLI tests for environment redeploy * Add unit-tests for categories * Import images publicly when package is public * Add test for environment deployment using CLI * Updated from global requirements * Use standard formatters for environment-create-session output * Small refactoring of CLI tests for packages * Updated from global requirements * Improve README contents * Updated from global requirements * Add more tests for categories, env-templates and packages * Updated from global requirements * Add Active field\_labels for package list * Updated from global requirements * Use non-absolute paths, to allow urljoin join them correctly * Hide token id in logs * Fix incorrect output after error in package-save * Add missing parameter 'acquired\_by' to environment model in tests * Updated from global requirements * Fix archive name generation in package\_create command 0.7.1 ----- * Fix the common/utils typo * Fixed YAQL tag leakage to YAML loader * Check package-download for stdout redirection * Update the python-muranoclient docs * Update MURANO\_REPO\_URL * Output detail informations about murano --debug command * Updated from global requirements * Update path to subunit2html in post\_test\_hook and fix env-show test * Updated from global requirements * Enable F812 and H904 style check * Fix app-show command * Fix test for environment table structure * Delete the unused LOG code * Return detailed info when get environment by name 0.7.0 ----- * Added the support of Glance Artifact Repository * Copy the code of Glance V3 (artifacts) client * Fixed issue with cacert parameter * Strip json and html from error messages * Update the git ingore * Fix the reversed incoming parameters of assertEqual * Add olso.log into muranoclient's requirements.txt * Updated from global requirements * Standardise help parameter of CLI commands * Fix some spelling mistakes of setup files 0.6.3 ----- * Support for YAQL 1.0.0 * Updated from global requirements * Generate ids in environment-apps-edit command * Allow editing packages with package-update command * Add parameter support to pagination-list * Add CLI command package-save * Add check for dashes in template name * Add environment-apps-edit command * Add environment-session-create command * Add action controlling commands * Add params to environment\_show command * Updated from global requirements * Add environment-deploy command * Updated package-import help description * Add sanity tests for testing actions with environment CLI command * Add ability to filter from categories with unicode * Allow joining existing networks when creating environment * Switch to oslo\_log in murano client * Add basic tests for Murano CLI client. Add CleanUp after tests * Add bundle-save CLI command * Place generated hot package to work directory * Updated from global requirements * Add description to CLI command category-show * Remove unused module cliutils and timeutils 0.6.2 ----- * Rename all visible occurrences of 'services' name * Add docstring to service-show command * Fix the unit tests with wrong using of mock * Add passenv parameter for tox G.variables passthrough * Allows congress to fetch environments from all tenants * Remove all vim modelines * Edits the help-text for python-muranoclient * Add abandon parameter to delete method * Add --version parameter for murano python client * Update requirements.txt * Adding config-like auth configuration for functional tests 0.6.0 ----- * Point default MURANO\_REPO\_URL to http://storage.apps.openstack.org * Support multiple filenames for package commands * Remove hash check during image upload * Make post\_test\_hook.sh executable * Add post\_test\_hook for functional tests * First pass at tempest\_lib based functional testing * Add OS\_TEST\_PATH to testr * Move unit tests into unit test directory * Drop use of 'oslo' namespace package * Only delete 'owned' packages for --exists-action update * Updated YAQL requirement to >= 0.2.6 * Update Readme * Limit parameter is no longer ignored for packages * Update .gitreview file to reflect repo rename * Better error logging for package/bundle import * Update package composing command * Tests for repository machinery * Bash completion script for murano * Add bash-completion subparser to shell client * Update from global requirements 0.5.6 ----- * Use HTTPClient in package\_create manager method * Removed default Content-Type HTTP request header * Improve image and bundle handling * Support local bundles * Fix service-show command * Fix category list CLI command * Enable Bundle.packages() to yield Package objects * Environment Template CLI * Handle duplicate packages during uploading via CLI * Add support for required images file * Client support for Require section in manifest * Allow importing bundles of packages * Allow importing packages name * Allow importing packages by url * Adds client support for action results * Use name or ID of environment for murano CLI commands * Enable category management support * Fixed pagination parameter to conform to API specs * Reverted "Add keystone v3 support to client" * Fixes pagination in "packages-list" command * Add keystone v3 support to client * Add 'verify' option to package.create * Do not parse empty body * Remove obsolete checks from tox.ini * Use pretty-tox for better test output * Update from global requirements * Use modules from oslo istead of openstack-common * Add opportunity to create public packages via shell * Revert "Add keystone v3 support to client" * Add keystone v3 support to client * Catch ConnectionError exception from requests * Updated from global requirements 0.5.5 ----- * Log response when using requests library * Provide method for calling action in an environment * Bump hacking to 0.9.x series 0.5.4 ----- * Fix some calls after switch to 'requests' 0.5.3 ----- * Convert muranoclient to 'requests' * Fix exception names and CLI args * Adds toggle public functionality * Add LICENSE to muranoclient * Update package-import command * Add package-create command * Updated from global requirements * Allow deletion of multiple environments * Infrastructure update * Add ability to retrieve supplier logo for package * Correct muranoclient service\_get function * Enable to set updating type for "Update package" * Extend package-show output with detailed information 0.5.2 ----- * Improve logging in CLI client * Extend CLI functionality * Fix issue with python 3.3 gate job * Paginate packages list using 'next\_marker' attribute * Run hacking in a right way * fixed several pep8 issues * Fixed Keystone endpoint query * Enable unpickling base.Resource objects 0.5.1 ----- * for pkg list, make include\_disabled default False * Return package object in packages.create method 0.5.0 ----- * Remove version from setup.cfg * Added HTTP proxy support * Fix the issue with stats API call * Update requirements.txt due to changes in global-requirements.txt * InstanceStatistics updated for corresponding API change * Parse YAML in muranoclient with the loader of the given class * Refactor packages filter and list methods * Interface to get instance statistics added * Support package update, toggle\_active and filter methods * Initial support of working with package definitions * Updated openstack-common * Calls to the v1 API are prefixed with the appropriate version prefix * Add Stats List method * Fixed issue with requirements * Update README with actual info * Update requirements to stable/havana * Update version in setup.cfg * Support passing networking info during environment create, not update * Support getting and updating network\_info of environment * Cherry-picked changes from release-0.3 * Support building wheels (PEP-427) * Update requirements to match havana's ones * Resolve issues with package setup.py * Cherry-pick following change-ids from release-0.2 * Fix error from previous commit * Fix service creation failure * Fix http.py to initial * Resolved bug 746 * Add SSL support to muranoclient * Modified client to support lastStatus fetching * Add CentOS setup shell script * setup.sh add * list deployments in client now properly returns a list, not dictionary * Fixed a couple of bugs in path resolution routine * Changes in client to work with deployment logs fetching * Added support for new API v0.2 * fixed python setup workflow * Added tests for new services * Another changes within Service::Delete * Correct Service::Delete params * Murano Python Client support for IIS/ASP.NET web farms * Fix API according to New Sessions Handling design * python-muranoclient to support ASP.NET apps deployment API * Added .gitreview file * glazierclient -> muranoclient in tests * KEERO-320 - Fix all occurrences of old names (keero, glazier) in python client * Removed all projects except Glazier Api Client * Removed all projects except Glazier Api Client * Small fixes for unit tests * Fixed issue with length of new name * Fixed small issue * Fixed issue with names * Fixed issue with names * Fixed issue with names * Fixed issue with names * Fixed issue with names * Fixed issue with names * Fixed issue with names * Fixed small issue with new names * Fixed issue with horizon component installation * Fixed issue with horizon component installation * Fixed issue with horizon component installation * Finished converting API Specification * Fixed small issue with setup.py * Fixed issue with renaming of the tabula component * Fixed issue with renaming of the tabula component * Added part of API Specification * Tabula renamed to dashboard * Tabula renamed to dashboard * Finished documentation for API * Finished documentation for API * Renamed Portas to API * Renamed Portas to API * Added tox for webUI tests * Added license to documentation of Portas Client * Small fix * Fixed api interface names to environments * Fixed api interface names to environments * Renamed and licensed python-glazierclient * Fixed api interface names to environments * Fixed api interface names to environments * Renamed documentation project * Fixed licenses for tabula and tests. Fixed name of tabula project * Fixed licenses for tabula and tests. Fixed name of tabula project * Fixed a few small issues * Pass all RabbitMQ settings from conductor to Agent * Initialization of tox for conductor and portas. Add new webUI automated tests * Licenses added * A lot of changes were made * Issue with figures * Main Documentation Project * Fixed ignore file for python-portasclient * Documentation for UI * Documentation for Python PortasClient * Forgot man pages * Documentation for Portas Project * Send token when deleting environment * Fixed https://mirantis.jira.com/browse/KEERO-227 * Fixed issue with sessions * Fixed issue with sessions * Experiments * Experiments * Experiments * Experiments * Experiments * Add logging to WebUI * Add initial files for unit tests * Fixed issues with sessions Added logging * Added unit tests for client. Coverage 66% * Rename RabbitMQ username field Removed use\_ssl option from settings * Fix running install\_venv.py * Updated python-portasclient * PEP8 compliance * Fixed unit tests * Added ability to add services to environment * bug fix * bug fix * Fixed index bug * Finalize UI * Finalize UI * Experiments * Experiments * Experiments * Experiments * Experiments * Experiments * Experiments * Experiments * Experiments * Experiments * Fix PEP8 errors * Experiments * Experiments * Experiments * Experiments * Experiments * Experiments * Experiments * Experiments * Fix issue with statuses * Experiments * Experiments * Experiments * Experiments * Experiments * Experiments * Experiments * Experiments * Experiments * Experiments * Experiments * Experiments * Fixed issue with sessions * Naming conventions: use name instead of id for instance names * Heat auto-discovery, keero-linux-keys -> keero-keys * Experiments with UI * typo * Scoped tokens * Experiments with UI * Experiments with UI * Experiments with UI * Experiments with UI * Experiments with UI * Experiments with UI * Experiments with UI * Fix issue for result of deleted environments * Fix merge issue * Merged * Fixed small issue * Fixed small issue * Fixed small issue * Fixed small issue * Fixed small issue * Fixed small issue * Fixed small issue * Fixed small issue * Fixed small issue * Fixed small issue * Fixed small issue * Fix name of the variable * Fixed small issue * Send token when deleting environment * Removed unneeded binding * Fixed small issue * Fixed small issue * Fixed small issue * Fixed small issue * Fixed small issue * Fixed small issue * Fixed small issue * Fixed small issue * Fixed small issue * Fixed small issue * Fixed small issue * Fixed small issue * Fixed small issue * Fixed small issue * Fixed small issue * Added tabs for services * forgotten file * Fixed issue with activeDirectory deletion * Add support for reports filtering * Added tabs for services * Fixed small issue * Fixed small issue * Fixed small issue * Fixed small issue * Fixed small issue * Fixed small issue * Added dropdown list with list of services * Added initial version of tabs for services * Added initial version of tabs for services * Added initial version of tabs for services * Fixed issues with templates * Fixed issue with installation * Fixed issue with installation * PEP-8 * Fixed issue with incorrect import * logging and bug-fixes * fixed VM initialization script * Evironment/Units deletion, bug-fixes * Fixed UI issues * Remove service deletion button * Finished installable UI * Removed old code * Naming conventions changed * Updated OpenStack Common (Fixed issue with notifier package) * Use Heat REST API instead of command-line * Added support for setup.py Added localization and documentation skeletons PEP8 Fixes, optimized imports * #KEERO-222 Remove random part of unit name * Send Env to Conductor for deletion * Hot fix for WebUI tests * Fixed WebUI tests. Added new tests * #KEERO-220 Send X-Auth-Token to Conductor * Added initial unit tests for RestAPI service * Fixed all pep8 * Fixed automated tests for web UI * Fixed automated tests for WebUI. Added class Page with simple objects * Code to start\stop keero components using devstack functions * Fixed: changed the run mode for install venv script * Added deployment script for automated tests * All Cloudbase-Init plugins disabled except UserDataPlugin * Userdata script updated to support computer renaming functionality * Entry point script renamed 2d27f4f5054f34982ed67da2bf4b35c8ac1558d3 * Issues #195 and #199 * README and guide for conductor * Fix and unit test for issue: https://mirantis.jira.com/browse/KEERO-219 * Added unit tests for REST API client. Fixed pep8 * New devstack scripts added * Old devstack scripts removed * Write-Host replaced by Write-Log * Fixed typo * Sync * Sync * Sync * Cloned horizon and added our dashboard * Removed obsolete code Removed projects: [windc, windcclient] * Added tests for REST API. Fixed issues with Web UI * Added tests for REST API. Fixed issues with Web UI * Merged iteration3 branch to master. Fixed pep8 * Fixed small issues with UI * Added lst fixes for demo * Fix another issue with environments list * Fix another issue with services * Fix issue with getting list of environments * Added progress bars for services. Fixed few issues for demo * Fix issue with ack on results * Add part of service id to unit name * Add ability to get status for Environments and Sessions * Added password-secure checks for UI, fix usability issues for demo * ExecutionPlanGenerator DSL processor * Updated workflow elements to reflect new name changes and to fix typos * ExecutionPlanGenerator DSL processor * Updated workflow elements to reflect new name changes and to fix typos * Another Issue with sessions * Added progress bar to Web UI. Fixed pep8 errors * Change behaviour of viewing services * Issue with session * No ack is needed when auto\_ack set to True * Resolved issue with reports from orchestration engine * FIx issue with fields created & updated * Fixed issue with logging * Added deploy button for data centers in UI. Fixed templates for services * Queues should be durable * Add debug logging for controllers * Sync before tag * Fixed issue with empty services list * Added new API for Web UI * typos * Fixed length of names * Fixed instance namings * Added WebServer and AD * Workflows, ExecutionPlanGenerator, Reporting, UserData, conductor improvements * Removed obsolete file * Bug with Session * Added Session operations * Added Environments CRUD operations * Issue with deleting Environment * Removed obsolete files * Added initial version for python-portasclient * Issue with port for RabbitMQ * Function updated to return IPv4 addresses only * Typo * Explicit import of module DnsServer added * Function to install IIS added * Code to return DNS listening IPs from DC added * WebServer API Added WebServer API Small refactoring * Fix issues with queues * Added units name generation * Write results from orchestration engine * Active Directory API * Remove obsolete service table * Enable session deployment * Reports from orchestration engine Added ability to store and expose reports from orchestration engine * Cleaned up API * Added support for reading messages from RabbitMQ asynchronously * Typo * userdata.py fixed * Finished Task KEERO-111. Added base UI tests * Hot fix: Fixed pep8 for Dashboard * Finished Task: KEERO-117. Added new UI wizard for Create Services Action * Userdata plugin with minimal MIME support added * User data sample file added * Extra functions moved to NotCoreFunctions.ps1 file in order to remove them in the future * Functions to work with Base64 strings added * Functions to work with Zip files added * Modified files from cloudbase-init added * Fixed pep8. Fixed deployment script * Added support for session checking * Most part of Session API * Removed obsolete code * Added new Session model and migration Fixed issues with previous models * Initial conductor implementation * Added deployment script and automated tests * Small PEP8 fixes * Fixed small issues with parameters. It is required fix * Added remove method for environments Also slightly updated routes table * Finished environments api * Only environments from same tenant as users should be shown * Remove unnecessary blocks of code * When new DC is adding tenant\_id is added as param * Fix issues with context * Moved utils.py from WindDC * Small changes to .gitignore Removed global .gitignore Added .gitignore to WindowsAgent project * Update added files * Added support for keystone-auth * Updated initial version of portas-api * Initial version of portas-api * Simple function to update agent config added * Simple function for working with templates added * Function to retrieve meta data opject from config drive added * localrc updated * Files to automate devstack installation added * Fixed small issues with WebUI * asd * test.commit * test.commit * Log functions updated * Stop-Execution modified * Removed obsole line * Removed obsolete file Added .gitignore file * Added reference JSON for Active Directory * Fixed urls for dashboard * Fixed Web UI for demo * Files removed * Windows PowerShell module added * Unattended installation files added * Execution plan files added * windc iteration2 * Added WebUI for correct configuration of new service AD * Resolved issue with datacenter id * Resolved issue with datacenter id * Fixed many small issues * Fixed typo * Fixed KEERO-89 * Fixed issue with data centers * Added services functions to client. Need to be tested * [KEERO-83] Windows Agent: Ability to reboot machine after execution plan is executed * [KEERO-83] Windows Agent: Typo fixes + sample values in config * [KEERO-83] Windows Agent initial implementation * Added operations for chef. They might be remove if we decide to not use chef * Fixed small issues * Fixed KEERO-85 * Fixed issue with virtual environment SQLAlchemy library * Added library libsqlite3-dev to virtual environment for windc client * Added new functional to dashboard, fixed small issues * Added windc API client, sync repo with dev box * Added new files * Updated design. Removed extra code * 1. Added support of CloudFormation templates. Made a simple interface to build template. Stan can work here to redesign template.py 2. Added calls of drivers. Now heat is called from cmd instead of client. Should be rewritten. 3. ActiveDirectory makes a static template. Need to rewrite this with working with actual parameters * Added additional fields for Domain Controller * Added simple form for configuration Domen Controllers and IIS Servers * Fixed small problems with links and titles on pages * Fixed small problems with links and titles on pages * Added initial project for horizon dashboard * 1. Added builders support. Each builder is a class dynamically loaded from ./windc/core/builders folder. The class name should be the same as module file name. 2. Updated core/api.py to support datacenter and service creation with extra parameters which are not defined by model explicitly. 3. Added event based approach for the windows environment change. Now when user submits a request to API the core updates database and initiates a new event which defined scope (datacenter, service, VM) and action (add, modify, delete). This event and data will be iterated over all registered builders. Each builder can use this event and data to plan some modification * 1. Fixed issue with main file start ./bin/windc-api 2. Added router to Route /datacenters/ and /services/ URLs 3. Added stubs for windc/core/api. 4. Fixed start-up process for service ------------------------------------------------- Now it is working service which will reply for curl http://localhost:8181/tenant\_id/datacenters/ curl http://localhost:8181/tenant\_id/datacenters/dc\_id/services curl http://localhost:8181/tenant\_id/datacenters/dc\_id/services/service\_id * Initial version of the Windows DataCenter project. It is openstak-skeleton based * Unattended files added * Initial empty repository python-muranoclient-1.3.0/babel.cfg0000664000175000017500000000002013523272253017276 0ustar zuulzuul00000000000000[python: **.py] python-muranoclient-1.3.0/CONTRIBUTING.rst0000664000175000017500000000235713523272253020230 0ustar zuulzuul00000000000000====================== Contributing to Murano ====================== If you're interested in contributing to the Murano project, the following will help get you started. Contributor License Agreement ============================= In order to contribute to the Murano project, you need to have signed OpenStack's contributor's agreement: * https://docs.openstack.org/infra/manual/developers.html * https://wiki.openstack.org/CLA Project Hosting Details ======================= * Bug tracker * https://launchpad.net/murano * https://launchpad.net/python-muranoclient * Mailing list (prefix subjects with ``[Murano]`` for faster responses) http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev * Wiki https://wiki.openstack.org/wiki/Murano * IRC channel * #murano at FreeNode * https://wiki.openstack.org/wiki/Meetings#Murano_meeting * Code Hosting * https://opendev.org/openstack/murano * https://opendev.org/openstack/murano-agent * https://opendev.org/openstack/murano-dashboard * https://opendev.org/openstack/python-muranoclient * Code Review * https://review.opendev.org/#/q/murano+AND+status:+open,n,z * https://docs.openstack.org/infra/manual/developers.html#development-workflow python-muranoclient-1.3.0/run_tests.sh0000775000175000017500000000556513523272253020160 0ustar zuulzuul00000000000000#!/bin/bash function usage { echo "Usage: $0 [OPTION]..." echo "Run python-muranoclient's test suite(s)" echo "" echo " -p, --pep8 Just run pep8" echo " -h, --help Print this usage message" echo " -V, --virtual-env Always use virtualenv. Install automatically if not present" echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment" echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added." echo " -u, --update Update the virtual environment with any newer package versions" echo "" echo "This script is deprecated and currently retained for compatibility." echo 'You can run the full test suite for multiple environments by running "tox".' echo 'You can run tests for only python 2.7 by running "tox -e py27", or run only' echo 'the pep8 tests with "tox -e pep8".' exit } just_pep8=0 always_venv=0 never_venv=0 wrapper= update=0 force=0 export NOSE_WITH_OPENSTACK=1 export NOSE_OPENSTACK_COLOR=1 export NOSE_OPENSTACK_RED=0.05 export NOSE_OPENSTACK_YELLOW=0.025 export NOSE_OPENSTACK_SHOW_ELAPSED=1 export NOSE_OPENSTACK_STDOUT=1 function process_option { case "$1" in -h|--help) usage;; -p|--pep8) let just_pep8=1;; -V|--virtual-env) let always_venv=1; let never_venv=0;; -f|--force) let force=1;; -u|--update) update=1;; -N|--no-virtual-env) let always_venv=0; let never_venv=1;; esac } for arg in "$@"; do process_option $arg done function run_tests { # Cleanup *pyc and *pyo ${wrapper} find . -type f -name "*.py[c|o]" -delete # Just run the test suites in current environment ${wrapper} $NOSETESTS } function run_pep8 { echo "Running pep8 ..." PEP8_EXCLUDE=".venv,.tox,dist,doc,openstack,build" PEP8_OPTIONS="--exclude=$PEP8_EXCLUDE --repeat --select=H402" PEP8_IGNORE="--ignore=E125,E126,E711,E712" PEP8_INCLUDE="." pep8 $PEP8_OPTIONS $PEP8_INCLUDE $PEP8_IGNORE } NOSETESTS="nosetests $noseopts $noseargs" if [ $never_venv -eq 0 ] then # Remove the virtual environment if --force used if [ $force -eq 1 ]; then echo "Cleaning virtualenv..." rm -rf ${venv} fi if [ $update -eq 1 ]; then echo "Updating virtualenv..." python tools/install_venv.py fi if [ -e ${venv} ]; then wrapper="${with_venv}" else if [ $always_venv -eq 1 ]; then # Automatically install the virtualenv python tools/install_venv.py wrapper="${with_venv}" else echo -e "No virtual environment found...create one? (Y/n) \c" read use_ve if [ "x$use_ve" = "xY" -o "x$use_ve" = "x" -o "x$use_ve" = "xy" ]; then # Install the virtualenv and run the test suite in it python tools/install_venv.py wrapper=${with_venv} fi fi fi fi if [ $just_pep8 -eq 1 ]; then run_pep8 exit fi run_tests || exit run_pep8 python-muranoclient-1.3.0/muranoclient/0000775000175000017500000000000013523272337020263 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/locale/0000775000175000017500000000000013523272337021522 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/locale/en_GB/0000775000175000017500000000000013523272337022474 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/locale/en_GB/LC_MESSAGES/0000775000175000017500000000000013523272337024261 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/locale/en_GB/LC_MESSAGES/muranoclient.po0000664000175000017500000000663613523272253027331 0ustar zuulzuul00000000000000# Andi Chandler , 2017. #zanata msgid "" msgstr "" "Project-Id-Version: python-muranoclient VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2018-01-30 01:30+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2017-10-21 09:07+0000\n" "Last-Translator: Andi Chandler \n" "Language-Team: English (United Kingdom)\n" "Language: en_GB\n" "X-Generator: Zanata 4.3.3\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" #, python-format msgid "AmbiguousEndpoints: %s" msgstr "AmbiguousEndpoints: %s" msgid "" "Application catalog API version, default={0}(Env:" "OS_APPLICATION_CATALOG_API_VERSION)" msgstr "" "Application catalogue API version, default={0}(Env:" "OS_APPLICATION_CATALOG_API_VERSION)" #, python-format msgid "AuthSystemNotFound: %s" msgstr "AuthSystemNotFound: %s" #, python-format msgid "Authentication failed. Missing options: %s" msgstr "Authentication failed. Missing options: %s" msgid "Bad Gateway" msgstr "Bad Gateway" msgid "Bad Request" msgstr "Bad Request" msgid "Cannot find endpoint or token for request" msgstr "Cannot find endpoint or token for request" msgid "Conflict" msgstr "Conflict" msgid "Defaults to env[MURANO_URL]." msgstr "Defaults to env[MURANO_URL]." msgid "Error {0} occurred while setting image {1} public" msgstr "Error {0} occurred while setting image {1} public" msgid "Expectation Failed" msgstr "Expectation Failed" msgid "Forbidden" msgstr "Forbidden" msgid "Gateway Timeout" msgstr "Gateway Timeout" msgid "Gone" msgstr "Gone" msgid "HTTP Client Error" msgstr "HTTP Client Error" msgid "HTTP Error" msgstr "HTTP Error" msgid "HTTP Redirection" msgstr "HTTP Redirection" msgid "HTTP Server Error" msgstr "HTTP Server Error" msgid "HTTP Version Not Supported" msgstr "HTTP Version Not Supported" msgid "Internal Server Error" msgstr "Internal Server Error" #, python-format msgid "" "Invalid %(api_name)s client version '%(version)s'. Must be one of: " "%(version_map)s" msgstr "" "Invalid %(api_name)s client version '%(version)s'. Must be one of: " "%(version_map)s" msgid "Length Required" msgstr "Length Required" msgid "Method Not Allowed" msgstr "Method Not Allowed" #, python-format msgid "Missing arguments: %s" msgstr "Missing arguments: %s" msgid "Multiple Choices" msgstr "Multiple Choices" #, python-format msgid "No %(name)s matching %(args)s." msgstr "No %(name)s matching %(args)s." msgid "Not Acceptable" msgstr "Not Acceptable" msgid "Not Found" msgstr "Not Found" msgid "Not Implemented" msgstr "Not Implemented" msgid "Payment Required" msgstr "Payment Required" msgid "Precondition Failed" msgstr "Precondition Failed" msgid "Proxy Authentication Required" msgstr "Proxy Authentication Required" msgid "Request Entity Too Large" msgstr "Request Entity Too Large" msgid "Request Timeout" msgstr "Request Timeout" msgid "Request-URI Too Long" msgstr "Request-URI Too Long" msgid "Requested Range Not Satisfiable" msgstr "Requested Range Not Satisfiable" msgid "Service Unavailable" msgstr "Service Unavailable" #, python-format msgid "Some attributes are missing in %(pkg_name)s: %(attrs)s." msgstr "Some attributes are missing in %(pkg_name)s: %(attrs)s." msgid "Unauthorized" msgstr "Unauthorised" msgid "Unprocessable Entity" msgstr "Unprocessable Entity" msgid "Unsupported Media Type" msgstr "Unsupported Media Type" python-muranoclient-1.3.0/muranoclient/data/0000775000175000017500000000000013523272337021174 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/data/heat_logo.png0000664000175000017500000020440013523272253023640 0ustar zuulzuul00000000000000PNG  IHDRrrgF^sBITOtEXtSoftwareShutterc IDATxis#GG ,uO]kkc;3=GX5*uDRH03p[ucD/ fu:!*"233RRJݮU1/wwBd3PD43U%"f^ Up8Zc_s"BDV3co /o}3c6xADmO;>m߻#U53CD"23i ~w={ׯs]v5YC\}A)}_dYァ> inEUgAaI~fd>芳L`e sELC-d Vzr/EziGskBHP\Z9wEK6zn5xYyJ;Kcfn oP⽏16^ G-Eoj9"JNE6H-zc+ou7@)嚭鬏) c[p']^!Biv)ov V$aiG喟-|\ ++Yy5g1SJcaZ]dylCK>z-]ץT5F+m 6iPBXC\YSli,<4PtxudxkψXۅ2eYT5tۥ.naG[&c߾ ι;M8wZUUD߷UDUG&>|>[y;~Zia&4HMazRyS:ZTe&Bpmsm^>_! Ta-= 7F[G` VǓ^o_XlZ@7nW`rLvSZ֒8 PVq p_|LX}1 Uט b/#,[D֭9O䘽s5\Ji+0mwkԛ{߂F[7nmӗvsuHDMْ->+ҽ.[â6[B|1V])[ιDv4ڂhk\K'}E`~ni-Ao%jۇ|D & 4jڇ Wx fSDאqe$foiȿE[?n}+ldPDRWT`j+4o,ul&_ڊ-AD !UKe"k(ԾrFB_syCAX6-Bh}ڼ{+]-blzŎ@>SrmZ0I} K),*2D案_r߮m/l-}DploVvyhEۻ]um/gJ)-um*Cy24eQ#tb9e%v:bno}VYP[Vʹ15|67XS?ya]5Dޫ!  QW]ܶϑ?W ^x3fhk ސ"JRk=rSUm}s}Wdu$.HD) ҚW2fhMZsYFU?x! {tsŴ:$0[+\ {U[Hhy9M[o[p'[brykͰVUjfUԲ(NG?_85O  rX) V>g/!:8[wZsYѝ`嶾}]xjڲ8Ve-*fcV@UMӘSy|>>8eB9ljZ bRj7 :VA0sVsVnm5V߶ZASEշ</L3m"֜S:|_܏ ba3ITjR@C! ^h] *3}{&|O{۶m5k[Ok$ADsn-ȶ.asj*RLK 5ɕYJDϞ@ @ւV7=u.vZ`9}J~k+Mn[ SZ|)eQ+ESUum6|Gme4MPQS1"IS4q6kEn7l5sź`f hgڂh^{FE=-,RJND.. V~n/>V{ѣ1V>a JZRe洸eЪ2r 쨋; ϵ5HM'ZLF=k1u Vn5YlK?֌îhQIRMDKKqH nq@r1 H,!`l>H<،T5^S+7XkkzX.fVXQ0m?)UR4gK R:t >nPؓ91 _r>*ҷR?Ces, -WcMIiVj? >os  ֦L` ""Ҍytq֔%[wٳ5?mB. jjU+3q)*r+2;/QǾV؞aTU TUk,M4Oz:2TTEÀ>:΃  f.e[||MOއv{+[bwdN#`LL@U WIs2|XO8dj CI >·ܹ4P a o{aŗv{loCwؗN./YiiP\$dyeeLcP葉DH =L=7<\\ Ke. Vn뼓VvZVnWV0#5 5kYd9R˰~8 agEtr%XW[Q;v{qλu6|jly;^ֱAo+? o+֪|a ?ZQLQL"j-&EQƉѦQ\!:uKFPDm"086RK!~$A!he]4r;Y`VVZv;ZE#ZBRrs]&Y&GsJ11B9h?~8,{?H͵f!z=AGDd]co.*³1B]9;ގttIi?4emq9iJL;wC z;dPS9s\m}O2'Ur*$Iw+f+$Ŷ-SkE6~l[Op[)ں 6RJNAibLDmK%&ɞ8R)օԹڅn_C([pUPI%! K,5ϸLs@K_!R1q`s7XAV6]DBiah^Ms=7me#"#ddJZTT甋2OKkzן_g͐s=>q?c޻H1Ypta}^#v{"YbRRLM3mZ"-K%ԭ9](%.[:A?wNoۙrXVa6*´2U.97b%k)c,qqgfvh)']N4YjeՇtw@A&gfvjRհK ZTU%XB7jTkmn"ROWUUZ!Qm/1~w?vrmdj%Uۏ>˹yR9zxq*Oe)UD@{zwCT 2333TeqCUKZ98^|cʋٵ",[wZ5ZGm^lxwnAЏ>.ssK ;j ٙ(X)sg'?O0NxL{9&j.3ڬZߙ QKT&ߐNOJ3s.Gg>z?`"3{+K)1cn _oMY*Lu<ӑ{|,J,FG/w4sثUS9AByeT$2%1R0-f,j֒Qx@sឿۺǔR$¿koQ)0 !6+>l|4U%YҶxVjU544'{z'{HOnVv>ҾN8ي*21sNS”ZKMTLP! 3پzV/޷(z{ZUVY; Y^YVC0&FdE6SߛU"z(C FFg1dieOGO~ .@gbuu lRsľ3-!:11/Tj1&e;gS.V']VV\z?G}>sޞ6mmgHUUeYP9~QU&T~ӱs7Ӝ)oÑ̇]꼯nfUz=He JE$~_r<7 K"ܟžWu-^&Z<˲4I~۪~!Blmۦ%+۱sf6+o bV e{^z^jbڲ[ EJjjjjՒ4OGF&Ox<#sý}b=Ǟ}DD13;gL:W!q KhVI a>%sk-wܯm-gvQWUmN[O+c1NlFHC!J5U<xN'{zbƘenw8tGA"@D`f bC%ђ*EsҴԴH*3P5D5ϨK%eߚPaƋ_I}q. oig@ɹm”dZ@9t[Zk V۶۱d a3y5[UYHjӀTJyO(?8˻]!wa7;3ڼ;#uIKR!'Es1πuYK}%yq](`5͠bJ~m 2[!ζioZ[\;mbkn /PEȵ.e|OGx|,yN9֡<<,}8pǾ# 3Iw5[ҟ&%}۪_V8ー؎ֻkauT+si-^زhVA Dƅv}w>vsTSDrH!8bfHjSXJRZAӉ `h``7n}XDˆ-{E[MYs%`-BPoY+"&xs{j&zb{]L1oHEN#"kONž  %cwx ?=3^G 1!a k%)2g Br-shP3"bqav5<}p5Iu(!&@$ HD`e+^ܟҜHfj*,11RkB٭EExzh4}3L| ]/:/ K)ifKKyz)N;4ђTԢ]_vyḰ))V%-&lH՘DbjBj PsLqrˌ<2K] EՓ``؜}͎msPzḫM~0_ O (+slEM]?M@$|-]m.BaT IDATbZj-BP,fyw@]8P׻؀Ξ.DvU5cޫ8 yPCY2. R\Yk*%UU# "y44 ¹H:ڷE^` Z][ s}}1敷8ZŶ;i `(Z"IKǑ'8$bĴJK hs] D&D##3`gJL1T F]0,br^橦e #`&d`*A$dVpM9_Un7kuz%b(׿-wQ+ZgmqbfW kIK2m<)x9tA({z 7r+&C1Ud!$50q1خ+ "DS)I])ރq02 @:?Fҳ=0j.ĺk<Ǐ#v|M@ ^ez\$ V[a6]jIiu>Ƽ 2L#O" eߕqwBO!oEوL >fLN`}!U @yW;Yj ̤䒗y$'wxԟ;9 >tC/C옉=e-ЁĘC'`(!v s@h&ЈмQR gtơ:/'-dU=,c&gFY$"J)"0)ȚڄgGյZD6j _n}m4qo^Sޚ䕵^Ҝ_tz TH^r-Y#G> Wj;xx(tqÞw}yYֆWD&_)sA) s jEdh)IN5Vc5j9dhd&6-b;VL__'{7^ao/M<~3k*Uњke9<4>>H*Y^z_v)\q.l B !0{ދ:GmS)ًuR;Y&I$#6_W*Z>@[a"Z?= jsnWu1x֏\Tb:TӨɏGq扑ԡv~ Cƽ|Y= õ0GFC@yoF`HȀ,JUN>hGs]Y ,i6jL׎m}qd [TkXV^A녏EȪhÔRJNKZ2`>xCxlfE- ;7܇0xhg\cvN9D%6vbЌ)CVŏs>#'"D9R3k%g`g;%t>PJ\s KRƉ%<_vvjjK,"͗듗}~r-\OYCTRSNlˌiiÉsu\u~(}_.;wjFp UR&0m~#[$>w G޶o6-Jȉ'LUYj4t,izz 9;~n!t};@Rֱ@ y:"2S2UQ3n9R+], R)>e;vޑwQ̪C!RSiiP|npÝIZp,DK/ ^ZDLTDJ9/Lʹ2%dgkk4W +[c)5wzs!!(( jMYO|zrc=b-Iknw]~pw!:l_١SB9B`S Tg}zݎFwgMsK)}g=ypm{^PXZQkTcfir jي @VXZ#/#G:|x|\B1ۇ00yDj Gu߉azw BN xSmػn]9쀩&Xn{{BM]TrɦYh@LZ˒Bir.22sZYe7;^ Cm2V,cN4_倿>@-aOoc-]ww1vEpv`d0F8xf*~10}luj.Z bZ2߭-ʵr.,D續umhi>p^0nyn'}ݻuq߇q`fdߜlCfc'w7:7BC8&:8ቖ)ϧ̒ŴuW0PVyVwb*UKii)iyiӄ E!nc]{ak7gsק ;<}hqؑ j`0 |`8O0#E22ʲh&T.~ S[|[Ii5cUTma:xǓ?ܸ$&짇lw_\a}`fj`9/xn_Mt5ylMD h.J`kp ,Fv;xset4ղh&" gsu+AP$#2j햷Jc榺7sJ"ʥ|%t,$.0bEӔ`<ӓaZDA]8+G*"n MM;Cc=G.wf#D"GD06%θgBu|ˌKW*}o9Z)?>AO<\[L̨AݹoTAOJz{%EY4'_jڪJYj$`q:@O4N(du2t5F:ڬz C&CRCSQON w"`ީ  wGP ?aq\d=ŧ'z|qR0eM}n 9El3h;mb  ټ3{ໞ{Oj\` D@Lm*\G8i N9-'@D$P HUiuش֟1XB׶ZFYDJ*i(H蟎4(Uevw'wnui(0Ra/I{b1!VŅu}e*PSTA!:D=#MS̙9ϧ42K);4]| 퓇m۞gw[hs T\2t@OSgՑ.~/wej't;!r%FlQU7@Fd3;r>T%Fza.AMu'+U̳ u@K ~adHDw]2= {~.Ҕ\yK!mZsGPG:R)9ss)11JlffHڼo~cmlv ^Nk1V-Ys40z,̞\hlEbYS)BbL)aw[͵q$SO33dVf\0rU;v: B%&`-AtMt9tp?ol!$}EY򙣜yty3 Osӣ^qtk7<8_C)VM>nҷVX6^_8!b &@F47'prr039uOUbYӢ!͘Z Xýu$9ا=ZW*è[|y%}9L|-!8g9n59u&c^iBtY˧$]֑2UKH[$"$fw'~sĬ ! &rFjn@3mAH"[-04,B(;y}ԃS֔ A'Dܳ _Y?+nƮdJh(nUm|ov[~5^oЊǛdי.}8 ve@hMC`D0'rb`w$70w@Wc NlH[DH_f&,#7kJ9%(IUH6z2 TN"wG7YNof,EQK0 &DEVq ?ЗLlsF ʸi9fEb Ff0|Os +غh SZ!9 MݘߥEفwIsGgm'Ήm\DbИ@R%u>˴p;_Tspxav{)z:4fts'_g <6OoZRpߥzakQxXl=,9S(縇#}?;~?|ϭC6S ba;,;a:b{9\_쾏|3Y(݁Pkd= +Md  IDATTU]wmUkcFon;ՎDחc[.KX6K Q"`q~`#46bDԑc{̶GYt$Qc}Ljh$Mb#eFGn8vfcWstz3 9NS hCՎR/G~M%Y,ˌ—W[.I-%EUn6o˘>!^VXS!93Sw`gaDPs 16 &1Cx 䄂0[(bc,}$]_]Z>nx4^%31"0 x =% ~z:ɪhgvP~}8nljLrr6+6[HQDTkۯڎ⣻;d ƮaYU‡)\);S(&X) V*9Nfemie^(*#[l.<5"OP +⁈@hXqwwUs^pF;&]2ǿ߶&ˋ-CDHr&ioovڏՎ۬rO0e”DPQ)~<2$̓C̼eeϟ71߱\җanF\Y^ǩ C6_#F_Fʜ2F! ?U= +2u"d0G;nZwxvͥc'Sa,9vBOk}o?o?7C/_#.#opg$$c֘-g\fND,4w8JJё191>8 ZveǭeӜG(G.O4yʿh;NٵumEbfGطY/.lļŸEPOk߯z-}wMD!-yEj ?in "!J=$b!` 1FVeQNmRt&gfZ"^ѱC[ks3{V@OXW$${"zT+{߾сp Kl}[c!02ZuJkw aחٛ?/DtJ2P"1òPN5=狠4GtI^c_>]!Y.ڏYwGzeb>I''XQ#vnߐZ. oXP_Th D43D)|Ǽ/*:(x7m^[j\Ԃ``f <\`Pv|d3~C1h9E3=PN-.#wu{Gl7mSu:7,2nvC5 m\ ek^^RH,Q$ 84>zQv8:*7/O>5FY$ ('V*_ S$,"1b!%y`mΉc^is9i1%07.-NRkQiԽ}oJ~ 0sUS9C[z`+\ψҦitep\bBLOh3fs4kk c$ Ĕh4E1Q XN_z<BHJ1v%O ܪtn1Y=F|yD5{nzLm=?"SOX}8 ݦ1[V5){:X'AEKIKy|W (gn>G٭rv{ n#n_֙y %90gxřYD88PT|0tC4$[ױle׋+CrǷ=W.{/שt\sF+dVki>^F Vlt+;[ho0:EėKKy&˅J1DFAThήVԎv {hÙSmKn9͔ /W)?pDD!aʍx~}oWi.ǺB^yYòRp,f>Zmco{J&k2E-mWDV䗢^$( BYj9eMۦb)9Lz ٜ.q6`B'/n+|>Km " xw2@w(ܱksjd[z[ѷM")$$b>$9]ͦ~p/|zщh/ 乬6^^²JH_=wx q9s^=)q.r#B {(%)c)I,m]-"b?~ cWhU{Qu`Yӻ m秵V~{TœػnK)rŽp9X8U_6^z̘_$)!"HHULmvu6[ޱ* @\7]6kHk HdfD'_d+[qs,8!0 1zŖbtJKnh$2eX"m0 [؏J.JVWmfwg@suwpuW _Z,N""`$;K̘+.۬8{ҨoU{˖8ΐ= ﳯ)B;v.;]:A$B@SB7sDDB@7.6?Rff>:jRع5xse_yÐ%.)!$@DBD HD :lGZvHm",.[ 2s8,1rX>>SHC(W2$D&yuF01T?Gq# Bu-)m ^@Tjg=:@WtC7BgFg<ё9imF)ڪBuo73 H\`,y qIqaM0!8%68wכ؁Dº˦9zHRC,B?IT?a"2K A"I@ d!esfo j1Q'N>p"Kʐ"nu(Q,ƀq"\ \ ("Wx.O0>١W=ABmeK5Aa:슠QuV~QoMbP1% g {{2.#Qye³ b!i'ƽnNu Li%~[뫵6S@f1F_>p[ʳ2}t@d0ps70D>E3[` :98@:ەm~s_|T}_ m2~Bq]&e't ֝G9yk?h#2g qe8T7QМN%_808&r3$'C !Ĭ$tTធ_.A>]Ovdz.. /u}-//K_ 6z ;}c۷9{ǘmp$F @ Oɕ':.ӹ& q>3uchSGoCA+qx*h9:eĤ)B\9."9CTw9Pv8}idYC@L1bJ"J@S/2![h9Mgwhjֹ56sCĸrrD>ebJx1Z]U݌ m'_P{nesڥ7;M/_]HbyʺƼpH$锿536{xkPzSUcĔ|͖)pʧL@ؒ1C!P mXP}h6I1eu'zY#ҿI*eVvf%g xr+0?ܵ?_iCpz6՝[]84v'"%e[\2^9 ƘubPxT:v8 9ٖg3Os%:1~)?( +!Cr 9Q|LS:N˘BX- %yŶE@UtÍ~Q(f+lz  $^xf+T3G~.̺;~)g{R8~P)4i =Ǿc]SΎ20icx/*#Jx`[\R[Hr12#?d 0ǥ*?E$D b!̜5GL,)q)қoK[Ȗݕj]z-w9p쳖1zש9}oT'':lfUCFȗmnKɩŖ  ;KLhv~9fPoTK(NISS]ZJ#% ƅb$ gRp:HB0sl9ZNȄr]o` %dΫUK +@ c~eO^Wmή:Ny1{n0?aW'?>0hVwU߮[sOևxmLkI8~(ns6Gz`9bmP*vAYe,Y *q9Y%@W·ńiє{pMy*"ѽ9),ȫs_V}}I&rxʱVЪc6i:tA:iOn/T6USGִ5+wn;oT .歅i|DI2 09jYaTnEZJzre.y!$ %2 ! E?2x3Ր0%Gv,gBrC0즓 ,d]6[yy!SamIdfb:"s aN%|-gj:#!}nsz3 nFV1ڧtګjRm*1D\ciBE":`O1l}8s4u)#كXXZܨL%2G2u˦)QzjuסT /$73[_#cfju6*έqΥB\ː!SHG '^O_ul4.֤H)hL#D bfIXb@480#1͜GJ=%Y:P'21625da7k;m+\+cm]yg;0á<_<* U7tPZ,E;n`역iR\c\RHI# ?Oe9C[֤7ijRI'"N".q rn- Yè=!8!p iy3]Dgq5k{/>kC5A (dZ6Hy\^ܖ!,xT] 7-bՏꙒ~fj:7 [ZQvc9|˪c!CX$1-!SYԍ9ҵ6[8;!mQV ;)D )E81?/}{# &_^zQGR ˬu2/m{yyga8pzkS|`ɣ?3'zA G`TnmiCᴁ>5eB1K\)&r>r 5 Suؘ:;N}Hػ0"FN*8d, _2~@DbD1$jb=ڌ>ڬ;)$żq^5]ulާ*E#nl|~H= 'zH@9sGvsѭ Zq/:;&m:cpZ%.3+cl5yW IDAT{0ƨ)ƀ!S"A( q"" $"9C9 sm=KްW}s Cf^f^4/3^v,j^fo\rw0{N=aWqAClN=]JR(W*HK5[̘2s ؙ\O$k}sZV}"bZ8X rI8 nAN8U"3א9ՠOC⣢}#KvIh9 bKֹuoz6Ou3|bV~) 4 6GzP-X Ƒ Q%x#KF @t.9[ fѸU s`}YgL# b "IpV3WDX0$at[#&lJmԦPI":Su 8AǍ[^wf6Qӳ_A;apitxk ]jg +l-R "q%1l1lOSeUgmsozPq~Xk49'N+HHH!Z f@<.vMGX?̧z>&sz보s@`"&bB$"t8BB|8~> S4.۪!LdSm\csvi`g)fiFA`K\ƭsm::mė'ᆲ s4ukSJ:h4) S!k2ρ3Uw3ӧ8 ILV (@GԂs嘈řC%Y^f^,/nxX:*u`ڏ>ZT`& bZ5X7 ɥQ5n^uT7s5g|஧(>ۘGX0m%GW $D?*GUtGw5y<'ot@srpPw5j2{18dr yi!hH ޵IncI L?7=Ȯj;;W(d-tDxxGfENaf  +橫*uc ;Z[;cj4q4E1xppE)-Ҹi# :a.\B Ϻl-Pc=<|2Hy[ݐDVԧڦڴN<=qaO+ezFx'7.ְ,&AiLYKp)X:$rkپa (,Eu:Ӽw-8hO. Ks{kBH>Ͼ.,e2VzuD[ɝ>/V5V&\^A潏e.GEfb4BRHBFL'NDY֊Egw )i2e\!ZEvLؿO TIBsB\:CND KseQzfZL/z|=g_{DKrdiR|j j8H;Sk־.f->`o)ͼ@z絕u˺r |k.^Ԫ|7tJ'7m\Y,\.eD)tyZPf-eR.ʲ5z"("=ew6c3FwX ,%u U+X5!t祔b79]C)6v%AD\RRKӮҹ<|B[&i "HN1b"T]ZMɺw H;ݭe\F\6c&L.IYTE02LYEdd"GX3NnRh*@hG2y׎:glAs&S"12ɊVjVէڔ;čn}DʤEsyI=H:Ú{~7o7.1/(l]7_6C\পrZP*EdI1#Fc?zF8R(S)#w6oS 0q="*ZHKz-9)UM76g3r#73,beB:XV*OsmE}ݽj?X)W-<!)=̚y3_mr%'tW`4c{L$N\w:͙$Dhmp772CND)U 0YC|I}Jw:᫊¤2 PsvY+:]iXWCLǕ%IQw) 3 羪u-#,ˣ8XZ~ZRҼxRXu]>je\Ѭw:4_zx2T}yTe2r$yD$-;y8B5SPU&):`w'4P( IQgZDwf:E )AD0 ecR+O{"b4%3 b&>PLK]Ft=\OsOՂZ[o)("A;O{ʛv5lfaW^eYYf-^ԴZ$k!-Qfr+ Ndp˰-{G&Eu4c3p koEtp ts$0TDZCeR93;znjmd-RK Ju& ajf9fs;bq:K|D{Y[ӻK[/-n-ATRQR ~nnCCl &U]7MY!o1ϡdRj( N5Xztz=oKSDNR$ïQ!x3z~E?a҈15n ݽI\+M՚( 7lVNK7g|"$锈d"p6(uʦ&ܘ3"߅DEf ղѨ<{,aZ0k%QZdJD\)fd=څ†8Q8%7M2 ]>eXCu6Gk.d̄(O%"%KAQVҹ{_e-LXZj$9d̶`$KS2X俵Jʯ]; l(J\j)Y˙mѯp\JJ PZI :1;2JZBKZQpxƲm3fOMrO:q<-QfdfEXZsqW2\ "e5n@TD[BOZanP${FMܥ7 &"EPR&0Qrrn W q7ILN0cߑ"q[N $!$3D])7dR<Kflsh 3TihqcRH$q‡ʖi2?|L{ƣ}%(*\p@r)$&3)2(,'9I8LIB@ \@`k-rtevϊ 0(#Ҝznc >VyXDH5X\DpQ"t0xK, tܔNNĘnt?*GM) |+􀻌#̨,Pތ7&2ܴp-ˆcS%ޝ/緙yf t,,ĽdJ0FPa")@*01 1LC9>|?cF;GHDEB8!$Hq3CL[XC2@yFKWAȷf G71,`BTCKG#| A#v~ !")>")=zwQpNa3,)) )Cia-FD<WizeP Ӗ!  {kv 0m0+9C]aBz(#o{peAzrxwFJQ<`VqN#C|l %IY**-W0SwwG6Œ>kx I2~@ff%T33")"o)t o.ߚ l Q:Q֨x`Sg:!8G4"'E8p5KF` Um JF\zLhrn\@l`J&.m;2y`;:7XHMP$(:31%@H =R Dݺ㕑OI~Wm<o!]ÛV6yEN[2)3+b؄poJ((~}n ^*˽h2ڼwxA/,bP*B 6cE6,qNJG \b..s 6%R# P*B Xr%| ?1 Pp$K5{L•v dSJ]]fpkeQvR 7^Dc9r*'S#"uL IDAT|%*ͨy!* *U)%PT7$yXi^|8gT *R8hy"O0l6 <8G yYz/&<ǠX 'IF18@ۻ(WMb; [$X %]Gw1x1ܥOy$2dWn\ R$V17Þ[Fh3x'RfQ Ǐt[úe8BL.ZGbx@g]ƋRa􀕯)B7d]x }-|bM\soGV8I,@T!Lt%nbp #˃|Y2<`.K´^T]dJ!-Y,K, 0&0gU2RT1h5ļ$ Qok~plAL@:0mZp *l|aJ2KE]RGodF-܇'æ}·5ol5'~LM[2ĵ,*ܫXQ2G7ޭ_]кc1E֊R04,% Oz2x_/7k{gR["&۰d#2<\hؾnd1(Z,l&Sa=h|||r/Wզj  #IVkrCpN0VfbDzXX˾r޵um&QsNs:V.6H(ADpDo2y1jR s'pq8Fح@]囱wTH.YJZQfgewXg3qc3 3bV }6|ξi{xXn&JﴮC2(DךRX CGʇnVYW].|Ÿ9 Tϵk "Vb[0m>G|H&#p?(LČ)|*[f:9>N:`ET&Co.jZo3Z֣s8W `d@_}1AtJ0of-zֹ5 n`yu fe(wk|W6R4R] ʬe'RRD "7]?o%NNI;xx9'En)o/o7_}t RLŵD7nSqxGbS޾\t:gLO}UN[3JZ #),lvYr:HsH 1ԉu:z_B7?똒eI`vr-$tE}g3"3ƫ“T5)-3 T'+." c7KTKzo[0+?[N#Ԉj֬kIoX;=0Q*0pHs'o -r>K?rLzz^ET.3Ke)i3僧* z_rWȵbҒHB@tiYgxʇ_ySC5).礚)ݪ4˾x-#"~]coJ"`H2;z:d]iY7,1UŹP)؈VLۙWYt*G>]hmRq89=M,2 VNZPJR3i/XJ36Kh-~'#m0R ]TRKd&Y ֆֹ-6_ͽ'yFdĵ ⑂ps[LOa-v>녗+ԕS'~ʌ}ȧc9Wx%2vr>O64e>42 %~r-M}xw3l̈́BZHB1h7a:բs+X )"Q|lVPp%EF"<ÜDzpdekVkjv{2f)FM$ -%"@d,.Na*#CWbi:yfȷ@^K" Z풶rETDP-9Q"NDdVi.t |e7]DNЙHFW#gʟx~m-$4TP&e.YO8O*.Sbz^ȭۺ* R7d3KP KAX$"k)Z ;,$;ZT0/yx׃Q;{[&h2= HIՋ9` IRnǶmIjqy-ryjW.??u$GD) \΅< .?>,?|TcNZ窥BY{n%1myf2rzU )XuJ!-Tg|h?wE'\tT35ڲu|ZnERPjjZ{<4;Kų(Jo{Xgj;"Ї/Ak^vKB\>(תQH^tiƴKL[Fg[p~-c="E)ylѴeWtQTף F[jUTTiRyaN|@gb&nr@P\U$'J}Z'%dND~9R{&e__-f㽹i?<1Խy Ch-5OG^-O8^*x]2q"(Jl}K[ʧ})KdH;yzSN{/;|꾔\3HVȒH(\X'ѹDey j_γ/YݖX0oI̼e$! "(r݅V糳gf\^/^zX9tfbq^:73JB#F뒗3Ϻ8tByB̻jP E[i]qx'_46|a:Pu>:W(AVߞn537&sj:Ӽi:~~'ǣ ^.t)SoK%( b84d0NS}u7)~Dܜ[ӫ.'mKoK:IR6-Cۛ W}vEn,v>z#3xQ4M#Dyw^OoӉAgÇuXd=ϘR,|-spѲyoiצʻ=yΞS'=rҖY2le˳ QZr6OtPP 9r[:Ys[X+Ձ@bJLˈۂuRKf(~xi*&  rzݽ~*?RdK~/vm7I_EY$Rpx<Xn,ѰHUx:2lӞWW/;帶ږIRHƘVZ]֩sg2Hxr^t]Жn֛6ֈp#uOpT{_ۺrI.rOd]*uҘHFYϧx,?BKoP*c/şL^뤪<3[|C>hp$9E `g{~Akuz?,t\ΧXq]~L)Rt #c_DT+%ThQR2t4];wK_[nH,׽eI-2(#¢u֖u9-SY "ثU=-5DuU?=gӇ?N??SuZXgSCGл-MLY^[YK@7rQ8?JmR0% 1 &NIѨEˤl>mLk'β&@AiLѩLC'"}BD!@G65%/<|Tg_8}4OC#Ӱ"x*Y*?:M;>LXp6""Z|}ჯ é3)OC2Y'eE2p2-p>D5A\r=ףr:ӱy:k[EzMS797S1>ج3T7aCpxQAd#Ĉtt>]P(qwD\tiM7{G;i:tjucvՆ (: #LTA-l3̻g߷| OLD9U^0y9kF[rr"6lZwPPFXm+8^pNO,:TSdAyKnBq=`{i7XPeD`2:ɼ! /ODz:]p:N9_tzj22%4Y VQ4^JkAPE=P 9wRI &\-g+n.rOӺr*}<paܕDѧNH9z_zYӌ)3oAUO$V*quw=DZH Gy aF=~q 1W5UM&@ ASLH"pS tWң%#fEiH"Cݮo(e};<:zI&$)vЎk mWն\aE8T!,vvCf3d(KC %dV,&8&>"sf|u_10w֓9Pd&N;v/}Wu_ UXO\kWGUcA#@1b!GU✲$< 1!1VaZzw VlGQ ^SS'=ִY:lZ[JA`8ڶbw0 s-d2vf͌&3?=Uc!)8ݩ~Jq㉸=o Pޢ0sVEH4 R Ħ Y\?P? &F3 הˆ̬3! 3:3Dq^"v~0*8?&g qa@` j"R~C/onna.]eC8D*.XݫMKaVrdR|V[_CD3 uz@""`fBfF5&YYE!{@qou4;:r;D0u۸튮_jC&0St-1ë]ޖ КƖ38tMdg.ƺ!:8 ^;hϷmVPȼ*ќ04B#34@04SU":r8hו:6vk۽WQ+: @QHb:,J`԰DΗy9ͦ0=M[85N Fr*`?y@Mz$#W6I IáOQ;趘/jpMHܶ'XsKՙڀ@ m>*c0>I#ۿ`m~;R4 ":VS.]Ư:^_͍@*ޚYQn5:UL0Ja 94 = @l*`%ʹ0)XeSCE)t@R ;ҡq: @U JC][ nn+|qoUd).j!PC RV] w6x"gLg6YMob3Q7|?`v0{~D*NVANv$WWj$y΅bH=hQ0'(8  Lopl_ğaAQkfE3He(NWUnv/50̦" "7}eCl n֓,xzNӥf\UU[34?@O V~K❥w7Qb+@@M ?:CE_P:]Ml˭Jzc={ѣhCEL| IDAT<D31 k$:.SP<:%Y 66utV[W+&Ҟ=jUCtVrAO]&gSk4=硝0R$p|?i9NH'L9o5# t@.M[I!aqFEr\{la_r 9(CV=9fbC$D#"DG ?&*d1 23C$C#3CK/%IQ}٬_+Y>G3&q2aZ dz6<$uX%]{Em dDv|Ir1 W'BtAZ`A϶κe )O]HGGn5Sqq:!o)[1;cC2D0PU%羓ax uu onpwCSհo[0}[<rtVs{Hgd~.m~'qvgU4T;YޝApIIm$4$f$4$Y\BT`&C֢hRZ ٣V8q1~*0?w YӢilW^ݦwq 7k\\G h>U`6EX&Ntq&dZ//|zb였m{uzmf0 z ZqE0xI m(n=RAJRPIɔrg1; LGyz((qw@5=wyuXݾެ _=Q.x02lKNjCB/qjg,dꦊG"$3#uT{hܞ V~Fq,K#{B%h 0jr`&M9S@]oI2] -XM\!0#⽕]GV XK)lkY[/ M>2C7zF~.dr|Ӧԍ̛Y8!8@0ڽb~i`7zp?sQO1Sv`5vqz(cal[]E4 yH^8k(dquQWeoޘq82+u^[|pObx(,.m6n ۵\-vO`{L:YzMpVE=WVQ"Phc&SNrX͵.oVכpke&f&C( \ K] ;[LZ:{ՙNn6os?2D1H!ShFx{;zL1wf!јԿ-93P24DTcH#Jd*\p-p(9,@!|7x>ug~9dJRRC7̆2XhcA5CEff3A "*Dw8Uu)V臞A)?J>lusVEV[zy[ L-6@٬hFF@ La;x=: {i2ɬH2MYT،""5 #H*3P4L& Ґ]t]ݬfe owA٫9153dgٹTä)Iy(Mt6Lge:OnzVMθTuB3{<3F`?:қD|xn6HsEtܴB@qS1,tbJp5TD+^q|/9tz^Ґ$ђ$Kx)bUGd1pC DDfVDEw2tl}+p+}rJȢ]_aF< mr>m=IyntIjg.>T3KZoTCe0rUabע >u_ +HD`l6pa !r\&1F.q%Pd :uhѹ.um*I0 2OlXĈˊDdf]+fIEDJN}W[~vqruū&WA-& q\/pǺ.2Ld^7XMy±fx|?gvჄoH@H#GՉ @fՅĦ^ɫ!~%sʸP!@Sif %(j! 숑L__?$"2N)z%:&?&lV)9L.2 e_t7t4O0]t"LǦ B`"Bq$=o+LC%<ءvZu Fx!0 !"'.U}ebn [x~ςi`]尗Rh\ 9U%Rf q~?e)I!u-5VzܭnETCEX,Ά45N4Yo"TH) c">\}gpn60C2$ 3p %t9qAK)SrYqjʛA= ̂]v]S2(v hOV;`Bw`!#"""? ۝t'7n}+Ͽ\Ny0>/gӏԴiT' 7[Roz|9H ?26=ȃStъGNЉ{"3"28 T̈Dd]fT;#@@*fy)51^%acǕWj/7kh"l]_^;<2mw U`逩w]YJ0жۍͶ0ΰni1NB;c_xx&kXww`'{ V~hT YEr"LTd!:C#02+cDo-n6t2“Ȉ% TR!ZB@qW!4q>lVy>ًvF f:Z< n09vzuYJ'ٯ].4?[:HXbU0x}EqT -?b#ݫW₄0MO2C۶\,i6:ϖ7-7o[ y\ "N3C|`z0Sp('XE TDTd:nnV޹ŮcuC18!TmN{|'`qRW苧\G|.hҫ\}ӹz\=2B1)۱!\OE,ƩJw4 fLxzO+y5`!84GdUhE`WVTՀR")nHY餉I3 k7XŰw6> LЩ eOHg9\Ggwwe@ &ssU5;My"G؎~uaOCdJb\J)d 3&0Y#h::bݠ{#;M @a%oZLAd9'X39X0`f]׭Vz>tݤHUU(0<6}CgQnGc5 )c6  㒪PsЛT-!l2REChE%BRrU7$SWO];\d=-A.N֒Q<Г+d̈4 9?rÐ9\@9`~x E16וfk,:V]`Df$ zR䑃Um)%r U1 5$D3'ӇZ8U>DՇʇQqn\v⫖{SM <8ѠZ?}Ff9!b˗W}ߗ,9D|9u`D0 93=*bz:ϵ_Uf&`ffj{4NrsMjDz4D "c3Jɒ+m·c 64"2qhCo[!PCU!1331׽;z7*磚ݙc">{vO"DO^MUc N+Op;jTaÛ:X1Ө" ƅȯe^zLD@\`B9g3EC`f}dݭ_k^ H9~fc1#iTĜ+4;#vrweBs‘d3sNƘj-vAHDUU#&OGp.tyjo;1^~;whr߈^bY~7!89WU!m`zg:O”dxx}XT%D$hG (12)g|~oi7xVފȬ";SEcy |@D@aLq{X7G+JQHf#֫ȳm; ɟNG"}bw\} [wh4xO#1y@DfeFD+FLDlfj`?("Y"1Ԫ S/_>yrX,J)!1o33շ/} O+?sX旋S^;?h0C"D`CdqTߌV?~𰺌DfBDfpٟV PFY{>ƽ*"H̎]9jv0ER0+ʼ4>E!D|5B-ת_/QaMLTf7]PSSD39_(g|Ha+(ƈȇðC. FpfV{Lйu^DTTQDFQ98 [P;WgaV?ċw& W/5DŽ@yͱ w_'ڝDךsAI1炈y)gaH8 ȉ;<{wŲW V~u)k*_=07|$r.ߦ~(9g~uG~htý{DD7O+؎^^4Pfbalx??v/q)U8Ccgޡ 7(+*O{ YJ ">J|~yh4u,uk˻!8yB:6ÿCxo/]x˽׼ =?Khf^H;? V~u> ^^M,T`|q7;!$ӕoV/|Ao''G֧~_{qtۛ'jm/z \7$o>EMx' V~fykq62^PmL#_0cC[7l_='=#={gZFuyٽɧޟ,L 70߯.3֓>:c =xq2~%53$}tA8Z Ott%HHNUSJppۭLDTALMqﻮ33T&+T TETHDDD Dz8 W4iz}{;v} fR"2j L `X9%],z]UTs.8~.sco~i V 7.9g`󁛦u%,`x)")!vR@.XUU]͚󳳪JQD5].h9eEPqT(hTعc*Ebz? )uCrHy}UUU ZBpl>l6JH 8`i& KI<1}}3$fw!}JECN@0cc|ԁЬҨ1TDT2h{9K]M_^ sfÐá*D(NzCY}v̪{?L˅jɣ[?a V a UQQd!/~lT!9';? 6])qGG}TW{o@De3}3ܐ8 YC;dIEZ=rʐ7>wD̪ݗ\⣧=hEz@w(9h)Iau]!`0noz"޵V ":lK$WG/>Y3$)5-wZF߾x9(@}fs8{=C1BO*2WWf߬n>Xvbq 0<\m}ţgYCpHR ɘq_JQUBd3<;$3,9䒆u P^K"̜p{{u:t}BdS"u53 M3G~g=S6T IDAT@KaR^珪BiV V~+9R~'W7UL& !dDD vCR1sm\>z PUL1CN9Ñ&_?S.Ť%"FdD4$t ;/E3u7h6NvQORhgl^xɣ๎A]} V/<)>TsDF"! |׳{hW HjͿ~t"b=3C66%"2{vիo} rw!VHNDg&6*,Cd <S7_={:k*efc;2b$A I]~vE;=ջC 0*" #&PFCL{_~w񓤥ƦdK&SV}^HaҠe LhPS|{UH4b'>+?o4ltiQC.=~C*K< fcޓTU$Cs!W>ԏ|RͷϾK{`uhd2`` `(nkD,"E}{v>lrL,(!;Q+"H^TL뛮7mU{b3@AЬ1FQ t>NvS6%tv{@3Tb.*)%31)Αv~Lgg߾:1*?Օ75FRʘc)1 s}ʒ #v(njѳH)V}]cU})j!֏m|x^2z?DM@U9DDv+э1C.*eH1"bn]'˳˗?Mba0s!3dD,ێ Sfճo>nϒ!E( #j51Q6Eq2X}{l\jލ45B23")f:w UOÐ}Lf)DUEr!=cT+`rfsK7_XL&.fUpL&Ef!LKV3mJy@$353e(!3Cq.ufRTu r?VOʏ+cʽ=Qa4/7?C Sb%0XNnެoKfHjQk#UӿW] CFD眪:JQO8FW7ۗqi]צ%GӰߗzls{/`*~'g d=8UUŗ߼Tdc"xr i+iZ_늂3 a=nΊ P2;?ţj?OϟfJ}Ji4W-̖A$KR0ag<(fNnhfVR" @U4 !ZJ=Rw)Z뺛WR60t11I fFfNS0 !8asTji!3nwWWϛoObمMJQGY*S]0e9+ %WmL)mַW//QB⼩'Mן/Cfeݫo_g ^8~< 1QR<+Kڙ9GN*"XI4 f4ʹiC31i("ͦg[ɗWIɲh<)w#";D9']nǀc,<)uBpR2@+? & *`82!l7~?nG.u=͚ !<蓪˶}T"4n^{oܼy pPN )}~yy|^Uq@F8,Krjfh9t[sn7MJIhՈȻXUUs|8lXΣg2M|ylgmeYM\=/nTD>]<~ѣ'~/v`c)}.C+'SNI V~0lԛ3#9)ԩC5n^ӟ?˗áViDLCAOw?'&rKB&L|_||>B,(cRF>n]9O>%#þ>>[nnw/p4sm[fu٬Pmoo8V5> o^\ӺU5ֹ'9@U訤~zy7\>ٗGz³ !Py!3l駿gX^z̎ݓܮx4l2z6>MJݳozۧ~ȟ}{mrjf)et9SU"H/QRSrc+IVB}IY__߯9a_~1L 9UUsvu}ǟ<}я1fubM֞{Q"pXL$_ {+!`u]տ_gujR?=[.擶il\(jl>_6O>z̃zW!W/8mÌU9$CAv$" ŷ<{˫('cs5,: YJ^}տ|?ǟGjI~8t@\nֻ㳶g<_NGU}ѦWG"I̬[du̪'OM*5(D @ɓ>l8k'3*x&jdXk{PJ~])EKyuosU7M3ggg$ov6CRYvuə"R9K+Gf%4ܳHDXw8[3lfzu{SL}i'.>ITu] {S]ovM\ Ų&!)pѣTEU,]aʹ1E M[-v/"Tt>pӮOɰ,뢨D?yxRԻd ({ iǗU¦r,|4Mcmu2{a嗝)v֖qo?ׯl6Mǣ`ԥJc [[׵+nZm6o 1w`kF{h8__-eԥSղ,! b6u+B>߾y[Λ>}<~t9]=qEׅ՛o^b<5&;kժmOŃYaMbH8$oOrUU |lj:TeYUEQ43%S뻮M].6u?])1ݱu޾UE4<lEa/.&EU.3࣫; JM " Fl_Ea3Wr{5-ʪafг1V~Avow8)%뜪vD{d2Ma5UmqUUR1qY£GO^~OLTqFpmr1\>3"r~23hvXV +ʲjnnn6-[z߽y񇇗ˇ?a07UU*#3z^nvh0.Űr+-\*k 2"@IUcjˋި,˲t#c>ψHUB}XWu|4nu8u_[V]v]J  ƪ(1]o=#4 ".K7GWA9Cy -@5eFMYs.UΰK, J%5|!!rL|Ǐ^6ڈ@I$[u])~Ç޿}ӳ̗M1$y!kr&$ں(v勻OHd_W_^\C(RJ4Ml:]N~z[ulrwwh<ͧe٬jd$uuSPZR4P*g$@xƘa}LrWbLnY~Pzt*&pr6&@"[V&PU뺷.:UAPX+l錛M.`k @:Y5!|v<yruu5N~U5_IPEGӮ?ۺ?S J ^nx}SY{A $Dd攔BČdYp$FBa3:w7wZTH - ~lۭvK0"* lG>#)TT5Ka͟^^g(9߀b̙ e6fd)<9i޻DM~VUU$E"+{?ϗ"r2E2U$B۷vtzuuӇG,aUT`f"[.>\E=VB@d_NG!jAD&PUATnĆ $"#2c~ c4Aڪ)Ǐ>%JBkeӶqXVݶK}`pA""ulF4s8t?_9y6G~vgϮ^3;ɱՙs/SkmT^SJyB8 ne^m!%(jD@wl@ e2춛vO"_~u8 kl8/vQxQ.OOH8Q7>3:L4)D\cnv]\ :f yz^mV1|q9^m YG>\! \aOUU2VUChjcv*"* `$` "!̒ bc5x0s\%[l5^|_D^} " +?n "v /TIrI"Ϫ;FwHcBǼwn[owz\Ej٬~  D%j$5&eln!Ig[.ղ !bTq~ALGB&1].b'El#N'e̚Z(\l7fڶo3f!:go{xSt.Uΰ T9`gۢcp/c^y( ![ EU\=c˷o!hBT"*o}P4HJVOPJ؂m)bMIH}ozn+TmQ6ȬϛDGA!`s2 (Bȓ0{S+w|ΰW/:6_ѱʥЩBԱ|?5>'Ud,wHTmT5FOD6O:cb5!34!zAW\5Hz`Ra 1cA$ i>_~qFuĺ{"ւ`0԰U"*Lyo$ *"*i$98T>|#gL9 _W9gX9e@0,J^AFդ r deQzSE`$,&!iJ I:/"0eQQ.;J*bJ>`ɄŞ?n^o\2Wv׭+cv$BF޷vbO)~"rH?[" IDAT 0ΊTB=]GTYa8/s$0$F4LJ(BqLFCstg'm"&9 4C=fR$HX1 D`Cz"1%A5eѳX=xŔ2DNn͕SH?5)(QVaUEY1xI`cFB?\/?\o'>Zv]W֥/oEvޮ)x$Y/)*$ι:?uY!YKm* {/G9^r$bbfA9R)j $"B H (cgaV~q?˜Eɫ@gÀ-]U8VDsIѰ56%q?&b695JL)&QQVD0/%= IQDe, LIQB1SvrՒq+n2I~֫5X'_`@5 >~wYv?+BsZ-J$WDYŸ:Vm4}A|_5cGPF@I)Ds1Ho(РAb6xI@D$2e @NDrjJTSݪ+M"2r>+ jW]H͠ N|bƏ_xz5_/ݺk(dЫ˲pv,zFrl\i*N@_EJYrvPR%ۅ^tgQ/ND) _WCWD. IcbPUIDI BPQU&AA%HJ )(hR "%A"Tك–{5 @IqozIA pB~^ݾzW~]Gt2{x:UQEAPw^[obwkʧ%*'.%_}dg3} ݮ]]yP({S|s بkSB yM^C]E!ybP$dAҵДQ*F!6.HoXw>D˟~ONͳǏ~`ثZ6 TPݧ:Dpjds(^Ő) fs}A:y3̜az0 (-,cG !Ө {u }F2AJ &nf B nu@iHDT0y(o@YX{GR8+AQj}XM>J,/v Q]]M^]>}bk%f @6h|O$؛#ⷁ%ϣgޭ'>zxZk˲\w)kI$E11ɨ` z*u"Rf @4 ٗK -h4bDIRV lA H o e2]fW.&I+˂䓀 )03hJIc"O_:Y`ƿo_̦_Viv*P%$8CN$ qZe_loTd~Fn0=xpt<-VoP$K[; `BJ$ac2NU ɋYG Ie揄8gJ u#9V~XwD,'x#GXG( "ʉoI ,CJI1ua? '13A{G2wKTJ1ZR{}g&CĄ@L^7 ʲar達@rKIgʟ K?!gGC7A ;71&P=9 dJj벩"UQÜU{)olN+zaH!p3sY2 겪\aɒR0M/n9폤Ȩ'ST/y%B|s&Tΰw+c Z Yy7 )H9]rbcmumh&&F@IٴI y{d̠("? (#dѪ)H BLD5Eꫪ*²a$$G`T`ȪcR"D7Q!€$tϰwbjOD?/V~|ʞbKm۶ (eYu/%]]H`H3=%@jOQҚAn/Bf]`2RQYoĆI  lj4!H4qJ:Qֆ)%Bv~Z䠯>VR$:ڞa_|v"TTfPHP̷=.>_|d"{~!4lG{izxVΗ6ȇRCE_eUT{t^{kKoRhozP%(R'iD` !Ϩ :."S!״mm%Хcrr9T{ιՊj"S (Ƥ @"@dBس0U51(j0?G"1˙2AꘓDĮHBluev[kH?p/__oOZv X(*2S{A. O@P:*I~I% cPUU|g$q8 mSvEilZ# !YYJh[n 8û0k`W5MųYwaƢt&ȋMITQ^h;%3 B˲>mQϩj iL9Y*"QȰ~]NgUa[EdM!)@JDŽɠv:)ꋂ1zQ_nC͇x2cb kIҐF0H6Z&)j~3 P70HFrF/j1 hzu!eĜ0EvfgEQqͶ VOjȚ b^V c{Othd瓙Y\.kK6ƈ1(8R""mJ"lVh_ϨYDDcR DhXɢV3|/*g㫞_Qsy]Zkxa.@rL"6x2)nveU7l(fZ6HrJeZQuImlv~XxLI YT)@u]5Z57=37wu>~^.1F`"0VZR|#Arw{)*<ﺝ+|>nYY 1p7 ]<K)eFyPP׵1wz(/LaS8FQRYY3qx>_>@D$ɊĔq[O3.A7EAp$@+cLI1bԫ^\QV8d~Zl6%]z 9Dt>) 1 !z·IRbɃhJJj&d}qΨBPFn>(2[W&]!iaLa$R7Rn r]}{^3Zj게Qڶ[,Dc6X[EAdTQˇx3rH]׃HDE$'@!@D(1$f{)ΰK0 *)آJ*Y-ҏ7Zaj 9Tx*1G bX/MOCE9=L{֩+{0ǃ(*"J.bR [_[b7˪#1Rxxлm_M7SJftq,^Li C,/4!B}Lɲ)dCZ-o>7rΥ2(ٞtE]ݶ&h:-w7M銦Ou]Z-fYCN$y8_) g~ '3ُN;VE@DF$҆2C9lA^nZiȶGEQ7n>ܔf3s}ehכnmmشm@ΖuSx!"SBz SjWw6<Zu?Ŭ`Pg Z5&*(ծm?y~ں6m>lwj[`92{\F,gɻO&[zEhf4=*rq>_~ 3k<>_ +Ġ `HRc"D@5 $%)d&I͓/~*)nC4_-u Q;g`IvmcbحV t=:S܄qmӔӲ.a&"&"kwq1$xQ!vM7MkJc`%6DڵQnnnkFE5.2nSvK@)J{u,˲, "gKsUD%b~=M^|iuوHagvXW+LUU,gXZ`@BDBtI8.[= |tɆYa ,KFLM}+lweUQ7m۝^܁(Q# ;HJEAES| wn" )x4'?uU,fvVu3EBD?ݬC蜝$Dl6lEfl^Z@1H w+AǏ_u1 82& lT_7/!Xfx2vo>{:% "[xJk/// +}"xĔ3LAIx)@"ug<PDU5IRH\cTB\ÐI@c I1J]Arln?YK|Em 1- Rc*RJ)&%BCF@CR[ؘh烵4զ(?}|ܬw](.v6fǹ]zjk\Nx1^bKu]o7*ܠ0I6K)0ږE= !>c1h ݻ߯7|:=zih= !lnbra J A.^cݮ.JArwx|5D$ðZqr޺""J-E !& Pi}|g˵_X̐RJ00!=Te<|IA$%Mea/ݶ7EȆ0(SQƽ-(Zx^߽]-h4y@d44&v)xvNlDǘ:UdbTh uۄn*R6ooz/s Q"*s@(Q/!{7/N_=Ckn_<#9{y9[GDTb+SҮO+so$] LӋIvϽ2MV~9[UC(OS`DJb> N/?, U3 *TaZ",p0lB tI{[v;U1ւ:r1$TRL!Ԑb[_Oѣ=gdX׵sNDnlȈ9XZPS Z]Nn-֋~u=_R$Ru͠1T !w6'v" +? SJ\Ca,ATDg8DJYʮ!OQ{[;P5EoL(ZFks7x%hե1߿ﶣADDƷAUYؔ!x,*z?~xml׿I,U9KW(KJPi:޾yz4|qZ$*1Hd2h<F6jݫׯ~m]ڧ|=~t`4Eaͻ3X߅p`8.77no!t\ 1`!C1Fn6n ,]Э֋nvl 3oTI1*u !Ů]n( 8 %/O*":fݪZWu!rR`"s9T5H"l2=z$;jZݕUTADS+KlSRP m0 Ln7 ^LFAtU3{%[ )̳'Q$2o_ppcM]u*k-!*hL1ЅvfZl6fX|z\|*N'O~fN)x/EQE3ߐD t<=yxo>|t9}P5!Uo 9^֛fYQի 3rsDDFT@0 c7wnl6z~S_]1eC#bި߻/wwㇷQM!tldQC(h]9˨7o׶ʺ7MQbڭ7f`vehBVjZ-nooW˜Q/t<{ëdZEUTHSvE @!pqn`/^sƳtr "FP mY/7fO>mWt6_=zb4l6)!;՞$sQIWϾn%v4Hv]>ӻo~5v٬;;)gXY'1[d~xsv>xsǦLvPlPUeYUpd<?N&``S k%%h7ÇbޮV̆fd̻蜫jxb, Vjr1N˪,crƑ3TLDDM]dd\^ezu]E/&ߦWR.ˢ1t*[۶^,lvuu5dXSSOLGEiʍdpu9~އ2BQu]eYU`0UU5M3)˒I# ďR @,5 zbrZ6V2e56JY~4x<F^,O_3bdz M9ʱlӛf.1UU]]]fh\Z{Pΰ ! P1$~e۶Y"1L9玷=d~PgXɍL4,8/..sƘ .;V(o_<h4X׻n^a%28fWL]]s0F;voy0}Xۉd:Io-˞n^V+7Ma=4cJ2/rٶx:jivsy:,f: 9aQ(ocp1U^OS۶tUE*1OT.YEށ֒Rޣab( R('JZTqB:tSCsպւPYd,h. 5@=f nu]v;s9UVSNQoRU,VNAK_͠q %7v{2OXN%A&p7̵,ʫэLYu !L*nܼ^9~잺@Sx1kο [PzkEP6䜋17]ʏ2y T8. I0 ey hE>$rd $>NJMI3jѺg݋ n@\.ݦ̢{Zetϵ}lh'ݐ31$vGeH|2ߴb韌n.*f/TEaEaL1ciTxӜ|r-HN\ES4 ^Rʇm "ti0i ^%w ð\.a /C;lHJt&G|mdA5Y8ǘY7psR7!o ]*F:O &HPhABP(V ҊBP(( BP(ߎViEfY9_,H,?j۲oq1VURb^uJ RC(x1=WMEbH/* ZBg?}BIm!UPu]fMB k8H !o=BI<([IENDB`python-muranoclient-1.3.0/muranoclient/data/mpl_logo.png0000664000175000017500000001405213523272253023511 0ustar zuulzuul00000000000000PNG  IHDR,,N~GPLTE$\R>|7uG$K'R!C9w4A;51864%M7/k<{;z;{=|>~3.b-_+Z*Y0g0f1T4O3pLGEB B!D?#H:y=}*X<|>~/c-` >|.a/d2l =y:u ;x&P,]=@?95CBA @~#F$I$K@+[)V8<5'Q4o:v ;w'R {8t8s(T&O&N2?1i ?|(S4n2j5p5o6q7r9u;$J2k76>!E!C%L#G24A13l3m!.tRNSfIDATxkp]Uo#yk_cvpo3QFSpPDDPDjyy<2<, *hC7yXk9'wӜ{kڿML'*`9X`9`9X {ԓ VPX*P/X݈*U1UVT1Y)68ݨEűiUԬfKZVmJezzkcUqPZҍuUq[2ZY'=,DX-, +';Vr:RtXu}S `k9X`)U88S :r @V_Na) `}) 9XS ?8zN}4X 9XS ;zA?jGiay!+ #1Wth)@0g:I4XO 䧭 ,k`ݕ_Ez*z|sDuyn.`=[}"t.+O_VBμRrDuiNN ł>t԰.`ݑSߙpG"_N~L-MK օua{"~:XGw| uAg\"Z;B4Xw_~~:*(`ݚC_Ouk':DuD-E]QM9s ºDu,[ W%Csy̙hd- 8cDuW϶Mfl}F4X_c2g$x9 ,%A'rdud m^\7+6vX`}Nrc)[JZFV:X߶ܕbG_򥩁ַ$ѕ!~!O䎔>2_O~ԳvF*FLhun4vF*zfuIpB$LUT GqnθE-5E٥J5uX1 L16fg=-R,skBIcaezǕ+NQv21c SdM4~6YRj$Hk+M֭֩l/ kSNV0,o`m-$vW²LB24XPؽB|[2-Bz" IXz Y>n!JpXU+8 Q`4gb5هfJ!iC`Nov8 aa8a*TXL7lf=ugTܙTS`7e,%1DX3socm,߻),U%MFZf&pgcH$gY.؟>mMxg5,3M6/vϥP8'o nn.i3&rOILt g<O z,qc1/c:e4WSjw'fne:L}m^LՈ:n G~bX&͐7$cǠH+ q6ZS:6QNvWXG>D̈;Q?`ԈC؜F4%Yc߀ݍ5dc jDsFrFL](-y8F"^DhgqN-* I ;כLj4X$Wªf+ FVআ%ՀؚbetXA w}X+mgyxg& UB3u4ٍkb6/~m&0&ҬlETgB{ UATXIbTf٦o*˜> V&Vj@Z|TbWUUlKk 3Vu"Rժž`UBi#fT&DlWa*w5m`TcըJ]@aD[K*c5d vVlc}U{ P38U|%E6=^㑑fk0-t:Hd 2kA'Aڕ⟓ Saў}sӈ,oe"al hh' : R:g` k' 15!VM. |pB Vta0Y  \K :*¢eǁdbڵڑmĿ`8 C|2s#hl;$Mk8[G0K2FD씬?O3*F HF0]M~pXvȺ q@xC Ei5aŠk,*_ i|u/4 \+hQkXiHk$K U\̒zڭQoRX ^ ސ ZjfͪW(`EZ[G ^3Z}EJBdRZɨw*PCS~2}!E Cɰ A e03!5=B dj(^PBCtX +E1"V}͵T) u ֛* @ PAUuO UoXra*rOXrt9ozK*(:2V`CImBu`Xw$XZ9XZ̅[gTb].XC4X;5 `[ٰ6t/s*˶3amgXXZ`mkc7 X m,lrMfj5SXZ`=`q(Jm,VVVޝk3A %v@X;vvl7XPCw3X4X;Q;fw0ug9S +qܿD~+뗠KMEH~mkz9^F1/8dڠz~~}qF)( `S>h jH#*N%Oq(NĵK.-*%` rՄ|4ڔ4,Tqv`-l?@|A0FaRq_ ys1A)Pԃy;`)'t@)POS6zXA I2A)PJx93#QeMnNŜN eRߍޟQ@Ѱ0\3LHv EK%5n% zX Uzʄx PP| rԍa*+姳l:;( QDeMn@i쨗y'끚(|R3z@\ Olf?WAeIRL!eM` Olg(fR&!ĿXb`=H#Q8 PAjcJY3W`L3;ņ'h\Fm)krs\bO ? (!`PiRj@3' q':S?D ؙ80GeL'qԝ AKcgh@W?'3:X7G I'1bg$dB F^Hc7jcLYS5Up:XWqG)x*!r! G7ϛ +:Nuvqv1r0<'lj*]x .  pUwWy2fU/`cq*Vvza*,Ne grRn VOBko,V ±J#Q,0l}Vu)*5h{u שp',r,r,[a>IENDB`python-muranoclient-1.3.0/muranoclient/version.py0000664000175000017500000000127213523272253022321 0ustar zuulzuul00000000000000# Copyright (c) 2013 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 pbr import version version_info = version.VersionInfo('python-muranoclient') python-muranoclient-1.3.0/muranoclient/tests/0000775000175000017500000000000013523272337021425 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/tests/functional/0000775000175000017500000000000013523272337023567 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/tests/functional/hooks/0000775000175000017500000000000013523272337024712 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/tests/functional/hooks/post_test_hook.sh0000775000175000017500000000402613523272253030314 0ustar zuulzuul00000000000000#!/bin/bash -xe # 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 script is executed inside post_test_hook function in devstack gate. function generate_testr_results { if [ -f .testrepository/0 ]; then sudo .tox/functional/bin/testr last --subunit > $WORKSPACE/testrepository.subunit sudo mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit sudo /usr/os-testr-env/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html sudo gzip -9 $BASE/logs/testrepository.subunit sudo gzip -9 $BASE/logs/testr_results.html sudo chown $USER:$USER $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz fi } export MURANOCLIENT_DIR="$BASE/new/python-muranoclient" sudo chown -R $USER:stack $MURANOCLIENT_DIR # Get admin credentials cd $BASE/new/devstack source openrc admin admin # Pass the appropriate variables via a config file CREDS_FILE=$MURANOCLIENT_DIR/functional_creds.conf cat < $CREDS_FILE # Credentials for functional testing [auth] uri = $OS_AUTH_URL [admin] user = $OS_USERNAME tenant = $OS_TENANT_NAME pass = $OS_PASSWORD EOF # Go to the muranoclient dir cd $MURANOCLIENT_DIR sudo chown -R $USER:stack $MURANOCLIENT_DIR # Run tests echo "Running muranoclient functional test suite" set +e # Preserve env for OS_ credentials sudo -E -H -u $USER tox -efunctional EXIT_CODE=$? set -e # Collect and parse result generate_testr_results exit $EXIT_CODE python-muranoclient-1.3.0/muranoclient/tests/functional/cli/0000775000175000017500000000000013523272337024336 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/tests/functional/cli/utils.py0000664000175000017500000000406113523272253026046 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. import os import zipfile import yaml MANIFEST = {'Format': 'MuranoPL/1.0', 'Type': 'Application', 'Description': 'MockApp for CLI tests', 'Author': 'Mirantis, Inc'} def compose_package(app_name, manifest, package_dir, require=None, archive_dir=None): """Composes a murano package Composes package `app_name` with `manifest` file as a template for the manifest and files from `package_dir`. Includes `require` section if any in the manifest file. Puts the resulting .zip file into `archive_dir` if present or in the `package_dir`. """ with open(manifest, 'w') as f: fqn = 'io.murano.apps.' + app_name mfest_copy = MANIFEST.copy() mfest_copy['FullName'] = fqn mfest_copy['Name'] = app_name mfest_copy['Classes'] = {fqn: 'mock_muranopl.yaml'} if require: mfest_copy['Require'] = require f.write(yaml.dump(mfest_copy, default_flow_style=False)) name = app_name + '.zip' if not archive_dir: archive_dir = os.path.dirname(os.path.abspath(__file__)) archive_path = os.path.join(archive_dir, name) with zipfile.ZipFile(archive_path, 'w') as zip_file: for root, dirs, files in os.walk(package_dir): for f in files: zip_file.write( os.path.join(root, f), arcname=os.path.join(os.path.relpath(root, package_dir), f) ) return archive_path python-muranoclient-1.3.0/muranoclient/tests/functional/cli/murano_test_utils.py0000664000175000017500000002201313523272253030463 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. import json import multiprocessing import os import shutil import SocketServer import tempfile import time from oslo_utils import uuidutils from six.moves import SimpleHTTPServer from tempest.lib.cli import output_parser from tempest.lib import exceptions from muranoclient.tests.functional.cli import utils from muranoclient.tests.functional import muranoclient class CLIUtilsTestBase(muranoclient.ClientTestBase): """Basic methods for Murano CLI client.""" def delete_murano_object(self, murano_object, obj_to_del): """Delete Murano object Delete Murano object like environment, category or environment-template. """ if obj_to_del not in self.listing('{0}-list'.format(murano_object)): return object_list = self.listing('{0}-delete'.format(murano_object), params=obj_to_del['ID']) start_time = time.time() while obj_to_del in self.listing('{0}-list'.format(murano_object)): if start_time - time.time() > 60: self.fail("{0} is not deleted in 60 seconds". format(murano_object)) return object_list def create_murano_object(self, murano_object, prefix_object_name): """Create Murano object Create Murano object like environment, category or environment-template. """ object_name = self.generate_name(prefix_object_name) mrn_objects = self.listing('{0}-create'.format(murano_object), params=object_name) mrn_object = None for obj in mrn_objects: if object_name == obj['Name']: mrn_object = obj break if mrn_object is None: self.fail("Murano {0} has not been created!".format(murano_object)) self.addCleanup(self.delete_murano_object, murano_object, mrn_object) return mrn_object def create_murano_object_parameter(self, murano_object, prefix_object_name, param): """Create Murano object Create Murano object like environment, category or environment-template. """ object_name = self.generate_name(prefix_object_name) params = '{0} {1}'.format(param, object_name) mrn_objects = self.listing('{0}-create'.format(murano_object), params=params) mrn_object = None for obj in mrn_objects: if object_name == obj['Name']: mrn_object = obj break if mrn_object is None: self.fail("Murano {0} has not been created!".format(murano_object)) self.addCleanup(self.delete_murano_object, murano_object, mrn_object) return mrn_object @staticmethod def generate_uuid(): """Generate uuid for objects.""" return uuidutils.generate_uuid(dashed=False) @staticmethod def generate_name(prefix): """Generate name for objects.""" suffix = CLIUtilsTestBase.generate_uuid()[:8] return "{0}_{1}".format(prefix, suffix) def get_table_struct(self, command, params=""): """Get table structure i.e. header of table.""" return output_parser.table(self.murano(command, params=params))['headers'] def get_object(self, object_list, object_value): """"Get Murano object by value from list of Murano objects.""" for obj in object_list: if object_value in obj.values(): return obj def get_property_value(self, obj, prop): return [o['Value'] for o in obj if o['Property'] == '{0}'.format(prop)][0] class TestSuiteRepository(CLIUtilsTestBase): def setUp(self): super(TestSuiteRepository, self).setUp() self.serve_dir = tempfile.mkdtemp(suffix="repo") self.app_name = self.generate_name("dummy_app") self.dummy_app_path = self._compose_app(name=self.app_name) def tearDown(self): super(TestSuiteRepository, self).tearDown() shutil.rmtree(self.serve_dir) def run_server(self): def serve_function(): class Handler(SimpleHTTPServer.SimpleHTTPRequestHandler): pass os.chdir(self.serve_dir) httpd = SocketServer.TCPServer( ("0.0.0.0", 8089), Handler, bind_and_activate=False) httpd.allow_reuse_address = True httpd.server_bind() httpd.server_activate() httpd.serve_forever() self.p = multiprocessing.Process(target=serve_function) self.p.start() def stop_server(self): self.p.terminate() def _compose_app(self, name, require=None): package_dir = os.path.join(self.serve_dir, 'apps/', name) shutil.copytree(os.path.join(os.path.dirname( os.path.realpath(__file__)), 'MockApp'), package_dir) app_name = utils.compose_package( name, os.path.join(package_dir, 'manifest.yaml'), package_dir, require=require, archive_dir=os.path.join(self.serve_dir, 'apps/'), ) return app_name class CLIUtilsTestPackagesBase(TestSuiteRepository): """Basic methods for Murano Packages CLI client.""" def import_package(self, pkg_name, pkg_path, *args): """Create Murano dummy package and import it by url.""" actions = ' '.join(args) params = '{0} {1}'.format(pkg_path, actions) package = self.listing('package-import', params=params) package = self.get_object(package, pkg_name) self.addCleanup(self.delete_murano_object, 'package', package) return package def prepare_file_with_obj_model(self, obj_model): temp_file = tempfile.NamedTemporaryFile(prefix="murano-obj-model", delete=False) self.addCleanup(os.remove, temp_file.name) with open(temp_file.name, 'w') as tf: tf.write(json.dumps([obj_model])) return temp_file.name def wait_deployment_result(self, env_id, timeout=180): start_time = time.time() env = self.listing('environment-show', params=env_id) env_status = self.get_property_value(env, 'status') expected_statuses = ['ready', 'deploying'] while env_status != 'ready': if time.time() - start_time > timeout: msg = ("Environment exceeds timeout {0} to change state " "to Ready. Environment: {1}".format(timeout, env)) raise exceptions.TimeoutException(msg) env = self.listing('environment-show', params=env_id) env_status = self.get_property_value(env, 'status') if env_status not in expected_statuses: msg = ("Environment status %s is not in expected " "statuses: %s" % (env_status, expected_statuses)) raise exceptions.TempestException(msg) time.sleep(2) return True def prepare_bundle_with_non_existed_package(self): temp_file = tempfile.NamedTemporaryFile(mode='w', delete=False) self.addCleanup(os.remove, temp_file.name) with open(temp_file.name, 'w') as tf: tf.write(json.dumps({'Packages': [ {'Name': 'first_app'}, {'Name': 'second_app', 'Version': '1.0'} ]})) return temp_file.name def prepare_bundle_with_invalid_format(self): temp_file = tempfile.NamedTemporaryFile(mode='w', delete=False) self.addCleanup(os.remove, temp_file.name) with open(temp_file.name, 'w') as tf: tf.write('Packages: [{Name: first_app}, {Name: second_app}]') return temp_file.name def deploy_environment(self, env_id, obj_model): session = self.listing('environment-session-create', params=env_id) session_id = self.get_property_value(session, 'id') temp_file = self.prepare_file_with_obj_model(obj_model) self.listing('environment-apps-edit', params='--session-id {0} {1} {2}'. format(session_id, env_id, temp_file)) self.listing('environment-deploy', params='{0} --session-id {1}'. format(env_id, session_id)) result = self.wait_deployment_result(env_id) self.assertTrue(result) python-muranoclient-1.3.0/muranoclient/tests/functional/cli/MockApp/0000775000175000017500000000000013523272337025670 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/tests/functional/cli/MockApp/Classes/0000775000175000017500000000000013523272337027265 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/tests/functional/cli/MockApp/Classes/mock_muranopl.yaml0000664000175000017500000000142313523272253033014 0ustar zuulzuul00000000000000Namespaces: =: io.murano.apps std: io.murano # Name: MockApp # Using Name from manifest.yaml Extends: std:Application Properties: greeting: Usage: Static Contract: $.string() Default: 'Hello, ' Methods: testAction: Usage: Action Body: - sleep(1) - $this.find(std:Environment).reporter.report($this, 'Completed') deploy: Body: - $this.find(std:Environment).reporter.report($this, 'Follow the white rabbit') staticAction: Scope: Public Usage: Static Arguments: - myName: Contract: $.string().notNull() - myAge: Contract: $.int().notNull() Body: - $futureAge: $myAge + 5 - Return: concat($.greeting, $myName, ". In 5 years you will be {0} years old.".format($futureAge)) python-muranoclient-1.3.0/muranoclient/tests/functional/cli/test_murano.py0000664000175000017500000010035613523272253027252 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. import json import unittest from muranoclient.tests.functional.cli import \ murano_test_utils as utils from muranoclient.tests.functional import muranoclient as murano_client from oslo_utils.strutils import bool_from_string as str2bool # TODO(mstolyarenko): need to remove this raw when # https://bugs.launchpad.net/python-muranoclient/+bug/1625039 is fixed backend_name =\ murano_client.ClientTestBase.get_backend_flag().rstrip().split()[-1] class SimpleReadOnlyMuranoClientTest(utils.CLIUtilsTestBase): """Basic, read-only tests for Murano CLI client. Basic smoke test for the Murano CLI commands which do not require creating or modifying murano objects. """ def test_category_list(self): category = self.get_table_struct('category-list') self.assertEqual(['ID', 'Name'], category) def test_env_template_list(self): templates = self.get_table_struct('env-template-list') self.assertEqual(['ID', 'Name', 'Created', 'Updated', 'Is public'], templates) def test_environment_list(self): environment = self.get_table_struct('environment-list') self.assertEqual(['ID', 'Name', 'Status', 'Created', 'Updated'], environment) def test_package_list(self): packages = self.get_table_struct('package-list') self.assertEqual(['ID', 'Name', 'FQN', 'Author', 'Active', 'Is Public', 'Type', 'Version'], packages) class TableStructureMuranoClientTest(utils.CLIUtilsTestBase): """Smoke test for the Murano CLI commands Smoke test for the Murano CLI commands which checks table structure after create or delete category, env-template environment and package. """ def test_table_struct_deployment_list(self): """Test scenario: 1) create environment 2) check table structure """ environment = self.create_murano_object('environment', 'MuranoTestTS-depl-list') table_struct = self.get_table_struct('deployment-list', params=environment['ID']) self.assertEqual(['ID', 'State', 'Created', 'Updated', 'Finished'], table_struct) def test_table_struct_of_environment_create(self): """Test scenario: 1) create environment 2) check table structure """ self.create_murano_object('environment', 'MuranoTestTS-env-create') table_struct = self.get_table_struct('environment-list') self.assertEqual(['ID', 'Name', 'Status', 'Created', 'Updated'], table_struct) def test_table_struct_of_environment_delete(self): """Test scenario: 1) create environment 2) delete environment 3) check table structure """ environment = self.create_murano_object('environment', 'MuranoTestTS-env-del') self.delete_murano_object('environment', environment) table_struct = self.get_table_struct('environment-list') self.assertEqual(['ID', 'Name', 'Status', 'Created', 'Updated'], table_struct) def test_table_struct_of_category_create(self): """Test scenario: 1) create category 2) check table structure """ self.create_murano_object('category', 'MuranoTestTS-cat-create') table_struct = self.get_table_struct('category-list') self.assertEqual(['ID', 'Name'], table_struct) def test_table_struct_of_category_delete(self): """Test scenario: 1) create category 2) delete category 3) check table structure """ category = self.create_murano_object('category', 'MuranoTestTS-cat-create') self.delete_murano_object('category', category) category = self.get_table_struct('category-list') self.assertEqual(['ID', 'Name'], category) def test_table_struct_of_env_template_create(self): """Test scenario: 1) create env_template 2) check table structure """ self.create_murano_object('env-template', 'MuranoTestTS-env-tmp-create') table_struct = self.get_table_struct('env-template-list') self.assertEqual(['ID', 'Name', 'Created', 'Updated', 'Is public'], table_struct) def test_table_struct_of_env_template_delete(self): """Test scenario: 1) create env_template 2) delete env_template 3) check table structure """ env_template = self.create_murano_object('env-template', 'MuranoTestTS-env-tmp-create') self.delete_murano_object('env-template', env_template) table_struct = self.get_table_struct('env-template-list') self.assertEqual(['ID', 'Name', 'Created', 'Updated', 'Is public'], table_struct) class EnvironmentMuranoSanityClientTest(utils.CLIUtilsTestBase): """Sanity tests for testing actions with environment. Smoke test for the Murano CLI commands which checks basic actions with environment command like create, delete, rename etc. """ def test_environment_create(self): """Test scenario: 1) create environment 2) check that created environment exist """ environment = self.create_murano_object('environment', 'TestMuranoSanityEnv') env_list = self.listing('environment-list') # Deleting dates from dictionaries to skip it in assert map(lambda x: x.pop('Updated', None), env_list + [environment]) map(lambda x: x.pop('Created', None), env_list + [environment]) self.assertIn(environment, env_list) def test_environment_delete(self): """Test scenario: 1) create environment 2) delete environment """ environment = self.create_murano_object('environment', 'TestMuranoSanityEnv') self.delete_murano_object('environment', environment) env_list = self.listing('environment-list') self.assertNotIn(environment, env_list) def test_environment_rename(self): """Test scenario: 1) create environment 2) rename environment """ environment = self.create_murano_object('environment', 'TestMuranoSanityEnv') new_env_name = self.generate_name('TestMuranoSEnv-env-rename') rename_params = "{0} {1}".format(environment['Name'], new_env_name) new_list = self.listing('environment-rename', params=rename_params) renamed_env = self.get_object(new_list, new_env_name) self.addCleanup(self.delete_murano_object, 'environment', renamed_env) new_env_list = self.listing('environment-list') # Deleting dates from dictionaries to skip it in assert map(lambda x: x.pop('Updated', None), new_env_list + [environment] + [renamed_env]) map(lambda x: x.pop('Created', None), new_env_list + [environment] + [renamed_env]) self.assertIn(renamed_env, new_env_list) self.assertNotIn(environment, new_env_list) def test_table_struct_env_show(self): """Test scenario: 1) create environment 2) check structure of env_show object """ environment = self.create_murano_object('environment', 'TestMuranoSanityEnv') env_show = self.listing('environment-show', params=environment['Name']) # Check structure of env_show object self.assertEqual(['acquired_by', 'created', 'description_text', 'id', 'name', 'services', 'status', 'tenant_id', 'updated', 'version'], map(lambda x: x['Property'], env_show)) def test_environment_show(self): """Test scenario: 1) create environment 2) check that env_name, ID, updated and created values exist in env_show object """ environment = self.create_murano_object('environment', 'TestMuranoSanityEnv') env_show = self.listing('environment-show', params=environment['Name']) self.assertIn(environment['Created'], map(lambda x: x['Value'], env_show)) self.assertIn(environment['Updated'], map(lambda x: x['Value'], env_show)) self.assertIn(environment['Name'], map(lambda x: x['Value'], env_show)) self.assertIn(environment['ID'], map(lambda x: x['Value'], env_show)) def test_environment_delete_by_id(self): """Test scenario: 1) create environment 2) delete environment by environment ID """ env_name = self.generate_name('TestMuranoSanityEnv') environment = self.create_murano_object('environment', env_name) result = self.murano('environment-delete', params=environment['ID'], fail_ok=False) self.assertNotIn(environment['Name'], result) env_list = self.listing('environment-list') self.assertNotIn(environment, env_list) def test_environment_model_show(self): """Test scenario: 1) create environment 2) check that the result of environment-model-show is a valid non-empty json """ env_name = self.generate_name('TestMuranoSanityEnv') environment = self.create_murano_object('environment', env_name) model = self.murano('environment-model-show', params=environment['ID']) result = json.loads(model) self.assertEqual(4, len(result)) class CategoryMuranoSanityClientTest(utils.CLIUtilsTestBase): """Sanity tests for testing actions with Category. Smoke test for the Murano CLI commands which checks basic actions with category command like create, delete etc. """ def test_category_create(self): """Test scenario: 1) create category 2) check that created category exist """ category = self.create_murano_object('category', 'TestMuranoSanityCategory') category_list = self.listing('category-list') self.assertIn(category, category_list) def test_category_delete(self): """Test scenario: 1) create category 2) delete category 3) check that category has been deleted successfully """ category = self.create_murano_object('category', 'TestMuranoSanityCategory') self.delete_murano_object('category', category) category_list = self.listing('category-list') self.assertNotIn(category, category_list) def test_table_struct_category_show(self): """Test scenario: 1) create category 2) check table structure of category-show object """ category = self.create_murano_object('category', 'TestMuranoSanityCategory') category_show = self.listing('category-show', params=category['ID']) self.assertEqual(['id', 'name', 'packages'], map(lambda x: x['Property'], category_show)) def test_category_show(self): """Test scenario: 1) create category 2) check that category values exist in category_show object """ category = self.create_murano_object('category', 'TestMuranoSanityCategory') category_show = self.listing('category-show', params=category['ID']) self.assertIn(category['ID'], map(lambda x: x['Value'], category_show)) self.assertIn(category['Name'], map(lambda x: x['Value'], category_show)) def test_non_existing_category_delete(self): """Test scenario: 1) try to call category-delete for non existing category 2) check that error message contains user friendly substring """ result = self.murano('category-delete', params='non-existing', fail_ok=True) self.assertIn("Failed to delete 'non-existing'; category not found", result) def test_non_existing_category_show(self): """Test scenario: 1) try to call category-show for non existing category 2) check that error message contains user friendly substring """ result = self.murano('category-show', params='non-existing', fail_ok=True) self.assertIn("Category id 'non-existing' not found", result) def test_category_create_with_long_name(self): """Test scenario: 1) try to create category with long name (>80) 2) check that error message contains user friendly substring """ result = self.murano('category-create', params='name' * 21, fail_ok=True) self.assertIn( "Category name should be 80 characters maximum", result) class EnvTemplateMuranoSanityClientTest(utils.CLIUtilsTestBase): """Sanity tests for testing actions with Environment template. Smoke test for the Murano CLI commands which checks basic actions with env-temlate command like create, delete etc. """ def test_environment_template_create(self): """Test scenario: 1) create environment template 2) check that created environment template exist """ env_template = self.create_murano_object('env-template', 'TestMuranoSanityEnvTemp') env_template_list = self.listing('env-template-list') # Deleting dates from dictionaries to skip it in assert map(lambda x: x.pop('Updated', None), env_template_list + [env_template]) map(lambda x: x.pop('Created', None), env_template_list + [env_template]) self.assertIn(env_template, env_template_list) def test_environment_template_delete(self): """Test scenario: 1) create environment template 2) delete environment template 3) check that deleted environment template doesn't exist """ env_template = self.create_murano_object('env-template', 'TestMuranoSanityEnvTemp') env_template_list = self.delete_murano_object('env-template', env_template) self.assertNotIn(env_template, env_template_list) def test_table_struct_env_template_show(self): """Test scenario: 1) create environment template 2) check table structure of env-template-show object """ env_template = self.create_murano_object('env-template', 'TestMuranoSanityEnvTemp') env_template_show = self.listing('env-template-show', params=env_template['ID']) tested_env_template = map(lambda x: x['Property'], env_template_show) self.assertIn('created', tested_env_template) self.assertIn('id', tested_env_template) self.assertIn('name', tested_env_template) self.assertIn('services', tested_env_template) self.assertIn('tenant_id', tested_env_template) self.assertIn('updated', tested_env_template) self.assertIn('version', tested_env_template) def test_env_template_show(self): """Test scenario: 1) create environment template 2) check that environment template values exist in env-template-show object """ env_template = self.create_murano_object('env-template', 'TestMuranoSanityEnvTemp') env_template_show = self.listing('env-template-show', params=env_template['ID']) tested_env = map(lambda x: x['Value'], env_template_show) self.assertIn(env_template['ID'], tested_env) self.assertIn(env_template['Name'], tested_env) def test_env_template_create_environment(self): """Test scenario: 1) create environment template 2) create environment from template """ env_template = self.create_murano_object('env-template', 'TestMuranoSanityEnvTemp') new_env_name = self.generate_name('EnvFromTemp') params = "{0} {1}".format(env_template['ID'], new_env_name) env_created = self.listing('env-template-create-env', params=params) tested_env_created = map(lambda x: x['Property'], env_created) self.assertIn('environment_id', tested_env_created) self.assertIn('session_id', tested_env_created) def test_env_template_clone(self): """Test scenario: 1) create environment template 2) clone template 3) check that create environment template has the new name 4) delete new template """ env_template = self.create_murano_object_parameter( 'env-template', 'TestMuranoSanityEnvTemp', '--is-public') new_template = self.generate_name('TestMuranoSanityEnvTemp') params = "{0} {1}".format(env_template['ID'], new_template) template_created = self.listing('env-template-clone', params=params) list = map(lambda x: ({x['Property']: x['Value']}), template_created) result_name = filter(lambda x: x.get('name'), list)[0]['name'] result_id = filter(lambda x: x.get('id'), list)[0]['id'] self.listing('env-template-delete', params=result_id) self.assertIn(result_name, new_template) class PackageMuranoSanityClientTest(utils.CLIUtilsTestPackagesBase): """Sanity tests for testing actions with Packages. Smoke tests for the Murano CLI commands which check basic actions with packages like import, create, delete etc. """ def test_package_import_by_url(self): """Test scenario: 1) import package by url 2) check that package exists """ try: self.run_server() package = self.import_package( self.app_name, 'http://localhost:8089/apps/{0}.zip'.format(self.app_name) ) finally: self.stop_server() package_list = self.listing('package-list') self.assertIn(package, package_list) def test_package_import_by_path(self): """Test scenario: 1) import package by path 2) check that package exists """ package = self.import_package( self.app_name, self.dummy_app_path ) package_list = self.listing('package-list') self.assertIn(package, package_list) def test_package_is_public(self): """Test scenario: 1) import package 2) check that package is public """ package = self.import_package( self.app_name, self.dummy_app_path, '--is-public') package_show = self.listing('package-show', params=package['ID']) package_show = {item['Property']: item['Value'] for item in package_show} self.assertEqual(package['Is Public'], 'True') self.assertEqual( str2bool(package['Is Public']), str2bool(package_show['is_public'])) def test_package_delete(self): """Test scenario: 1) import package 2) delete package 3) check that package has been deleted """ package = self.import_package( self.app_name, self.dummy_app_path ) package_list = self.delete_murano_object('package', package) self.assertNotIn(package, package_list) def test_package_show(self): """Test scenario: 1) import package 2) check that package values exist in return by package-show object """ package = self.import_package( self.app_name, self.dummy_app_path ) package_show = self.listing('package-show', params=package['ID']) package_show = {item['Property']: item['Value'] for item in package_show} self.assertEqual( str2bool(package['Active']), str2bool(package_show['enabled'])) self.assertEqual( package['FQN'], package_show['fully_qualified_name']) self.assertEqual( package['ID'], package_show['id']) self.assertEqual( str2bool(package['Is Public']), str2bool(package_show['is_public'])) self.assertEqual( package['Name'], package_show['name']) self.assertEqual( package['Type'], package_show['type']) def test_package_import_update(self): """Test scenario: 1) import package 2) import new_package using option 'u' - update 3) check that package has been updated """ package = self.import_package( self.app_name, self.dummy_app_path ) upd_package = self.import_package( self.app_name, self.dummy_app_path, '--exists-action', 'u' ) self.assertEqual(package['Name'], upd_package['Name']) self.assertNotEqual(package['ID'], upd_package['ID']) def test_package_import_skip(self): """Test scenario: 1) import package using option 's' - skip for existing package 2) try to import the same package using option 's' - skip 3) check that package hasn't been updated """ package = self.import_package( self.app_name, self.dummy_app_path, '--exists-action', 's' ) updated_package = self.import_package( self.app_name, self.dummy_app_path, '--exists-action', 's' ) package_list = self.listing("package-list") self.assertIn(package, package_list) self.assertIsNone(updated_package) def test_package_import_abort(self): """Test scenario: 1) import package 2) import new_package using option 'a' - skip 3) check that package hasn't been updated """ package = self.import_package( self.app_name, self.dummy_app_path ) package_list = self.listing('package-list') self.assertIn(package, package_list) package = self.import_package( self.app_name, self.dummy_app_path, '--exists-action', 'a' ) package_list = self.listing('package-list') self.assertNotIn(package, package_list) class DeployMuranoEnvironmentTest(utils.CLIUtilsTestPackagesBase): """Test for testing Murano environment deployment. Test for the Murano CLI commands which checks addition of app to the environment, session creation and deployment of environment. """ # TODO(mstolyarenko): need to unskip this test when # https://bugs.launchpad.net/python-muranoclient/+bug/1625039 is fixed @unittest.skipIf(backend_name == 'glare', "This test fails when GLARE is used as packages " "service. To be fixed as part of #1625039") def test_environment_deployment(self): """Test scenario: 1) import package 2) create environment 3) create session for created environment 4) add application to the environment 5) send environment to deploy 6) check that deployment was successful """ self.import_package( self.app_name, self.dummy_app_path ) env_id = self.create_murano_object('environment', 'TestMuranoDeployEnv')['ID'] obj_model = { 'op': 'add', 'path': '/-', 'value': { '?': { 'type': 'io.murano.apps.{0}'.format(self.app_name), 'id': '{0}'.format(self.generate_uuid()), } } } self.deploy_environment(env_id, obj_model) deployments = self.listing('deployment-list', params=env_id) self.assertEqual('success', deployments[0]['State']) self.assertEqual(1, len(deployments)) # TODO(mstolyarenko): need to unskip this test when # https://bugs.launchpad.net/python-muranoclient/+bug/1625039 is fixed @unittest.skipIf(backend_name == 'glare', "This test fails when GLARE is used as packages " "service. To be fixed as part of #1625039") def test_add_component_to_deployed_env(self): """Test scenario: 1) import package 2) create environment 3) create session for created environment 4) add application to the environment 5) send environment to deploy 6) check that deployment was successful 7) add application to environment 8) deploy environment again """ self.import_package( self.app_name, self.dummy_app_path ) env_id = self.create_murano_object('environment', 'TestMuranoDeployEnv')['ID'] obj_model = { 'op': 'add', 'path': '/-', 'value': { '?': { 'type': 'io.murano.apps.{0}'.format(self.app_name), 'id': '', } } } obj_model['value']['?']['id'] = self.generate_uuid() self.deploy_environment(env_id, obj_model) deployments = self.listing('deployment-list', params=env_id) self.assertEqual('success', deployments[0]['State']) self.assertEqual(1, len(deployments)) obj_model['value']['?']['id'] = self.generate_uuid() self.deploy_environment(env_id, obj_model) deployments = self.listing('deployment-list', params=env_id) self.assertEqual('success', deployments[1]['State']) self.assertEqual(2, len(deployments)) # TODO(mstolyarenko): need to unskip this test when # https://bugs.launchpad.net/python-muranoclient/+bug/1625039 is fixed @unittest.skipIf(backend_name == 'glare', "This test fails when GLARE is used as packages " "service. To be fixed as part of #1625039") def test_delete_component_from_deployed_env(self): """Test scenario: 1) import package 2) create environment 3) create session for created environment 4) add application to the environment 5) send environment to deploy 6) check that deployment was successful 7) delete application from environment 8) deploy environment again """ self.import_package( self.app_name, self.dummy_app_path ) env_id = self.create_murano_object('environment', 'TestMuranoDeployEnv')['ID'] obj_model = { 'op': 'add', 'path': '/-', 'value': { '?': { 'type': 'io.murano.apps.{0}'.format(self.app_name), 'id': '{0}'.format(self.generate_uuid()), } } } self.deploy_environment(env_id, obj_model) obj_model = { 'op': 'remove', 'path': '/0' } self.deploy_environment(env_id, obj_model) deployments = self.listing('deployment-list', params=env_id) self.assertEqual('success', deployments[1]['State']) self.assertEqual(2, len(deployments)) class BundleMuranoSanityClientTest(utils.CLIUtilsTestPackagesBase): """Sanity tests for testing actions with bundle. Tests for the Murano CLI commands which check basic actions with bundles. """ def test_bundle_import_without_bundle_name(self): """Test scenario: 1) Execute murano bundle-import command without bundle name 2) check that error message contains user friendly substring """ result = self.murano('bundle-import', params='', fail_ok=True) self.assertIn("murano bundle-import: error: too few arguments", result) @unittest.skip("Skip due to apps.openstack.org website is retired.") def test_bundle_import_with_non_existing_package_name(self): """Test scenario: 1) Execute murano bundle-import command with non-existing packages name inside 2) check that error message contains user friendly substring """ result = self.murano( 'bundle-import', params=self.prepare_bundle_with_non_existed_package(), fail_ok=False) self.assertIn("Couldn't find file for package", result) self.assertIn("Error Got non-ok status(404) while connecting", result) @unittest.skip("Skip due to apps.openstack.org website is retired.") def test_bundle_import_with_non_existing_name(self): """Test scenario: 1) Execute murano bundle-import command with non-existing bundle name 2) check that error message contains user friendly substring """ result = self.murano('bundle-import', params=self.app_name, fail_ok=True) self.assertIn("Bundle file '{}' does not exist".format(self.app_name), result) self.assertIn("reason: Got non-ok status(404) while connecting to", result) def test_bundle_import_with_invalid_file_format(self): """Test scenario: 1) Execute murano bundle-import command with invalid bundle file format 2) check that error message contains user friendly substring """ try: self.murano( 'bundle-import', params=self.prepare_bundle_with_invalid_format(), fail_ok=False) except utils.exceptions.CommandFailed as exception: self.assertIn("Can't parse bundle contents", exception.stdout) class StaticActionMuranoClientTest(utils.CLIUtilsTestPackagesBase): """Tests for testing static actions execution. Tests for the Murano CLI commands which check the result of sample static action execution. """ def test_static_action_call(self): """Test scenario: 1) import package 2) call static action of the class in that package 3) check the result of action """ package = self.import_package( self.app_name, self.dummy_app_path ) result = self.murano( 'static-action-call', params='{0} staticAction --package-name {1} ' '--arguments myName=John myAge=28'.format(package['FQN'], package['FQN'])) expected = "Waiting for result...\nStatic action result: Hello, " \ "John. In 5 years you will be 33 years old.\n" self.assertEqual(expected, result) python-muranoclient-1.3.0/muranoclient/tests/functional/cli/__init__.py0000664000175000017500000000000013523272253026432 0ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/tests/functional/muranoclient.py0000664000175000017500000000520213523272253026635 0ustar zuulzuul00000000000000# Copyright (c) 2014 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 six.moves import configparser from tempest.lib.cli import base class ClientTestBase(base.ClientTestBase): def murano(self, action, flags='', params='', fail_ok=False, endpoint_type='publicURL', merge_stderr=True): flags += self.get_backend_flag() return self.clients.cmd_with_auth( 'murano', action, flags, params, fail_ok, merge_stderr) def _get_clients(self): cli_dir = os.environ.get( 'OS_MURANOCLIENT_EXEC_DIR', os.path.join(os.path.abspath('.'), '.tox/functional/bin')) self.username = os.environ.get('OS_USERNAME') self.password = os.environ.get('OS_PASSWORD') self.tenant_name = os.environ.get('OS_TENANT_NAME') self.uri = os.environ.get('OS_AUTH_URL') config = configparser.RawConfigParser() if config.read('functional_creds.conf'): # the OR pattern means the environment is preferred for # override self.username = self.username or config.get('admin', 'user') self.password = self.password or config.get('admin', 'pass') self.tenant_name = self.tenant_name or config.get('admin', 'tenant') self.uri = self.uri or config.get('auth', 'uri') clients = base.CLIClient( username=self.username, password=self.password, tenant_name=self.tenant_name, uri=self.uri, cli_dir=cli_dir ) return clients def listing(self, command, params=""): return self.parser.listing(self.murano(command, params=params)) def get_value(self, need_field, known_field, known_value, somelist): for element in somelist: if element[known_field] == known_value: return element[need_field] @staticmethod def get_backend_flag(): backend = os.environ.get('MURANO_PACKAGES_SERVICE', 'murano') backend_flag = " --murano-packages-service {0} ".format(backend) return backend_flag python-muranoclient-1.3.0/muranoclient/tests/functional/__init__.py0000664000175000017500000000000013523272253025663 0ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/tests/unit/0000775000175000017500000000000013523272337022404 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/tests/unit/test_utils.py0000664000175000017500000004440713523272253025163 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. import json import os.path import tempfile import zipfile import mock import requests import requests_mock import six import testtools import yaml from muranoclient.common import utils class FileTest(testtools.TestCase): def test_file_object_from_file(self): f_obj = tempfile.NamedTemporaryFile(delete=True) new_f_obj = utils.File(f_obj).open() self.assertTrue(hasattr(new_f_obj, 'read')) new_f_obj = utils.File(f_obj.name).open() self.assertTrue(hasattr(new_f_obj, 'read')) def test_file_object_file_fails(self): f_obj = utils.File('') self.assertRaises(ValueError, f_obj.open) def test_file_object_url_fails(self): resp = requests.Response() resp.status_code = 400 resp.raw = six.BytesIO(six.b("123")) with mock.patch( 'requests.get', mock.Mock(side_effect=lambda k, *args, **kwargs: resp)): f = utils.File("http://127.0.0.1") self.assertRaises(ValueError, f.open) def test_file_object_url(self): resp = requests.Response() resp.raw = six.BytesIO(six.b("123")) resp.status_code = 200 with mock.patch( 'requests.get', mock.Mock(side_effect=lambda k, *args, **kwargs: resp)): new_f_obj = utils.File('http://127.0.0.1/').open() self.assertTrue(hasattr(new_f_obj, 'read')) def make_pkg(manifest_override, image_dicts=None): manifest = { 'Author': '', 'Classes': {'foo': 'foo.yaml'}, 'Description': '', 'Format': 1.0, 'FullName': 'org.foo', 'Name': 'Apache HTTP Server', 'Type': 'Application'} manifest.update(manifest_override) file_obj = six.BytesIO() zfile = zipfile.ZipFile(file_obj, "a") zfile.writestr('manifest.yaml', yaml.dump(manifest)) zfile.writestr('Classes/foo.yaml', yaml.dump({})) if image_dicts: images_list = [] default_image_spec = { 'ContainerFormat': 'bare', 'DiskFormat': 'qcow2', 'Name': '', } for image_dict in image_dicts: image_spec = default_image_spec.copy() image_spec.update(image_dict) images_list.append(image_spec) images = {'Images': images_list, } zfile.writestr('images.lst', yaml.dump(images)) zfile.close() file_obj.seek(0) return file_obj class PackageTest(testtools.TestCase): base_url = "http://127.0.0.1" @requests_mock.mock() def test_from_location_local_file(self, m): temp = tempfile.NamedTemporaryFile() pkg = make_pkg({'FullName': 'single_app'}) temp.write(pkg.read()) temp.flush() path, name = os.path.split(temp.name) # ensure we do not go to base url m.get(self.base_url + '/apps/{0}.zip'.format(name), status_code=404) self.assertEqual('single_app', utils.Package.from_location( name=name, base_url=self.base_url, path=path, ).manifest['FullName']) def test_package_from_directory(self): path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "fixture_data/empty-app") pkg = utils.Package(utils.File(path)) self.assertEqual('empty', pkg.manifest['FullName']) pkg = utils.Package.from_location('', path=path) self.assertEqual('empty', pkg.manifest['FullName']) @requests_mock.mock() def test_from_location_url(self, m): """Test that url overrides name specification.""" pkg = make_pkg({'FullName': 'single_app'}) m.get('http://127.0.0.2/apps/single_app.zip', body=pkg) m.get(self.base_url + '/apps/single_app.zip', status_code=404) self.assertEqual('single_app', utils.Package.from_location( name='single_app', base_url=self.base_url, url="http://127.0.0.2/apps/single_app.zip", ).manifest['FullName']) @requests_mock.mock() def test_from_location(self, m): """Test from location url requesting mechanism.""" pkg = make_pkg({'FullName': 'single_app'}) pkg_ver = make_pkg({'FullName': 'single_app'}) m.get(self.base_url + '/apps/single_app.zip', body=pkg) m.get(self.base_url + '/apps/single_app.1.0.zip', body=pkg_ver) m.get(self.base_url + '/apps/single_app.2.0.zip', status_code=404) self.assertEqual('single_app', utils.Package.from_location( name='single_app', base_url=self.base_url).manifest['FullName']) self.assertEqual('single_app', utils.Package.from_location( name='single_app', version='1.0', base_url=self.base_url).manifest['FullName']) self.assertRaises( ValueError, utils.Package.from_location, name='single_app', version='2.0', base_url=self.base_url) def test_no_requirements(self): pkg = make_pkg({'FullName': 'single_app'}) app = utils.Package.fromFile(pkg) reqs = app.requirements(base_url=self.base_url) self.assertEqual({'single_app': app}, reqs) @requests_mock.mock() def test_requirements(self, m): """Test that dependencies are parsed correctly.""" pkg3 = make_pkg({'FullName': 'dep_of_dep'}) pkg2 = make_pkg({'FullName': 'dep_app', 'Require': { 'dep_of_dep': "1.0"}, }) pkg1 = make_pkg({'FullName': 'main_app', 'Require': { 'dep_app': None}, }) m.get(self.base_url + '/apps/main_app.zip', body=pkg1) m.get(self.base_url + '/apps/dep_app.zip', body=pkg2) m.get(self.base_url + '/apps/dep_of_dep.1.0.zip', body=pkg3) app = utils.Package.fromFile(pkg1) reqs = app.requirements(base_url=self.base_url) self.assertEqual( {'main_app': app, 'dep_app': mock.ANY, 'dep_of_dep': mock.ANY}, reqs) @mock.patch('muranoclient.common.utils.Package.from_file') def test_requirements_order(self, from_file): """Test that dependencies are parsed in correct order.""" pkg5 = make_pkg({'FullName': 'd4', }) pkg4 = make_pkg({'FullName': 'd3', 'Require': {'d4': None}, }) pkg3 = make_pkg({'FullName': 'd2', 'Require': {'d3': None}, }) pkg2 = make_pkg({'FullName': 'd1', 'Require': {'d3': None}, }) pkg1 = make_pkg({'FullName': 'M', 'Require': {'d1': None, 'd2': None, 'd4': None}, }) def side_effect(name): if 'M' in name: return utils.Package(utils.File(pkg1)) if 'd1' in name: return utils.Package(utils.File(pkg2)) if 'd2' in name: return utils.Package(utils.File(pkg3)) if 'd3' in name: return utils.Package(utils.File(pkg4)) if 'd4' in name: return utils.Package(utils.File(pkg5)) from_file.side_effect = side_effect app = from_file('M') reqs = app.requirements(base_url=self.base_url) def key_position(key): keys = list(six.iterkeys(reqs)) return keys.index(key) self.assertTrue( key_position('d4') < key_position('d3') and key_position('d4') < key_position('M') and key_position('d3') < key_position('d1') and key_position('d3') < key_position('d2') < key_position('M') ) @mock.patch('muranoclient.common.utils.Package.from_file') def test_requirements_order2(self, from_file): """Test that dependencies are parsed in correct order.""" pkg5 = make_pkg({'FullName': 'd4', 'Require': {'d6': None}, }) pkg4 = make_pkg({'FullName': 'd3', 'Require': {'d4': None}, }) pkg3 = make_pkg({'FullName': 'd1', 'Require': {'d3': None, 'd7': None}, }) pkg2 = make_pkg({'FullName': 'd2', 'Require': {'d3': None}, }) pkg6 = make_pkg({'FullName': 'd6', }) pkg7 = make_pkg({'FullName': 'd7', 'Require': {'d8': None}, }) pkg8 = make_pkg({'FullName': 'd8', }) pkg1 = make_pkg({'FullName': 'M', 'Require': {'d1': None, 'd2': None, 'd4': None, }, }) def side_effect(name): if 'M' in name: return utils.Package(utils.File(pkg1)) if 'd1' in name: return utils.Package(utils.File(pkg2)) if 'd2' in name: return utils.Package(utils.File(pkg3)) if 'd3' in name: return utils.Package(utils.File(pkg4)) if 'd4' in name: return utils.Package(utils.File(pkg5)) if 'd6' in name: return utils.Package(utils.File(pkg6)) if 'd7' in name: return utils.Package(utils.File(pkg7)) if 'd8' in name: return utils.Package(utils.File(pkg8)) from_file.side_effect = side_effect app = from_file('M') reqs = app.requirements(base_url=self.base_url) def key_position(key): keys = list(six.iterkeys(reqs)) return keys.index(key) self.assertTrue( key_position('d6') < key_position('d4') < key_position('d3') < key_position('d1') and key_position('d3') < key_position('d2') and key_position('d1') < key_position('M') and key_position('d2') < key_position('M') and key_position('d8') < key_position('d7') < key_position('d1') ) @mock.patch('muranoclient.common.utils.Package.from_file') def test_cyclic_requirements(self, from_file): """Test that a cyclic dependency would be handled correctly.""" pkg3 = make_pkg({'FullName': 'dep_of_dep', 'Require': { 'main_app': None, 'dep_app': None}, }) pkg2 = make_pkg({'FullName': 'dep_app', 'Require': { 'dep_of_dep': None, 'main_app': None}, }) pkg1 = make_pkg({'FullName': 'main_app', 'Require': { 'dep_app': None, 'dep_of_dep': None}, }) def side_effect(name): if 'main_app' in name: return utils.Package(utils.File(pkg1)) if 'dep_app' in name: return utils.Package(utils.File(pkg2)) if 'dep_of_dep' in name: return utils.Package(utils.File(pkg3)) from_file.side_effect = side_effect app = from_file('main_app') reqs = app.requirements(base_url=self.base_url) self.assertEqual( {'main_app': app, 'dep_app': mock.ANY, 'dep_of_dep': mock.ANY}, reqs) @mock.patch('muranoclient.common.utils.Package.from_file') def test_order_with_cyclic_requirements2(self, from_file): """Test that dependencies are parsed in correct order.""" pkg6 = make_pkg({'FullName': 'd5', 'Require': {'d6': None}, }) pkg7 = make_pkg({'FullName': 'd6', }) pkg5 = make_pkg({'FullName': 'd4', 'Require': {'d3': None, 'd5': None}}) pkg4 = make_pkg({'FullName': 'd3', 'Require': {'d4': None}, }) pkg3 = make_pkg({'FullName': 'd2', 'Require': {'d1': None, 'd5': None, 'd6': None}, }) pkg2 = make_pkg({'FullName': 'd1', 'Require': {'d2': None}, }) pkg1 = make_pkg({'FullName': 'M', 'Require': {'d1': None, 'd3': None}, }) def side_effect(name): if 'M' in name: return utils.Package(utils.File(pkg1)) if 'd1' in name: return utils.Package(utils.File(pkg2)) if 'd2' in name: return utils.Package(utils.File(pkg3)) if 'd3' in name: return utils.Package(utils.File(pkg4)) if 'd4' in name: return utils.Package(utils.File(pkg5)) if 'd5' in name: return utils.Package(utils.File(pkg6)) if 'd6' in name: return utils.Package(utils.File(pkg7)) from_file.side_effect = side_effect app = from_file('M') reqs = app.requirements(base_url=self.base_url) def key_position(key): keys = list(six.iterkeys(reqs)) return keys.index(key) self.assertTrue( key_position('d5') < key_position('d4') and key_position('d5') < key_position('d2') and key_position('d5') < key_position('d3') < key_position('M') and key_position('d5') < key_position('d1') < key_position('M') ) @mock.patch('muranoclient.common.utils.Package.from_file') def test_order_with_cyclic_requirements3(self, from_file): """Test that dependencies are parsed in correct order.""" pkg5 = make_pkg({'FullName': 'd4', }) pkg4 = make_pkg({'FullName': 'd3', 'Require': {'M': None}, }) pkg3 = make_pkg({'FullName': 'd2', 'Require': {'d3': None, 'd4': None}, }) pkg2 = make_pkg({'FullName': 'd1', 'Require': {'d2': None}, }) pkg1 = make_pkg({'FullName': 'M', 'Require': {'d1': None}, }) def side_effect(name): if 'M' in name: return utils.Package(utils.File(pkg1)) if 'd1' in name: return utils.Package(utils.File(pkg2)) if 'd2' in name: return utils.Package(utils.File(pkg3)) if 'd3' in name: return utils.Package(utils.File(pkg4)) if 'd4' in name: return utils.Package(utils.File(pkg5)) from_file.side_effect = side_effect app = from_file('M') reqs = app.requirements(base_url=self.base_url) def key_position(key): keys = list(six.iterkeys(reqs)) return keys.index(key) self.assertTrue( key_position('d4') < key_position('M') and key_position('d4') < key_position('d1') and key_position('d4') < key_position('d2') and key_position('d4') < key_position('d3') ) def test_images(self): pkg = make_pkg({}) app = utils.Package.fromFile(pkg) self.assertEqual([], app.images()) pkg = make_pkg( {}, [{'Name': 'test.qcow2'}, {'Name': 'test2.qcow2'}]) app = utils.Package.fromFile(pkg) self.assertEqual( set(['test.qcow2', 'test2.qcow2']), set([img['Name'] for img in app.images()])) def test_file_object_repo_fails(self): resp = requests.Response() resp.raw = six.BytesIO(six.b("123")) resp.status_code = 400 with mock.patch( 'requests.get', mock.Mock(side_effect=lambda k, *args, **kwargs: resp)): self.assertRaises( ValueError, utils.Package.from_location, name='foo.bar.baz', base_url='http://127.0.0.1') def test_no_repo_url_fails(self): self.assertRaises(ValueError, utils.Package.from_location, name='foo.bar.baz', base_url='') @mock.patch.object(utils.Package, 'validate') def test_file_object_repo(self, m_validate): resp = requests.Response() resp.raw = six.BytesIO(six.b("123")) resp.status_code = 200 m_validate.return_value = None with mock.patch( 'requests.get', mock.Mock(side_effect=lambda k, *args, **kwargs: resp)): new_f_obj = utils.Package.from_location( name='foo.bar.baz', base_url='http://127.0.0.1').file() self.assertTrue(hasattr(new_f_obj, 'read')) class BundleTest(testtools.TestCase): base_url = "http://127.0.0.1" @requests_mock.mock() def test_packages(self, m): s = six.StringIO() bundle_contents = {'Packages': [ {'Name': 'first_app'}, {'Name': 'second_app', 'Version': '1.0'} ]} json.dump(bundle_contents, s) s.seek(0) bundle = utils.Bundle.from_file(s) self.assertEqual( set(['first_app', 'second_app']), set([p['Name'] for p in bundle.package_specs()]) ) # setup packages pkg1 = make_pkg({'FullName': 'first_app'}) pkg2 = make_pkg({'FullName': 'second_app'}) m.get(self.base_url + '/apps/first_app.zip', body=pkg1) m.get(self.base_url + '/apps/second_app.1.0.zip', body=pkg2) self.assertEqual( set(['first_app', 'second_app']), set([p.manifest['FullName'] for p in bundle.packages(base_url=self.base_url)]) ) class TraverseTest(testtools.TestCase): def test_traverse_and_replace(self): obj = [ {'id': '===id1==='}, {'id': '===id2===', 'x': [{'bar': '===id1==='}]}, ['===id1===', '===id2==='], '===id3===', '===nonid0===', '===id3===', ] utils.traverse_and_replace(obj) self.assertNotEqual('===id1===', obj[0]['id']) self.assertNotEqual('===id2===', obj[1]['id']) self.assertNotEqual('===id1===', obj[1]['x'][0]['bar']) self.assertNotEqual('===id1===', obj[2][0]) self.assertNotEqual('===id2===', obj[2][1]) self.assertNotEqual('===id3===', obj[3]) self.assertEqual('===nonid0===', obj[4]) self.assertNotEqual('===id3===', obj[5]) self.assertEqual(obj[0]['id'], obj[1]['x'][0]['bar']) self.assertEqual(obj[0]['id'], obj[2][0]) self.assertEqual(obj[1]['id'], obj[2][1]) self.assertEqual(obj[3], obj[5]) python-muranoclient-1.3.0/muranoclient/tests/unit/test_package_creator.py0000664000175000017500000001731413523272253027132 0ustar zuulzuul00000000000000# Copyright (c) 2014 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 import shutil from muranoclient.apiclient import exceptions from muranoclient.tests.unit import base from muranoclient.v1.package_creator import hot_package from muranoclient.v1.package_creator import mpl_package FIXTURE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), 'fixture_data')) TEMPLATE = os.path.join(FIXTURE_DIR, 'heat-template.yaml') CLASSES_DIR = os.path.join(FIXTURE_DIR, 'test-app', 'Classes') RESOURCES_DIR = os.path.join(FIXTURE_DIR, 'test-app', 'Resources') UI = os.path.join(FIXTURE_DIR, 'test-app', 'ui.yaml') LOGO = os.path.join(FIXTURE_DIR, 'logo.png') class TestArgs(object): pass class PackageCreatorTest(base.TestAdditionalAsserts): def test_generate_hot_manifest(self): args = TestArgs() args.template = TEMPLATE args.name = 'test_name' args.author = 'TestAuthor' args.full_name = None args.tags = None args.description = None expected_manifest = { 'Format': 'Heat.HOT/1.0', 'Type': 'Application', 'FullName': 'io.murano.apps.generated.TestName', 'Name': 'test_name', 'Description': 'Heat-defined application ' 'for a template "heat-template.yaml"', 'Author': 'TestAuthor', 'Tags': ['Heat-generated'] } result_manifest = hot_package.generate_manifest(args) self.check_dict_is_subset(expected_manifest, result_manifest) def test_generate_hot_manifest_nonexistent_template(self): args = TestArgs() args.template = '/home/this/path/does/not/exist' self.assertRaises(exceptions.CommandError, hot_package.generate_manifest, args) def test_generate_hot_manifest_with_all_parameters(self): args = TestArgs() args.template = TEMPLATE args.name = 'test_name' args.author = 'TestAuthor' args.full_name = 'test.full.name.TestName' args.tags = ['test', 'tag', 'Heat'] args.description = 'Test description' expected_manifest = { 'Format': 'Heat.HOT/1.0', 'Type': 'Application', 'FullName': 'test.full.name.TestName', 'Name': 'test_name', 'Description': 'Test description', 'Author': 'TestAuthor', 'Tags': ['test', 'tag', 'Heat'] } result_manifest = hot_package.generate_manifest(args) self.check_dict_is_subset(expected_manifest, result_manifest) def test_generate_hot_manifest_template_not_yaml(self): args = TestArgs() args.template = LOGO args.name = None args.full_name = None self.assertRaises(exceptions.CommandError, hot_package.generate_manifest, args) def test_prepare_hot_package(self): args = TestArgs() args.template = TEMPLATE args.name = 'test_name' args.author = 'TestAuthor' args.full_name = 'test.full.name.TestName' args.tags = 'test, tag, Heat' args.description = 'Test description' args.resources_dir = RESOURCES_DIR args.logo = LOGO package_dir = hot_package.prepare_package(args) prepared_files = ['manifest.yaml', 'logo.png', 'template.yaml', 'Resources'] self.assertEqual(sorted(prepared_files), sorted(os.listdir(package_dir))) shutil.rmtree(package_dir) def test_generate_mpl_manifest(self): args = TestArgs() args.template = TEMPLATE args.classes_dir = CLASSES_DIR args.resources_dir = RESOURCES_DIR args.type = 'Application' args.author = 'TestAuthor' args.name = None args.full_name = None args.tags = None args.description = None expected_manifest = { 'Format': 'MuranoPL/1.0', 'Type': 'Application', 'Classes': {'io.murano.apps.test.APP': 'testapp.yaml'}, 'FullName': 'io.murano.apps.test.APP', 'Name': 'APP', 'Description': 'Description for the application is not provided', 'Author': 'TestAuthor', } result_manifest = mpl_package.generate_manifest(args) self.check_dict_is_subset(expected_manifest, result_manifest) def test_generate_mpl_manifest_with_all_parameters(self): args = TestArgs() args.template = TEMPLATE args.classes_dir = CLASSES_DIR args.resources_dir = RESOURCES_DIR args.type = 'Application' args.name = 'test_name' args.author = 'TestAuthor' args.full_name = 'test.full.name.TestName' args.tags = ['test', 'tag', 'Heat'] args.description = 'Test description' expected_manifest = { 'Format': 'MuranoPL/1.0', 'Type': 'Application', 'Classes': {'io.murano.apps.test.APP': 'testapp.yaml'}, 'FullName': 'test.full.name.TestName', 'Name': 'test_name', 'Description': 'Test description', 'Author': 'TestAuthor', 'Tags': ['test', 'tag', 'Heat'] } result_manifest = mpl_package.generate_manifest(args) self.check_dict_is_subset(expected_manifest, result_manifest) def test_generate_mpl_wrong_classes_dir(self): args = TestArgs() args.classes_dir = '/home/this/path/does/not/exist' expected = ("'--classes-dir' parameter should be a directory", ) try: mpl_package.generate_manifest(args) except exceptions.CommandError as message: self.assertEqual(expected, message.args) def test_prepare_mpl_wrong_resources_dir(self): args = TestArgs() args.template = TEMPLATE args.classes_dir = CLASSES_DIR args.resources_dir = '/home/this/path/does/not/exist' args.type = 'Application' args.name = 'Test' args.tags = '' args.ui = UI args.logo = LOGO args.full_name = 'test.full.name.TestName' args.author = 'TestAuthor' args.description = 'Test description' expected = ("'--resources-dir' parameter should be a directory", ) try: mpl_package.prepare_package(args) except exceptions.CommandError as message: self.assertEqual(expected, message.args) def test_prepare_mpl_package(self): args = TestArgs() args.template = TEMPLATE args.classes_dir = CLASSES_DIR args.resources_dir = RESOURCES_DIR args.type = 'Application' args.name = 'test_name' args.author = 'TestAuthor' args.full_name = 'test.full.name.TestName' args.tags = 'test, tag, Heat' args.description = 'Test description' args.ui = UI args.logo = LOGO prepared_files = ['UI', 'Classes', 'manifest.yaml', 'Resources', 'logo.png'] package_dir = mpl_package.prepare_package(args) self.assertEqual(sorted(prepared_files), sorted(os.listdir(package_dir))) shutil.rmtree(package_dir) python-muranoclient-1.3.0/muranoclient/tests/unit/test_base.py0000664000175000017500000000350313523272253024725 0ustar zuulzuul00000000000000# Copyright 2015 Huawei. # 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 muranoclient.common import base from muranoclient.v1 import packages class BaseTest(testtools.TestCase): def test_two_resources_with_same_id_are_not_equal(self): # Two resources with same ID: never equal if their info is not equal r1 = base.Resource(None, {'id': 1, 'name': 'hi'}) r2 = base.Resource(None, {'id': 1, 'name': 'hello'}) self.assertNotEqual(r1, r2) def test_two_resources_with_same_id_and_info_are_equal(self): # Two resources with same ID: equal if their info is equal r1 = base.Resource(None, {'id': 1, 'name': 'hello'}) r2 = base.Resource(None, {'id': 1, 'name': 'hello'}) self.assertEqual(r1, r2) def test_two_resources_with_diff_type_are_not_equal(self): # Two resources of different types: never equal r1 = base.Resource(None, {'id': 1}) r2 = packages.Package(None, {'id': 1}) self.assertNotEqual(r1, r2) def test_two_resources_with_no_id_are_equal(self): # Two resources with no ID: equal if their info is equal r1 = base.Resource(None, {'name': 'joe', 'age': 12}) r2 = base.Resource(None, {'name': 'joe', 'age': 12}) self.assertEqual(r1, r2) python-muranoclient-1.3.0/muranoclient/tests/unit/test_methods.py0000664000175000017500000002772513523272253025472 0ustar zuulzuul00000000000000# Copyright (c) 2013 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 six import testtools from muranoclient import client from muranoclient.v1 import actions from muranoclient.v1 import deployments import muranoclient.v1.environments as environments from muranoclient.v1 import packages import muranoclient.v1.sessions as sessions from muranoclient.v1 import static_actions import muranoclient.v1.templates as templates def my_mock(*a, **b): return [a, b] api = mock.MagicMock(json_request=my_mock) class UnitTestsForClassesAndFunctions(testtools.TestCase): def test_create_client_instance(self): endpoint = 'http://no-resolved-host:8001' test_client = client.Client('1', endpoint=endpoint, token='1', timeout=10) self.assertIsNotNone(test_client.environments) self.assertIsNotNone(test_client.sessions) self.assertIsNotNone(test_client.services) def test_env_manager_list(self): manager = environments.EnvironmentManager(api) result = manager.list() self.assertEqual([], result) def test_env_manager_create(self): manager = environments.EnvironmentManager(api) result = manager.create({'name': 'test'}) self.assertEqual({'name': 'test'}, result.data) def test_env_manager_create_with_named_parameters(self): manager = environments.EnvironmentManager(api) result = manager.create(data={'name': 'test'}) self.assertEqual({'name': 'test'}, result.data) def test_env_manager_create_negative_without_parameters(self): manager = environments.EnvironmentManager(api) self.assertRaises(TypeError, manager.create) def test_env_manager_delete(self): manager = environments.EnvironmentManager(api) result = manager.delete('test') self.assertIsNone(result) def test_env_manager_delete_with_named_parameters(self): manager = environments.EnvironmentManager(api) result = manager.delete(environment_id='1') self.assertIsNone(result) def test_env_manager_delete_negative_without_parameters(self): manager = environments.EnvironmentManager(api) self.assertRaises(TypeError, manager.delete) def test_env_manager_update(self): manager = environments.EnvironmentManager(api) result = manager.update('1', 'test') self.assertEqual({'name': 'test'}, result.data) def test_env_manager_update_with_named_parameters(self): manager = environments.EnvironmentManager(api) result = manager.update(environment_id='1', name='test') self.assertEqual({'name': 'test'}, result.data) def test_env_manager_update_negative_with_one_parameter(self): manager = environments.EnvironmentManager(api) self.assertRaises(TypeError, manager.update, 'test') def test_env_manager_update_negative_without_parameters(self): manager = environments.EnvironmentManager(api) self.assertRaises(TypeError, manager.update) def test_env_manager_get(self): manager = environments.EnvironmentManager(api) result = manager.get('test') self.assertIsNotNone(result.manager) def test_env(self): environment = environments.Environment(api, api) self.assertIsNotNone(environment.data()) def test_session_manager_delete(self): manager = sessions.SessionManager(api) result = manager.delete('datacenter1', 'session1') self.assertIsNone(result) def test_session_manager_delete_with_named_parameters(self): manager = sessions.SessionManager(api) result = manager.delete(environment_id='datacenter1', session_id='session1') self.assertIsNone(result) def test_session_manager_delete_negative_with_one_parameter(self): manager = sessions.SessionManager(api) self.assertRaises(TypeError, manager.delete, 'datacenter1') def test_session_manager_delete_negative_without_parameters(self): manager = sessions.SessionManager(api) self.assertRaises(TypeError, manager.delete) def test_session_manager_get(self): manager = sessions.SessionManager(api) result = manager.get('datacenter1', 'session1') # WTF? self.assertIsNotNone(result.manager) def test_session_manager_configure(self): manager = sessions.SessionManager(api) result = manager.configure('datacenter1') self.assertIsNotNone(result) def test_session_manager_configure_with_named_parameter(self): manager = sessions.SessionManager(api) result = manager.configure(environment_id='datacenter1') self.assertIsNotNone(result) def test_session_manager_configure_negative_without_parameters(self): manager = sessions.SessionManager(api) self.assertRaises(TypeError, manager.configure) def test_session_manager_deploy(self): manager = sessions.SessionManager(api) result = manager.deploy('datacenter1', '1') self.assertIsNone(result) def test_session_manager_deploy_with_named_parameters(self): manager = sessions.SessionManager(api) result = manager.deploy(environment_id='datacenter1', session_id='1') self.assertIsNone(result) def test_session_manager_deploy_negative_with_one_parameter(self): manager = sessions.SessionManager(api) self.assertRaises(TypeError, manager.deploy, 'datacenter1') def test_session_manager_deploy_negative_without_parameters(self): manager = sessions.SessionManager(api) self.assertRaises(TypeError, manager.deploy) def test_action_manager_call(self): api_mock = mock.MagicMock( json_request=lambda *args, **kwargs: (None, {'task_id': '1234'})) manager = actions.ActionManager(api_mock) result = manager.call('testEnvId', 'testActionId', ['arg1', 'arg2']) self.assertEqual('1234', result) def test_package_filter_pagination_next_marker(self): # ``PackageManager.filter`` handles `next_marker` parameter related # to pagination in API correctly. responses = [ {'next_marker': 'test_marker', 'packages': [{'name': 'test_package_1'}]}, {'packages': [{'name': 'test_package_2'}]} ] def json_request(url, method, *args, **kwargs): self.assertIn('/v1/catalog/packages', url) return mock.MagicMock(), responses.pop(0) api = mock.MagicMock() api.configure_mock(**{'json_request.side_effect': json_request}) manager = packages.PackageManager(api) list(manager.filter()) self.assertEqual(2, api.json_request.call_count) def test_package_filter_encoding_good(self): responses = [ {'next_marker': 'test_marker', 'packages': [{'name': 'test_package_1'}]}, {'packages': [{'name': 'test_package_2'}]} ] def json_request(url, method, *args, **kwargs): self.assertIn('category=%D0%BF%D0%B8%D0%B2%D0%BE', url) return mock.MagicMock(), responses.pop(0) api = mock.MagicMock() api.configure_mock(**{'json_request.side_effect': json_request}) manager = packages.PackageManager(api) category = six.b('\xd0\xbf\xd0\xb8\xd0\xb2\xd0\xbe') kwargs = {'category': category.decode('utf-8')} list(manager.filter(**kwargs)) self.assertEqual(2, api.json_request.call_count) def test_action_manager_get_result(self): api_mock = mock.MagicMock( json_request=lambda *args, **kwargs: (None, {'a': 'b'})) manager = actions.ActionManager(api_mock) result = manager.get_result('testEnvId', '1234') self.assertEqual({'a': 'b'}, result) def test_static_action_manager_call(self): api_mock = mock.MagicMock( json_request=lambda *args, **kwargs: (None, 'result')) manager = static_actions.StaticActionManager(api_mock) args = {'className': 'cls', 'methodName': 'method'} result = manager.call(args).get_result() self.assertEqual('result', result) def test_env_template_manager_list(self): """Tests the list of environment templates.""" manager = templates.EnvTemplateManager(api) result = manager.list() self.assertEqual([], result) def test_env_template_manager_create(self): manager = templates.EnvTemplateManager(api) result = manager.create({'name': 'test'}) self.assertEqual({'name': 'test'}, result.data) def test_env_template_manager_create_with_named_parameters(self): manager = templates.EnvTemplateManager(api) result = manager.create(data={'name': 'test'}) self.assertEqual({'name': 'test'}, result.data) def test_env_template_manager_create_negative_without_parameters(self): manager = templates.EnvTemplateManager(api) self.assertRaises(TypeError, manager.create) def test_env_template_manager_delete(self): manager = templates.EnvTemplateManager(api) result = manager.delete('test') self.assertIsNone(result) def test_env_template_manager_delete_with_named_parameters(self): manager = templates.EnvTemplateManager(api) result = manager.delete(env_template_id='1') self.assertIsNone(result) def test_env_template_manager_delete_negative_without_parameters(self): manager = templates.EnvTemplateManager(api) self.assertRaises(TypeError, manager.delete) def test_env_template_manager_update(self): manager = templates.EnvTemplateManager(api) result = manager.update('1', 'test') self.assertEqual({'name': 'test'}, result.data) def test_env_template_manager_update_with_named_parameters(self): manager = templates.EnvTemplateManager(api) result = manager.update(env_template_id='1', name='test') self.assertEqual({'name': 'test'}, result.data) def test_env_template_manager_update_negative_with_one_parameter(self): manager = templates.EnvTemplateManager(api) self.assertRaises(TypeError, manager.update, 'test') def test_env_template_manager_update_negative_without_parameters(self): manager = templates.EnvTemplateManager(api) self.assertRaises(TypeError, manager.update) def test_env_template_manager_get(self): manager = templates.EnvTemplateManager(api) result = manager.get('test') self.assertIsNotNone(result.manager) def test_deployment_manager_list(self): manager = deployments.DeploymentManager(api) manager._list = mock.Mock(return_value=mock.sentinel.deployments) result = manager.list(mock.sentinel.environment_id) self.assertEqual(mock.sentinel.deployments, result) manager._list.assert_called_once_with( '/v1/environments/sentinel.environment_id/deployments', 'deployments') def test_deployment_manager_list_all_environments(self): manager = deployments.DeploymentManager(api) manager._list = mock.Mock(return_value=mock.sentinel.deployments) result = manager.list(mock.sentinel.environment_id, all_environments=True) self.assertEqual(mock.sentinel.deployments, result) manager._list.assert_called_once_with('/v1/deployments', 'deployments') python-muranoclient-1.3.0/muranoclient/tests/unit/fixture_data/0000775000175000017500000000000013523272337025063 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/tests/unit/fixture_data/test-app/0000775000175000017500000000000013523272337026620 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/tests/unit/fixture_data/test-app/Classes/0000775000175000017500000000000013523272337030215 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/tests/unit/fixture_data/test-app/Classes/testapp.yaml0000664000175000017500000000136113523272253032557 0ustar zuulzuul00000000000000Namespaces: =: io.murano.apps.test std: io.murano res: io.murano.resources Name: APP Extends: std:Application Properties: name: Contract: $.string().notNull() instance: Contract: $.class(res:Instance).notNull() Workflow: initialize: Body: - $.environment: $.find(std:Environment).require() deploy: Body: - $securityGroupIngress: - ToPort: 23 FromPort: 23 IpProtocol: tcp External: True - $.environment.securityGroupManager.addGroupIngress($securityGroupIngress) - $.instance.deploy() - $resources: new('io.murano.system.Resources') - $template: $resources.yaml('Deploy.template') - $.instance.agent.call($template, $resources) python-muranoclient-1.3.0/muranoclient/tests/unit/fixture_data/test-app/ui.yaml0000664000175000017500000000001313523272253030110 0ustar zuulzuul00000000000000Version: 2 python-muranoclient-1.3.0/muranoclient/tests/unit/fixture_data/test-app/Resources/0000775000175000017500000000000013523272337030572 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/tests/unit/fixture_data/test-app/Resources/Deploy.template0000664000175000017500000000051313523272253033557 0ustar zuulzuul00000000000000FormatVersion: 2.0.0 Version: 1.0.0 Name: Deploy Parameters: appName: $appName Body: | return deploy(args.appName).stdout Scripts: deploy: Type: Application Version: 1.0.0 EntryPoint: deploy.sh Files: - installer.sh - common.sh Options: captureStdout: true captureStderr: false python-muranoclient-1.3.0/muranoclient/tests/unit/fixture_data/test-app/Resources/scripts/0000775000175000017500000000000013523272337032261 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/tests/unit/fixture_data/test-app/Resources/scripts/deploy.sh0000664000175000017500000000001413523272253034101 0ustar zuulzuul00000000000000#!/bin/bash ././@LongLink0000000000000000000000000000014700000000000011217 Lustar 00000000000000python-muranoclient-1.3.0/muranoclient/tests/unit/fixture_data/test-app/Resources/scripts/installer.shpython-muranoclient-1.3.0/muranoclient/tests/unit/fixture_data/test-app/Resources/scripts/installer.0000664000175000017500000000001413523272253034247 0ustar zuulzuul00000000000000#!/bin/bash python-muranoclient-1.3.0/muranoclient/tests/unit/fixture_data/test-app/Resources/scripts/common.sh0000664000175000017500000000001413523272253034075 0ustar zuulzuul00000000000000#!/bin/bash python-muranoclient-1.3.0/muranoclient/tests/unit/fixture_data/empty-app/0000775000175000017500000000000013523272337026777 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/tests/unit/fixture_data/empty-app/manifest.yaml0000664000175000017500000000123413523272253031466 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. Format: 1.3 Type: Application FullName: empty Name: empty Description: empty description Author: 'Mirantis, Inc' python-muranoclient-1.3.0/muranoclient/tests/unit/fixture_data/heat-template.yaml0000664000175000017500000000004213523272253030472 0ustar zuulzuul00000000000000heat_template_version: 2013-05-23 python-muranoclient-1.3.0/muranoclient/tests/unit/fixture_data/logo.png0000664000175000017500000002656213523272253026541 0ustar zuulzuul00000000000000PNG  IHDR22?NiCCPICC ProfileX YgTsF2H"9Ee\ ""AE$ PfQ0!$(*b$׋}=yꞞZԘȨ8(q@pRccmjgӢGâ"ASBf.P9aAVùBC\= D3u0rd,D5{Bz\%ñm@g`o>XHL1]?9&bu}C؁ | U .ދ0n@՚^!g}L#=&t>?-0@D09u} cA P-3c' . |QP'8@*ބC$8mΟq,xK9AC"};l=;| ?تߞn͠aײ~E%F, LIA8cq@=1@4Or'GZ_>31kf+8"V P?5l2v=@k6lws^h8!YI?J:5?`D 5pUZQUeu{:]oΫ{5*Ȣ>P[ dRy<-=>|@*k\"!Dv,bpu{9\o0 f" Dp |"#6!3"H ;<r9 !҃—Og-~@ p Z"̈́(aH$zD"G""#6{o?HL$*ɜM"eJHgH^8iAaCCC>CM. lҌza;Kk2`ĴIɉ))LdvلC'#&7Q()!śGGR)?931_ce"bIJ%2K+ +5  [$3l&؉Rf8 ?N*o9 ҜVay9;9عԹܹrqƸ#/qp/ .]{ɏwO?JS@W_ W *(',MRCpFHXB(FPД0pppHHH{Q.Q#R6i1A1Kxbbk׸XS8xxxDY!IImɃ%礤+B"c EBO -.{D[Ӑ +G5CůYbYH!AkEnE[ 늟$ +TPPR~®brS嫪jjE\mZuy@O5844vkh,kji4k5'$|ʵ9h?liЙ_.nݥuutuN^j= }Q}_#bT 7 ǍdŒ}2V6_53YgjlZv66_cl~|BbE%ƲrJߪjZ:պ͆lbs捭-jgmWh^>pr(tx(ѩ靳s}.g\f]]]ɸŻ׸yzTL|UMv>=lC>>Y>7npM7ln=DuVPgM0 ( <8t h"X/0x2 $d*$p0˰casW"<""I7أ£ڢFdŌlYx4͆v*[ ?;ewſNO(Kxy+֨IrI9I'okIKّ:(Dֲ]|{;wxq N7323GwY:ŜEܭX6.;43G-P܀<弒={-ݻ/h_gfQ H>0ZhWxH({%%2?8Rj[ZHCKCՕ  8{h1cyzĵ JBeB*'O֜?wjtj3gϢgN9}|}B: B}/\rYr+W9^C%]r}ޫ7RuA6Fƕ䦙;wF[6֡q㫝׺uoi5ɽ>G=nO}G?S0{`phwt!eKm:x߉ɪ藺\r5g|&+:EKj]^;7Zo6ߺpv]cMSe;-)maw]i?`aWGw7tuvRz<)  V?Mxf8DחƋúã|x}(6Z766wMc&&WRX3ӎᄂ|z[l\Gc wWVV_a">I#)0H3J3!+S1;cobf]dR .zBisI&)i +Y_$µ:'q*bռ44nh?yzaQnӏ$ !KU+3k7(i;w9e9gquH r6`c{Sb vW˃{(CºoFDEGgж cEb'd%m5KRN&—ʕƺ}6͎;3wm̲mmCݑwrϽ>+9pcTɧ?)*?uoOxVSd㩆ψx: 5UZޛ[ ojNׅO&(&'I! J LȉgfUfYgl5\}t8lQc:uNhT(UT;v$swמ{rae+\Q~s[Ѣɯy-V7ypOG˝] ?Qi!/^1N˛Hhuvމ>jowABg$#\u  ^0Ӵ0s r`Fy4^XYBY懱^EFQCt7zLÒjlG­E*qx!!BOIb "C5 9&{j2GnHRr(]`S Xyuƹ˙k;S+[g7-"0*xLOX^=b5%"mde)rUHSQQTVyzF-G=DZSQKkE^wtMϚ3H$+k CcS[WGq6qʛ7VQ;~H nY3 ?1ݻŜ_bmݩ22mw.Y{wO>W _)=xR 5)׎\|Wՙ[]_[\3fEku@VkF-k B(UueQB XU7(FmQzmE?c acR#x=6-J"$~% It /1y2]0GѡgfbD"Et0dtCCqc`cifaeϮ#Ye;Gw Kzbky%_>KYcXk૘tD^7u~ + V: zDhƸД۬ۊͺ}ː{ד A>?6E1~Z !;+#AuZtp|kb|ROiUI;22],jٱ9}yn{&eW,xV][r#i]aP|T{cMYsݵu7.\:xt5R}M[ݷ34'[zHchg/A+C^8x?&c {1N|}'IG333gCfg&7?@YY]X,]dT|'dm }c`WVI@<rbr%L6^2Wt.ןN? #q pHYs  iTXtXML:com.adobe.xmp 1440 900 u IDAThݚyp]}ǿMzZےก2Cmh'ҖL)Sht&SBC3SR4JRL0 v^d˖-ɲ{' 1t޹Y~~S455Ŀ,\Ԥ=cD3,SQuVޅB^qּU֪.0?x=՛$r&DMu5='Ӂ*IYʗPeU/RQTյNz/EZ aG|F`&_W6'Vەe*T*|h ceQ)o4UJ8S#ZLKRi\iꦘ!E$> }ꕢ*%JPTZΫQ)mDi6ATS植\&j4hJTt!v\G-o95K =C5}KbQY\ WQPIb0,RB5INT&E m^e*|N;M{ OIdNk4+Qy|T ϰ8_h4]H+#V\L*G*5dG'&5@$ G:U5խk"}.[懃\u]=ژJ{<% :( R4g)Bm]&uPU9 /GVIi}'S\PyP~ ̟E93ԐeMC:ڝ>ĈL3e)W#Liw"m+mےT%]t{TW= ] { "t _)ʽ[UApLFHr+$$^p?ǴwX)>pI a)x7J 8T.UQO5=cm(dC>z@uW@xx7DMɲ IxRP2 qCTW`DLHę̌EzO-{t%K[6frpgܡUD}Ge<*BIQkQtX h)3}LCTAJy$ـD8> 2%3zyTq4%wx jdd LHˋ%]!}rǭ[Z}up("R8eR 0d!3^!4FfNbKVdD nbhIH{UFz @v^ćYh8!8\qo}I&L3ueS1}Ԧ*u@ {h396t{QE (iG|RBʌ$0-ՠsc8&]Ʉ_.~NLUV @n? f,"(=dZ.N л;ekc"TEPȜ57a98:Re:zPy3ŢLb60 vTh*q:i4/I1OӾ1;}< M`jv)*%vX3IDLmRCJ⡬2M+ ,k栾s8 1׆ 'P&c RS݅ɰ1k1TL`4 2:F9wBb0S_ߒc݀.ށ#L ĥg=z`Mt!8B37Y6b.'NL'>zh^ ё9 +3G . J:}I>6U^p/ ZjDFj6Mnz`8/.H{;S A%` k^[38`3x ^l`ۤմƾcXxC)*XlދC;g \9W391NX^'́wq @z%nvf eU]8g/`7"{D a;-4ggP3iH'c\pKДt019s!%YuTbπ-R!¼o׾AQ Zy=H^rl۝&w?9ob1lNI\}3³H X{j \.OV<8ժ iht:ᆭœ̩%罨ОXCs \떺7vrڑor@"(K׵hSr 2Ϋf }?x޳IE?|[Ɛ g[:s^Cxr޹A> cM瑌OPlmzu`BǑ" />Ë !Nxq )E3e|2GkJ3I߮{Y<8ӝu@_g/ܯg!Axۺ-&7 O7X$0ajZjWH ISf Bw{ͿԿ_ާhP;@؋Qp>T)_̵z~ևCf 52 :×! twX:H"})GJם! qKwݧ Z0'p"C:@m xfW^qcg(,zen?eo{DŽV>Wµ8m}kP+u~mͷ)ھ/rG@b^ERqV$?\y9Ld~ȼjͽ+I+O#>ޔ8ڛ& 5iP:b^/hS ~WA4i f6Ksg) *{_~Xa exxDvgsv_z3 &gO52e$\֓H1Nww;7jiYdN-s*׽D)A~6gnKՎGJHlHmx *î"R+A`tw/DC elHOpY>fda^08AP)9\{k]7^zz8L^@gY5dKO?DN@8D܂VzY@I>8:>ߍQG1ǙxnrkQ9p&)T_:Džϭ&Y5o# {PBoyRoi?!ׁj%$D5sGP<?ѨKԺH;@! `}eiƨ֮)ޅCg!^; ޵k>[ݻIN,[5LpAYLH.8oۋ;OXǴwvA ـ]`0sZ0 -P 3NY?g̢'`G7?'ڦݣ~5cڛ_P0ߑ.շ[rI;k4yG OL s-%s6^a۶?]4::'.;<aŠl⺻;w[u֪JXVS[-o[CyS&$V00IENDB`python-muranoclient-1.3.0/muranoclient/tests/unit/base.py0000664000175000017500000000320113523272253023661 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 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 dict1.items())) python-muranoclient-1.3.0/muranoclient/tests/unit/test_common_http.py0000664000175000017500000004443013523272253026346 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 muranoclient.common import exceptions as exc from muranoclient.common import http from muranoclient.tests.unit import fakes @mock.patch('muranoclient.common.http.requests.request') class HttpClientTest(testtools.TestCase): def test_http_raw_request(self, mock_request): headers = {'User-Agent': 'python-muranoclient'} mock_request.return_value = \ fakes.FakeHTTPResponse( 200, 'OK', {}, '') client = http.HTTPClient('http://example.com:8082') 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:8082', 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:8082') 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:8082', allow_redirects=False, headers={'User-Agent': 'python-muranoclient'}), mock.call('GET', 'http://example.com:8082', allow_redirects=False, headers={'User-Agent': 'python-muranoclient', 'X-Auth-Key': 'pass', 'X-Auth-User': 'user'}), mock.call('GET', 'http://example.com:8082', allow_redirects=False, headers={'User-Agent': 'python-muranoclient', '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:8082') client.region_name = 'RegionOne' resp = client.request('', 'GET') self.assertEqual(200, resp.status_code) mock_request.assert_called_once_with( 'GET', 'http://example.com:8082', allow_redirects=False, headers={'X-Region-Name': 'RegionOne', 'User-Agent': 'python-muranoclient'}) def test_http_json_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:8082') resp, body = client.json_request('', 'GET') self.assertEqual(200, resp.status_code) self.assertEqual({}, body) mock_request.assert_called_once_with( 'GET', 'http://example.com:8082', allow_redirects=False, headers={'Content-Type': 'application/json', 'User-Agent': 'python-muranoclient'}) def test_http_json_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:8082') 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.json_request('', 'GET', data='text') self.assertEqual(200, resp.status_code) self.assertEqual({}, body) mock_request.assert_called_once_with( 'GET', 'http://example.com:8082', allow_redirects=False, cert=('RANDOM_CERT_FILE', 'RANDOM_KEY_FILE'), verify=True, data='"text"', headers={'Content-Type': 'application/json', 'X-Auth-Url': 'http://AUTH_URL', 'User-Agent': 'python-muranoclient'}) def test_http_json_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:8082') resp, body = client.json_request('', 'GET', data='test-body') self.assertEqual(200, resp.status_code) self.assertEqual({}, body) mock_request.assert_called_once_with( 'GET', 'http://example.com:8082', data='"test-body"', allow_redirects=False, headers={'Content-Type': 'application/json', 'User-Agent': 'python-muranoclient'}) def test_http_json_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:8082') resp, body = client.json_request('', 'GET', data='test-data') self.assertEqual(200, resp.status_code) self.assertIsNone(body) mock_request.assert_called_once_with( 'GET', 'http://example.com:8082', data='"test-data"', allow_redirects=False, headers={'Content-Type': 'application/json', 'User-Agent': 'python-muranoclient'}) def test_http_json_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:8082') resp, body = client.json_request('', 'GET') self.assertEqual(200, resp.status_code) self.assertEqual('invalid-json', body) mock_request.assert_called_once_with( 'GET', 'http://example.com:8082', allow_redirects=False, headers={'Content-Type': 'application/json', 'User-Agent': 'python-muranoclient'}) def test_http_manual_redirect_delete(self, mock_request): mock_request.side_effect = [ fakes.FakeHTTPResponse( 302, 'Found', {'location': 'http://example.com:8082/foo/bar'}, ''), fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, '{}')] client = http.HTTPClient('http://example.com:8082/foo') resp, body = client.json_request('', 'DELETE') self.assertEqual(200, resp.status_code) mock_request.assert_has_calls([ mock.call('DELETE', 'http://example.com:8082/foo', allow_redirects=False, headers={'Content-Type': 'application/json', 'User-Agent': 'python-muranoclient'}), mock.call('DELETE', 'http://example.com:8082/foo/bar', allow_redirects=False, headers={'Content-Type': 'application/json', 'User-Agent': 'python-muranoclient'}) ]) def test_http_manual_redirect_post(self, mock_request): mock_request.side_effect = [ fakes.FakeHTTPResponse( 302, 'Found', {'location': 'http://example.com:8082/foo/bar'}, ''), fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, '{}')] client = http.HTTPClient('http://example.com:8082/foo') resp, body = client.json_request('', 'POST') self.assertEqual(200, resp.status_code) mock_request.assert_has_calls([ mock.call('POST', 'http://example.com:8082/foo', allow_redirects=False, headers={'Content-Type': 'application/json', 'User-Agent': 'python-muranoclient'}), mock.call('POST', 'http://example.com:8082/foo/bar', allow_redirects=False, headers={'Content-Type': 'application/json', 'User-Agent': 'python-muranoclient'}) ]) def test_http_manual_redirect_put(self, mock_request): mock_request.side_effect = [ fakes.FakeHTTPResponse( 302, 'Found', {'location': 'http://example.com:8082/foo/bar'}, ''), fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, '{}')] client = http.HTTPClient('http://example.com:8082/foo') resp, body = client.json_request('', 'PUT') self.assertEqual(200, resp.status_code) mock_request.assert_has_calls([ mock.call('PUT', 'http://example.com:8082/foo', allow_redirects=False, headers={'Content-Type': 'application/json', 'User-Agent': 'python-muranoclient'}), mock.call('PUT', 'http://example.com:8082/foo/bar', allow_redirects=False, headers={'Content-Type': 'application/json', 'User-Agent': 'python-muranoclient'}) ]) def test_http_manual_redirect_prohibited(self, mock_request): mock_request.return_value = \ fakes.FakeHTTPResponse( 302, 'Found', {'location': 'http://example.com:8082/'}, '') client = http.HTTPClient('http://example.com:8082/foo') self.assertRaises(exc.InvalidEndpoint, client.json_request, '', 'DELETE') mock_request.assert_called_once_with( 'DELETE', 'http://example.com:8082/foo', allow_redirects=False, headers={'Content-Type': 'application/json', 'User-Agent': 'python-muranoclient'}) 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:8082/foo') self.assertRaises(exc.InvalidEndpoint, client.json_request, '', 'DELETE') mock_request.assert_called_once_with( 'DELETE', 'http://example.com:8082/foo', allow_redirects=False, headers={'Content-Type': 'application/json', 'User-Agent': 'python-muranoclient'}) def test_http_json_request_redirect(self, mock_request): # Record the 302 mock_request.side_effect = [ fakes.FakeHTTPResponse( 302, 'Found', {'location': 'http://example.com:8082'}, ''), fakes.FakeHTTPResponse( 200, 'OK', {'content-type': 'application/json'}, '{}')] client = http.HTTPClient('http://example.com:8082') resp, body = client.json_request('', 'GET') self.assertEqual(200, resp.status_code) self.assertEqual({}, body) mock_request.assert_has_calls([ mock.call('GET', 'http://example.com:8082', allow_redirects=False, headers={'Content-Type': 'application/json', 'User-Agent': 'python-muranoclient'}), mock.call('GET', 'http://example.com:8082', allow_redirects=False, headers={'Content-Type': 'application/json', 'User-Agent': 'python-muranoclient'}) ]) def test_http_404_json_request(self, mock_request): mock_request.return_value = \ fakes.FakeHTTPResponse( 404, 'Not Found', {'content-type': 'application/json'}, '{}') client = http.HTTPClient('http://example.com:8082') e = self.assertRaises(exc.HTTPNotFound, client.json_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:8082', allow_redirects=False, headers={'Content-Type': 'application/json', 'User-Agent': 'python-muranoclient'}) def test_http_300_json_request(self, mock_request): mock_request.return_value = \ fakes.FakeHTTPResponse( 300, 'OK', {'content-type': 'application/json'}, '{}') client = http.HTTPClient('http://example.com:8082') e = self.assertRaises( exc.HTTPMultipleChoices, client.json_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:8082', allow_redirects=False, headers={'Content-Type': 'application/json', 'User-Agent': 'python-muranoclient'}) def test_fake_json_request(self, mock_request): headers = {'User-Agent': 'python-muranoclient'} mock_request.side_effect = [socket.gaierror] client = http.HTTPClient('fake://example.com:8082') self.assertRaises(exc.InvalidEndpoint, client.request, "/", "GET") mock_request.assert_called_once_with('GET', 'fake://example.com:8082/', allow_redirects=False, headers=headers) def test_http_request_socket_error(self, mock_request): headers = {'User-Agent': 'python-muranoclient'} mock_request.side_effect = [socket.gaierror] client = http.HTTPClient('http://example.com:8082') self.assertRaises(exc.InvalidEndpoint, client.request, "/", "GET") mock_request.assert_called_once_with('GET', 'http://example.com:8082/', allow_redirects=False, headers=headers) def test_http_request_socket_timeout(self, mock_request): headers = {'User-Agent': 'python-muranoclient'} mock_request.side_effect = [socket.timeout] client = http.HTTPClient('http://example.com:8082') self.assertRaises(exc.CommunicationError, client.request, "/", "GET") mock_request.assert_called_once_with('GET', 'http://example.com:8082/', 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:8082', timeout='123') resp, body = client.json_request('', 'GET') self.assertEqual(200, resp.status_code) self.assertEqual({}, body) mock_request.assert_called_once_with( 'GET', 'http://example.com:8082', allow_redirects=False, headers={'Content-Type': 'application/json', 'User-Agent': 'python-muranoclient'}, 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 = http.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('muranoclient.common.http.get_system_ca_file') as gsf: gsf.return_value = "SOMEWHERE" client = http.HTTPClient('https://foo') self.assertEqual("SOMEWHERE", client.verify_cert) # def test_curl_log_i18n_headers(self, mock_request): # self.m.StubOutWithMock(logging.Logger, 'debug') # kwargs = {'headers': {'Key': b'foo\xe3\x8a\x8e'}} # # mock_logging_debug = logging.Logger.debug( # u"curl -i -X GET -H 'Key: foo㊎' http://somewhere" # ) # mock_logging_debug.AndReturn(None) # # self.m.ReplayAll() # # client = http.HTTPClient('http://somewhere') # client.log_curl_request('', "GET", kwargs=kwargs) # # self.m.VerifyAll() python-muranoclient-1.3.0/muranoclient/tests/unit/osc/0000775000175000017500000000000013523272337023170 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/tests/unit/osc/test_plugin.py0000664000175000017500000000230613523272253026075 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 muranoclient.osc import plugin from muranoclient.tests.unit import base class TestApplicationCatalogPlugin(base.TestCaseShell): @mock.patch("muranoclient.v1.client.Client") def test_make_client(self, p_client): instance = mock.Mock() instance._api_version = {"application_catalog": '1'} instance._region_name = 'murano_region' instance.session = 'murano_session' plugin.make_client(instance) p_client.assert_called_with( mock.ANY, region_name='murano_region', session='murano_session', service_type='application-catalog') python-muranoclient-1.3.0/muranoclient/tests/unit/osc/v1/0000775000175000017500000000000013523272337023516 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/tests/unit/osc/v1/test_schema.py0000664000175000017500000000406013523272253026364 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 muranoclient.osc.v1 import schema as osc_schema from muranoclient.tests.unit.osc.v1 import fakes from muranoclient.v1 import schemas as api_schemas SAMPLE_CLASS_SCHEMA = { '': { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": {} }, 'modelBuilder': { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": {} } } class TestSchema(fakes.TestApplicationCatalog): def setUp(self): super(TestSchema, self).setUp() self.schemas_mock = \ self.app.client_manager.application_catalog.schemas self.schemas_mock.get.return_value = api_schemas.Schema( None, SAMPLE_CLASS_SCHEMA) self.cmd = osc_schema.ShowSchema(self.app, None) def test_query_class_schema(self): arglist = ['class.name', 'methodName1', '--package-name', 'package.name', '--class-version', '>1'] verifylist = [('class_name', 'class.name'), ('method_names', ['methodName1']), ('package_name', 'package.name'), ('class_version', '>1')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) expected_columns = ['', 'modelBuilder'] self.assertItemsEqual(expected_columns, columns) self.assertItemsEqual(tuple(SAMPLE_CLASS_SCHEMA.values()), data) python-muranoclient-1.3.0/muranoclient/tests/unit/osc/v1/test_action.py0000664000175000017500000000572013523272253026405 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 muranoclient.osc.v1 import action as osc_action from muranoclient.tests.unit.osc.v1 import fakes from muranoclient.v1 import static_actions as api_static_actions class TestAction(fakes.TestApplicationCatalog): def setUp(self): super(TestAction, self).setUp() self.static_actions_mock = \ self.app.client_manager.application_catalog.static_actions class TestStaticActionCall(TestAction): def setUp(self): super(TestStaticActionCall, self).setUp() self.static_actions_mock.call.return_value = \ api_static_actions.StaticActionResult('result') # Command to test self.cmd = osc_action.StaticActionCall(self.app, None) @mock.patch('osc_lib.utils.get_item_properties') def test_static_action_call_basic(self, mock_util): mock_util.return_value = 'result' arglist = ['class.name', 'method.name'] verifylist = [('class_name', 'class.name'), ('method_name', 'method.name')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) # Check that columns are correct expected_columns = ['Static action result'] self.assertEqual(expected_columns, columns) # Check that data is correct expected_data = ['result'] self.assertEqual(expected_data, data) @mock.patch('osc_lib.utils.get_item_properties') def test_static_action_call_full(self, mock_util): mock_util.return_value = 'result' arglist = ['class.name', 'method.name', '--arguments', 'food=spam', 'parrot=dead', '--package-name', 'package.name', '--class-version', '>1'] verifylist = [('class_name', 'class.name'), ('method_name', 'method.name'), ('arguments', ['food=spam', 'parrot=dead']), ('package_name', 'package.name'), ('class_version', '>1')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) # Check that columns are correct expected_columns = ['Static action result'] self.assertEqual(expected_columns, columns) # Check that data is correct expected_data = ['result'] self.assertEqual(expected_data, data) python-muranoclient-1.3.0/muranoclient/tests/unit/osc/v1/test_category.py0000664000175000017500000001213313523272253026741 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 muranoclient.osc.v1 import category as osc_category from muranoclient.tests.unit.osc.v1 import fakes from muranoclient.v1 import categories as api_category from muranoclient.v1 import packages as api_packages CATEGORY_INFO = {'id': 'xyz123', 'name': 'fake1', 'packages': [{'name': 'package1'}, {'name': 'package2'}]} class TestCategory(fakes.TestApplicationCatalog): def setUp(self): super(TestCategory, self).setUp() self.category_mock = self.app.client_manager.application_catalog.\ categories self.category_mock.reset_mock() self.packages_mock = \ self.app.client_manager.application_catalog.packages class TestListCategories(TestCategory): def setUp(self): super(TestListCategories, self).setUp() self.category_mock.list.return_value = [api_category.Category(None, CATEGORY_INFO)] # Command to test self.cmd = osc_category.ListCategories(self.app, None) @mock.patch('osc_lib.utils.get_item_properties') def test_category_list(self, mock_util): mock_util.return_value = ('xyz123', 'fake1') columns, data = self.cmd.take_action(parsed_args=None) # Check that columns are correct expected_columns = ['ID', 'Name'] self.assertEqual(expected_columns, columns) # Check that data is correct expected_data = [('xyz123', 'fake1')] self.assertEqual(expected_data, data) class TestShowCategory(TestCategory): def setUp(self): super(TestShowCategory, self).setUp() self.category_mock.get.return_value = api_category.\ Category(None, CATEGORY_INFO) self.packages_mock.filter.return_value = [ api_packages.Package(None, pkg_info) for pkg_info in CATEGORY_INFO[ 'packages'] ] # Command to test self.cmd = osc_category.ShowCategory(self.app, None) @mock.patch('textwrap.wrap') def test_category_show(self, mock_wrap): arglist = ['xyz123'] verifylist = [('id', 'xyz123')] mock_wrap.return_value = ['package1, package2'] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) # Check that columns are correct expected_columns = ('id', 'name', 'packages') self.assertEqual(expected_columns, columns) # Check that data is correct expected_data = ('xyz123', 'fake1', 'package1, package2') self.assertEqual(expected_data, data) class TestCreateCategory(TestCategory): def setUp(self): super(TestCreateCategory, self).setUp() self.category_mock.add.return_value = [api_category.Category(None, CATEGORY_INFO)] # Command to test self.cmd = osc_category.CreateCategory(self.app, None) @mock.patch('osc_lib.utils.get_item_properties') def test_category_list(self, mock_util): arglist = ['fake1'] verifylist = [('name', 'fake1')] mock_util.return_value = ('xyz123', 'fake1') parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) # Check that columns are correct expected_columns = ['ID', 'Name'] self.assertEqual(expected_columns, columns) # Check that data is correct expected_data = [('xyz123', 'fake1')] self.assertEqual(expected_data, data) class TestDeleteCategory(TestCategory): def setUp(self): super(TestDeleteCategory, self).setUp() self.category_mock.delete.return_value = None self.category_mock.list.return_value = [api_category.Category(None, CATEGORY_INFO)] # Command to test self.cmd = osc_category.DeleteCategory(self.app, None) @mock.patch('osc_lib.utils.get_item_properties') def test_category_list(self, mock_util): arglist = ['abc123', '123abc'] verifylist = [('id', ['abc123', '123abc'])] mock_util.return_value = ('xyz123', 'fake1') parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) # Check that columns are correct expected_columns = ['ID', 'Name'] self.assertEqual(expected_columns, columns) # Check that data is correct expected_data = [('xyz123', 'fake1')] self.assertEqual(expected_data, data) python-muranoclient-1.3.0/muranoclient/tests/unit/osc/v1/test_deployment.py0000664000175000017500000000641313523272253027310 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 muranoclient.osc.v1 import deployment as osc_deployment from muranoclient.tests.unit.osc.v1 import fakes from muranoclient.v1 import deployments as api_deployment DEPLOYMENT_COLUMNS = ('id', 'state', 'created', 'updated', 'finished') DEPLOYMENT_DATA = ('xyz123', 'success', '2016-06-25T12:21:37', '2016-06-25T12:21:47', '2016-06-25T12:21:47') ALL_DEPLOYMENT_DATA = (('abc123', 'success', '2016-06-25T12:21:37', '2016-06-25T12:21:47', '2016-06-25T12:21:47'), ('xyz456', 'success', '2017-01-31T11:22:35', '2017-01-31T11:22:47', '2017-01-31T11:22:47')) class TestDeployment(fakes.TestApplicationCatalog): def setUp(self): super(TestDeployment, self).setUp() self.deployment_mock = self.app.client_manager.application_catalog.\ deployments self.deployment_mock.reset_mock() self.environment_mock = self.app.client_manager.application_catalog.\ environments class TestListDeployment(TestDeployment): def setUp(self): super(TestListDeployment, self).setUp() deployment_info = dict(zip(DEPLOYMENT_COLUMNS, DEPLOYMENT_DATA)) self.deployment_mock.list.return_value = \ [api_deployment.Deployment(None, deployment_info)] # Command to test self.cmd = osc_deployment.ListDeployment(self.app, None) @mock.patch('osc_lib.utils.get_item_properties') def test_deployment_list(self, mock_util): arglist = ['xyz123'] verifylist = [('id', 'xyz123')] mock_util.return_value = DEPLOYMENT_DATA parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) # Check that columns are correct expected_columns = [c.title() for c in DEPLOYMENT_COLUMNS] self.assertEqual(expected_columns, columns) # Check that data is correct expected_data = [DEPLOYMENT_DATA] self.assertEqual(expected_data, data) @mock.patch('osc_lib.utils.get_item_properties', autospec=True) def test_deployment_list_all_environments(self, mock_util): arglist = ['--all-environments'] verifylist = [('id', None), ('all_environments', True)] mock_util.return_value = ALL_DEPLOYMENT_DATA parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) # Check that columns are correct expected_columns = [c.title() for c in DEPLOYMENT_COLUMNS] self.assertEqual(expected_columns, columns) # Check that data is correct expected_data = [ALL_DEPLOYMENT_DATA] self.assertEqual(expected_data, data) python-muranoclient-1.3.0/muranoclient/tests/unit/osc/v1/fixture_data/0000775000175000017500000000000013523272337026175 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/tests/unit/osc/v1/fixture_data/test-app/0000775000175000017500000000000013523272337027732 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/tests/unit/osc/v1/fixture_data/test-app/Classes/0000775000175000017500000000000013523272337031327 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/tests/unit/osc/v1/fixture_data/test-app/Classes/testapp.yaml0000664000175000017500000000136113523272253033671 0ustar zuulzuul00000000000000Namespaces: =: io.murano.apps.test std: io.murano res: io.murano.resources Name: APP Extends: std:Application Properties: name: Contract: $.string().notNull() instance: Contract: $.class(res:Instance).notNull() Workflow: initialize: Body: - $.environment: $.find(std:Environment).require() deploy: Body: - $securityGroupIngress: - ToPort: 23 FromPort: 23 IpProtocol: tcp External: True - $.environment.securityGroupManager.addGroupIngress($securityGroupIngress) - $.instance.deploy() - $resources: new('io.murano.system.Resources') - $template: $resources.yaml('Deploy.template') - $.instance.agent.call($template, $resources) python-muranoclient-1.3.0/muranoclient/tests/unit/osc/v1/fixture_data/test-app/ui.yaml0000664000175000017500000000001313523272253031222 0ustar zuulzuul00000000000000Version: 2 python-muranoclient-1.3.0/muranoclient/tests/unit/osc/v1/fixture_data/test-app/Resources/0000775000175000017500000000000013523272337031704 5ustar zuulzuul00000000000000././@LongLink0000000000000000000000000000015100000000000011212 Lustar 00000000000000python-muranoclient-1.3.0/muranoclient/tests/unit/osc/v1/fixture_data/test-app/Resources/Deploy.templatepython-muranoclient-1.3.0/muranoclient/tests/unit/osc/v1/fixture_data/test-app/Resources/Deploy.temp0000664000175000017500000000051313523272253034023 0ustar zuulzuul00000000000000FormatVersion: 2.0.0 Version: 1.0.0 Name: Deploy Parameters: appName: $appName Body: | return deploy(args.appName).stdout Scripts: deploy: Type: Application Version: 1.0.0 EntryPoint: deploy.sh Files: - installer.sh - common.sh Options: captureStdout: true captureStderr: false python-muranoclient-1.3.0/muranoclient/tests/unit/osc/v1/fixture_data/test-app/Resources/scripts/0000775000175000017500000000000013523272337033373 5ustar zuulzuul00000000000000././@LongLink0000000000000000000000000000015300000000000011214 Lustar 00000000000000python-muranoclient-1.3.0/muranoclient/tests/unit/osc/v1/fixture_data/test-app/Resources/scripts/deploy.shpython-muranoclient-1.3.0/muranoclient/tests/unit/osc/v1/fixture_data/test-app/Resources/scripts/dep0000664000175000017500000000001413523272253034056 0ustar zuulzuul00000000000000#!/bin/bash ././@LongLink0000000000000000000000000000015600000000000011217 Lustar 00000000000000python-muranoclient-1.3.0/muranoclient/tests/unit/osc/v1/fixture_data/test-app/Resources/scripts/installer.shpython-muranoclient-1.3.0/muranoclient/tests/unit/osc/v1/fixture_data/test-app/Resources/scripts/ins0000664000175000017500000000001413523272253034077 0ustar zuulzuul00000000000000#!/bin/bash ././@LongLink0000000000000000000000000000015300000000000011214 Lustar 00000000000000python-muranoclient-1.3.0/muranoclient/tests/unit/osc/v1/fixture_data/test-app/Resources/scripts/common.shpython-muranoclient-1.3.0/muranoclient/tests/unit/osc/v1/fixture_data/test-app/Resources/scripts/com0000664000175000017500000000001413523272253034064 0ustar zuulzuul00000000000000#!/bin/bash python-muranoclient-1.3.0/muranoclient/tests/unit/osc/v1/fixture_data/heat-template.yaml0000664000175000017500000000004213523272253031604 0ustar zuulzuul00000000000000heat_template_version: 2013-05-23 python-muranoclient-1.3.0/muranoclient/tests/unit/osc/v1/fixture_data/logo.png0000664000175000017500000002656213523272253027653 0ustar zuulzuul00000000000000PNG  IHDR22?NiCCPICC ProfileX YgTsF2H"9Ee\ ""AE$ PfQ0!$(*b$׋}=yꞞZԘȨ8(q@pRccmjgӢGâ"ASBf.P9aAVùBC\= D3u0rd,D5{Bz\%ñm@g`o>XHL1]?9&bu}C؁ | U .ދ0n@՚^!g}L#=&t>?-0@D09u} cA P-3c' . |QP'8@*ބC$8mΟq,xK9AC"};l=;| ?تߞn͠aײ~E%F, LIA8cq@=1@4Or'GZ_>31kf+8"V P?5l2v=@k6lws^h8!YI?J:5?`D 5pUZQUeu{:]oΫ{5*Ȣ>P[ dRy<-=>|@*k\"!Dv,bpu{9\o0 f" Dp |"#6!3"H ;<r9 !҃—Og-~@ p Z"̈́(aH$zD"G""#6{o?HL$*ɜM"eJHgH^8iAaCCC>CM. lҌza;Kk2`ĴIɉ))LdvلC'#&7Q()!śGGR)?931_ce"bIJ%2K+ +5  [$3l&؉Rf8 ?N*o9 ҜVay9;9عԹܹrqƸ#/qp/ .]{ɏwO?JS@W_ W *(',MRCpFHXB(FPД0pppHHH{Q.Q#R6i1A1Kxbbk׸XS8xxxDY!IImɃ%礤+B"c EBO -.{D[Ӑ +G5CůYbYH!AkEnE[ 늟$ +TPPR~®brS嫪jjE\mZuy@O5844vkh,kji4k5'$|ʵ9h?liЙ_.nݥuutuN^j= }Q}_#bT 7 ǍdŒ}2V6_53YgjlZv66_cl~|BbE%ƲrJߪjZ:պ͆lbs捭-jgmWh^>pr(tx(ѩ靳s}.g\f]]]ɸŻ׸yzTL|UMv>=lC>>Y>7npM7ln=DuVPgM0 ( <8t h"X/0x2 $d*$p0˰casW"<""I7أ£ڢFdŌlYx4͆v*[ ?;ewſNO(Kxy+֨IrI9I'okIKّ:(Dֲ]|{;wxq N7323GwY:ŜEܭX6.;43G-P܀<弒={-ݻ/h_gfQ H>0ZhWxH({%%2?8Rj[ZHCKCՕ  8{h1cyzĵ JBeB*'O֜?wjtj3gϢgN9}|}B: B}/\rYr+W9^C%]r}ޫ7RuA6Fƕ䦙;wF[6֡q㫝׺uoi5ɽ>G=nO}G?S0{`phwt!eKm:x߉ɪ藺\r5g|&+:EKj]^;7Zo6ߺpv]cMSe;-)maw]i?`aWGw7tuvRz<)  V?Mxf8DחƋúã|x}(6Z766wMc&&WRX3ӎᄂ|z[l\Gc wWVV_a">I#)0H3J3!+S1;cobf]dR .zBisI&)i +Y_$µ:'q*bռ44nh?yzaQnӏ$ !KU+3k7(i;w9e9gquH r6`c{Sb vW˃{(CºoFDEGgж cEb'd%m5KRN&—ʕƺ}6͎;3wm̲mmCݑwrϽ>+9pcTɧ?)*?uoOxVSd㩆ψx: 5UZޛ[ ojNׅO&(&'I! J LȉgfUfYgl5\}t8lQc:uNhT(UT;v$swמ{rae+\Q~s[Ѣɯy-V7ypOG˝] ?Qi!/^1N˛Hhuvމ>jowABg$#\u  ^0Ӵ0s r`Fy4^XYBY懱^EFQCt7zLÒjlG­E*qx!!BOIb "C5 9&{j2GnHRr(]`S Xyuƹ˙k;S+[g7-"0*xLOX^=b5%"mde)rUHSQQTVyzF-G=DZSQKkE^wtMϚ3H$+k CcS[WGq6qʛ7VQ;~H nY3 ?1ݻŜ_bmݩ22mw.Y{wO>W _)=xR 5)׎\|Wՙ[]_[\3fEku@VkF-k B(UueQB XU7(FmQzmE?c acR#x=6-J"$~% It /1y2]0GѡgfbD"Et0dtCCqc`cifaeϮ#Ye;Gw Kzbky%_>KYcXk૘tD^7u~ + V: zDhƸД۬ۊͺ}ː{ד A>?6E1~Z !;+#AuZtp|kb|ROiUI;22],jٱ9}yn{&eW,xV][r#i]aP|T{cMYsݵu7.\:xt5R}M[ݷ34'[zHchg/A+C^8x?&c {1N|}'IG333gCfg&7?@YY]X,]dT|'dm }c`WVI@<rbr%L6^2Wt.ןN? #q pHYs  iTXtXML:com.adobe.xmp 1440 900 u IDAThݚyp]}ǿMzZےก2Cmh'ҖL)Sht&SBC3SR4JRL0 v^d˖-ɲ{' 1t޹Y~~S455Ŀ,\Ԥ=cD3,SQuVޅB^qּU֪.0?x=՛$r&DMu5='Ӂ*IYʗPeU/RQTյNz/EZ aG|F`&_W6'Vەe*T*|h ceQ)o4UJ8S#ZLKRi\iꦘ!E$> }ꕢ*%JPTZΫQ)mDi6ATS植\&j4hJTt!v\G-o95K =C5}KbQY\ WQPIb0,RB5INT&E m^e*|N;M{ OIdNk4+Qy|T ϰ8_h4]H+#V\L*G*5dG'&5@$ G:U5խk"}.[懃\u]=ژJ{<% :( R4g)Bm]&uPU9 /GVIi}'S\PyP~ ̟E93ԐeMC:ڝ>ĈL3e)W#Liw"m+mےT%]t{TW= ] { "t _)ʽ[UApLFHr+$$^p?ǴwX)>pI a)x7J 8T.UQO5=cm(dC>z@uW@xx7DMɲ IxRP2 qCTW`DLHę̌EzO-{t%K[6frpgܡUD}Ge<*BIQkQtX h)3}LCTAJy$ـD8> 2%3zyTq4%wx jdd LHˋ%]!}rǭ[Z}up("R8eR 0d!3^!4FfNbKVdD nbhIH{UFz @v^ćYh8!8\qo}I&L3ueS1}Ԧ*u@ {h396t{QE (iG|RBʌ$0-ՠsc8&]Ʉ_.~NLUV @n? f,"(=dZ.N л;ekc"TEPȜ57a98:Re:zPy3ŢLb60 vTh*q:i4/I1OӾ1;}< M`jv)*%vX3IDLmRCJ⡬2M+ ,k栾s8 1׆ 'P&c RS݅ɰ1k1TL`4 2:F9wBb0S_ߒc݀.ށ#L ĥg=z`Mt!8B37Y6b.'NL'>zh^ ё9 +3G . J:}I>6U^p/ ZjDFj6Mnz`8/.H{;S A%` k^[38`3x ^l`ۤմƾcXxC)*XlދC;g \9W391NX^'́wq @z%nvf eU]8g/`7"{D a;-4ggP3iH'c\pKДt019s!%YuTbπ-R!¼o׾AQ Zy=H^rl۝&w?9ob1lNI\}3³H X{j \.OV<8ժ iht:ᆭœ̩%罨ОXCs \떺7vrڑor@"(K׵hSr 2Ϋf }?x޳IE?|[Ɛ g[:s^Cxr޹A> cM瑌OPlmzu`BǑ" />Ë !Nxq )E3e|2GkJ3I߮{Y<8ӝu@_g/ܯg!Axۺ-&7 O7X$0ajZjWH ISf Bw{ͿԿ_ާhP;@؋Qp>T)_̵z~ևCf 52 :×! twX:H"})GJם! qKwݧ Z0'p"C:@m xfW^qcg(,zen?eo{DŽV>Wµ8m}kP+u~mͷ)ھ/rG@b^ERqV$?\y9Ld~ȼjͽ+I+O#>ޔ8ڛ& 5iP:b^/hS ~WA4i f6Ksg) *{_~Xa exxDvgsv_z3 &gO52e$\֓H1Nww;7jiYdN-s*׽D)A~6gnKՎGJHlHmx *î"R+A`tw/DC elHOpY>fda^08AP)9\{k]7^zz8L^@gY5dKO?DN@8D܂VzY@I>8:>ߍQG1ǙxnrkQ9p&)T_:Džϭ&Y5o# {PBoyRoi?!ׁj%$D5sGP<?ѨKԺH;@! `}eiƨ֮)ޅCg!^; ޵k>[ݻIN,[5LpAYLH.8oۋ;OXǴwvA ـ]`0sZ0 -P 3NY?g̢'`G7?'ڦݣ~5cڛ_P0ߑ.շ[rI;k4yG OL s-%s6^a۶?]4::'.;<aŠl⺻;w[u֪JXVS[-o[CyS&$V00IENDB`python-muranoclient-1.3.0/muranoclient/tests/unit/osc/v1/test_package.py0000664000175000017500000006734613523272253026537 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import os import shutil import six import sys import tempfile from testtools import matchers from muranoclient.common import exceptions as common_exceptions from muranoclient.common import utils as mc_utils from muranoclient.osc.v1 import package as osc_pkg from muranoclient.tests.unit.osc.v1 import fakes from muranoclient.tests.unit import test_utils from muranoclient.v1 import packages import mock from osc_lib import exceptions as exc from osc_lib import utils import requests_mock make_pkg = test_utils.make_pkg FIXTURE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), 'fixture_data')) COLUMNS = ['Id', 'Name', 'Fully_qualified_name', 'Author', 'Active', 'Is public', 'Type', 'Version'] DATA = { 'class_definitions': ['com.example.apache.ApacheHttpServer'], 'updated': '2016-09-20T06:23:45.000000', 'description': 'Test description.\n', 'created': '2016-09-20T06:23:15.000000', 'author': 'Mirantis, Inc', 'enabled': True, 'owner_id': 'a203405ea871484a940850d6c0b8dfd9', 'tags': ['Server', 'WebServer', 'Apache', 'HTTP', 'HTML'], 'is_public': False, 'fully_qualified_name': 'com.example.apache.ApacheHttpServer', 'type': 'Application', 'id': '46860070-5f8a-4936-96e8-d7b89e5187d7', 'categories': [], 'name': 'Apache HTTP Server' } class TestPackage(fakes.TestApplicationCatalog): def setUp(self): super(TestPackage, self).setUp() self.package_mock = self.app.client_manager.application_catalog.\ packages self.package_mock.reset_mock() class TestCreatePackage(TestPackage): def setUp(self): super(TestCreatePackage, self).setUp() # Command to test self.cmd = osc_pkg.CreatePackage(self.app, None) def test_create_package_without_args(self): arglist = [] parsed_args = self.check_parser(self.cmd, arglist, []) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertEqual('Provide --template for a HOT-based package, OR at ' 'least --classes-dir for a MuranoPL-based package', str(error)) def test_create_package_template_and_classes_args(self): heat_template = os.path.join(FIXTURE_DIR, 'heat-template.yaml') classes_dir = os.path.join(FIXTURE_DIR, 'test-app', 'Classes') arglist = ['--template', heat_template, '--classes-dir', classes_dir] parsed_args = self.check_parser(self.cmd, arglist, []) error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertEqual('Provide --template for a HOT-based package, OR' ' --classes-dir for a MuranoPL-based package', str(error)) def test_create_hot_based_package(self): with tempfile.NamedTemporaryFile() as f: RESULT_PACKAGE = f.name heat_template = os.path.join(FIXTURE_DIR, 'heat-template.yaml') logo = os.path.join(FIXTURE_DIR, 'logo.png') arglist = ['--template', heat_template, '--output', RESULT_PACKAGE, '-l', logo] parsed_args = self.check_parser(self.cmd, arglist, []) orig = sys.stdout try: sys.stdout = six.StringIO() self.cmd.take_action(parsed_args) finally: stdout = sys.stdout.getvalue() sys.stdout.close() sys.stdout = orig matchers.MatchesRegex(stdout, "Application package " "is available at {0}".format(RESULT_PACKAGE)) def test_create_mpl_package(self): with tempfile.NamedTemporaryFile() as f: RESULT_PACKAGE = f.name classes_dir = os.path.join(FIXTURE_DIR, 'test-app', 'Classes') resources_dir = os.path.join(FIXTURE_DIR, 'test-app', 'Resources') ui = os.path.join(FIXTURE_DIR, 'test-app', 'ui.yaml') arglist = ['-c', classes_dir, '-r', resources_dir, '-u', ui, '-o', RESULT_PACKAGE] parsed_args = self.check_parser(self.cmd, arglist, []) orig = sys.stdout try: sys.stdout = six.StringIO() self.cmd.take_action(parsed_args) finally: stdout = sys.stdout.getvalue() sys.stdout.close() sys.stdout = orig matchers.MatchesRegex(stdout, "Application package " "is available at {0}".format(RESULT_PACKAGE)) class TestPackageList(TestPackage): def setUp(self): super(TestPackageList, self).setUp() self.cmd = osc_pkg.ListPackages(self.app, None) self.package_mock.filter.return_value = \ [packages.Package(None, DATA)] utils.get_dict_properties = mock.MagicMock(return_value='') def test_package_list_defaults(self): arglist = [] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.package_mock.filter.assert_called_with( include_disabled=False, owned=False) self.assertEqual(COLUMNS, columns) def test_package_list_with_limit(self): arglist = ['--limit', '10'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.package_mock.filter.assert_called_with( include_disabled=False, limit=10, owned=False) def test_package_list_with_marker(self): arglist = ['--marker', '12345'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.package_mock.filter.assert_called_with( include_disabled=False, marker='12345', owned=False) def test_package_list_with_name(self): arglist = ['--name', 'mysql'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.package_mock.filter.assert_called_with( include_disabled=False, name='mysql', owned=False) def test_package_list_with_fqn(self): arglist = ['--fqn', 'mysql'] parsed_args = self.check_parser(self.cmd, arglist, []) columns, data = self.cmd.take_action(parsed_args) self.package_mock.filter.assert_called_with( include_disabled=False, fqn='mysql', owned=False) class TestPackageDelete(TestPackage): def setUp(self): super(TestPackageDelete, self).setUp() self.package_mock.delete.return_value = None self.package_mock.filter.return_value = \ [packages.Package(None, DATA)] # Command to test self.cmd = osc_pkg.DeletePackage(self.app, None) @mock.patch('osc_lib.utils.get_item_properties') def test_package_delete(self, mock_util): arglist = ['fake1'] verifylist = [('id', ['fake1'])] mock_util.return_value = ('1234', 'Core library', 'io.murano', 'murano.io', '', 'True', 'Library', '0.0.0' ) parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) # Check that columns are correct self.assertEqual(COLUMNS, columns) # Check that data is correct expected_data = [('1234', 'Core library', 'io.murano', 'murano.io', '', 'True', 'Library', '0.0.0')] self.assertEqual(expected_data, data) class TestPackageImport(TestPackage): def setUp(self): super(TestPackageImport, self).setUp() self.package_mock.filter.return_value = \ [packages.Package(None, DATA)] # Command to test self.cmd = osc_pkg.ImportPackage(self.app, None) @mock.patch('muranoclient.common.utils.Package.from_file') def test_package_import(self, from_file): with tempfile.NamedTemporaryFile() as f: RESULT_PACKAGE = f.name categories = ['Cat1', 'Cat2 with space'] pkg = make_pkg({'FullName': RESULT_PACKAGE}) from_file.return_value = mc_utils.Package(mc_utils.File(pkg)) arglist = [RESULT_PACKAGE, '--categories', categories, '--is-public'] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.package_mock.create.assert_called_once_with({ 'categories': [categories], 'is_public': True }, {RESULT_PACKAGE: mock.ANY},) def _test_conflict(self, packages, from_file, raw_input_mock, input_action, exists_action=''): packages.create = mock.MagicMock( side_effect=[common_exceptions.HTTPConflict("Conflict"), None]) packages.filter.return_value = [mock.Mock(id='test_id')] raw_input_mock.return_value = input_action with tempfile.NamedTemporaryFile() as f: pkg = make_pkg({'FullName': f.name}) from_file.return_value = mc_utils.Package(mc_utils.File(pkg)) if exists_action: arglist = [f.name, '--exists-action', exists_action] else: arglist = [f.name] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) return f.name @mock.patch('six.moves.input') @mock.patch('muranoclient.common.utils.Package.from_file') def test_package_import_conflict_skip(self, from_file, raw_input_mock): name = self._test_conflict( self.package_mock, from_file, raw_input_mock, 's', ) self.package_mock.create.assert_called_once_with({ 'is_public': False, }, {name: mock.ANY},) @mock.patch('six.moves.input') @mock.patch('muranoclient.common.utils.Package.from_file') def test_package_import_conflict_skip_ea(self, from_file, raw_input_mock): name = self._test_conflict( self.package_mock, from_file, raw_input_mock, '', exists_action='s', ) self.package_mock.create.assert_called_once_with({ 'is_public': False, }, {name: mock.ANY},) self.assertFalse(raw_input_mock.called) @mock.patch('six.moves.input') @mock.patch('muranoclient.common.utils.Package.from_file') def test_package_import_conflict_abort(self, from_file, raw_input_mock): self.assertRaises(SystemExit, self._test_conflict, self.package_mock, from_file, raw_input_mock, 'a', ) self.package_mock.create.assert_called_once_with({ 'is_public': False, }, mock.ANY,) @mock.patch('six.moves.input') @mock.patch('muranoclient.common.utils.Package.from_file') def test_package_import_conflict_abort_ea(self, from_file, raw_input_mock): self.assertRaises(SystemExit, self._test_conflict, self.package_mock, from_file, raw_input_mock, '', exists_action='a', ) self.package_mock.create.assert_called_once_with({ 'is_public': False, }, mock.ANY,) self.assertFalse(raw_input_mock.called) @mock.patch('six.moves.input') @mock.patch('muranoclient.common.utils.Package.from_file') def test_package_import_conflict_update(self, from_file, raw_input_mock): name = self._test_conflict( self.package_mock, from_file, raw_input_mock, 'u', ) self.assertEqual(2, self.package_mock.create.call_count) self.package_mock.delete.assert_called_once_with('test_id') self.package_mock.create.assert_has_calls( [ mock.call({'is_public': False}, {name: mock.ANY},), mock.call({'is_public': False}, {name: mock.ANY},) ], any_order=True, ) @mock.patch('six.moves.input') @mock.patch('muranoclient.common.utils.Package.from_file') def test_package_import_conflict_update_ea(self, from_file, raw_input_mock): name = self._test_conflict( self.package_mock, from_file, raw_input_mock, '', exists_action='u', ) self.assertEqual(2, self.package_mock.create.call_count) self.package_mock.delete.assert_called_once_with('test_id') self.package_mock.create.assert_has_calls( [ mock.call({'is_public': False}, {name: mock.ANY},), mock.call({'is_public': False}, {name: mock.ANY},) ], any_order=True, ) self.assertFalse(raw_input_mock.called) def _test_conflict_dep(self, packages, from_file, dep_exists_action=''): packages.create = mock.MagicMock( side_effect=[common_exceptions.HTTPConflict("Conflict"), common_exceptions.HTTPConflict("Conflict"), None]) packages.filter.return_value = [mock.Mock(id='test_id')] pkg1 = make_pkg( {'FullName': 'first_app', 'Require': {'second_app': '1.0'}, }) pkg2 = make_pkg({'FullName': 'second_app', }) def side_effect(name): if 'first_app' in name: return mc_utils.Package(mc_utils.File(pkg1)) if 'second_app' in name: return mc_utils.Package(mc_utils.File(pkg2)) from_file.side_effect = side_effect arglist = ['first_app', '--exists-action', 's', '--dep-exists-action', dep_exists_action] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) @mock.patch('muranoclient.common.utils.Package.from_file') def test_package_import_conflict_dep_skip_ea(self, from_file): self._test_conflict_dep( self.package_mock, from_file, dep_exists_action='s', ) self.assertEqual(2, self.package_mock.create.call_count) self.package_mock.create.assert_has_calls( [ mock.call({'is_public': False}, {'first_app': mock.ANY}), mock.call({'is_public': False}, {'second_app': mock.ANY}), ], any_order=True, ) @mock.patch('muranoclient.common.utils.Package.from_file') def test_package_import_conflict_dep_abort_ea(self, from_file): self.assertRaises(SystemExit, self._test_conflict_dep, self.package_mock, from_file, dep_exists_action='a', ) self.package_mock.create.assert_called_with({ 'is_public': False, }, {'second_app': mock.ANY},) @mock.patch('muranoclient.common.utils.Package.from_file') def test_package_import_conflict_dep_update_ea(self, from_file): self._test_conflict_dep( self.package_mock, from_file, dep_exists_action='u', ) self.assertGreater(self.package_mock.create.call_count, 2) self.assertLess(self.package_mock.create.call_count, 5) self.assertTrue(self.package_mock.delete.called) self.package_mock.create.assert_has_calls( [ mock.call({'is_public': False}, {'first_app': mock.ANY}), mock.call({'is_public': False}, {'second_app': mock.ANY}), mock.call({'is_public': False}, {'second_app': mock.ANY}), ], any_order=True, ) @mock.patch('muranoclient.common.utils.Package.from_file') def test_package_import_no_categories(self, from_file): with tempfile.NamedTemporaryFile() as f: RESULT_PACKAGE = f.name pkg = make_pkg({'FullName': RESULT_PACKAGE}) from_file.return_value = mc_utils.Package(mc_utils.File(pkg)) arglist = [RESULT_PACKAGE] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.package_mock.create.assert_called_once_with( {'is_public': False}, {RESULT_PACKAGE: mock.ANY}, ) @requests_mock.mock() @mock.patch('muranoclient.common.utils.Package.from_file') def test_package_import_url(self, rm, from_file): filename = "http://127.0.0.1/test_package.zip" pkg = make_pkg({'FullName': 'test_package'}) from_file.return_value = mc_utils.Package(mc_utils.File(pkg)) rm.get(filename, body=make_pkg({'FullName': 'test_package'})) arglist = [filename] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.package_mock.create.assert_called_once_with( {'is_public': False}, {'test_package': mock.ANY}, ) @requests_mock.mock() @mock.patch('muranoclient.common.utils.Package.from_file') def test_package_import_by_name(self, rm, from_file): filename = "io.test.apps.test_application" murano_repo_url = "http://127.0.0.1" pkg = make_pkg({'FullName': filename}) from_file.return_value = mc_utils.Package(mc_utils.File(pkg)) rm.get(murano_repo_url + '/apps/' + filename + '.zip', body=make_pkg({'FullName': 'first_app'})) arglist = [filename, '--murano-repo-url', murano_repo_url] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.assertTrue(self.package_mock.create.called) self.package_mock.create.assert_called_once_with( {'is_public': False}, {filename: mock.ANY}, ) @requests_mock.mock() def test_package_import_multiple(self, rm): filename = ["io.test.apps.test_application", "http://127.0.0.1/test_app2.zip", ] murano_repo_url = "http://127.0.0.1" rm.get(murano_repo_url + '/apps/' + filename[0] + '.zip', body=make_pkg({'FullName': 'first_app'})) rm.get(filename[1], body=make_pkg({'FullName': 'second_app'})) arglist = [filename[0], filename[1], '--murano-repo-url', murano_repo_url] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.assertEqual(2, self.package_mock.create.call_count) self.package_mock.create.assert_has_calls( [ mock.call({'is_public': False}, {'first_app': mock.ANY}), mock.call({'is_public': False}, {'second_app': mock.ANY}), ], any_order=True, ) class TestBundleImport(TestPackage): def setUp(self): super(TestBundleImport, self).setUp() # Command to test self.cmd = osc_pkg.ImportBundle(self.app, None) @requests_mock.mock() def test_import_bundle_by_name(self, m): """Asserts bundle import calls packages create once for each pkg.""" pkg1 = make_pkg({'FullName': 'first_app'}) pkg2 = make_pkg({'FullName': 'second_app'}) murano_repo_url = "http://127.0.0.1" m.get(murano_repo_url + '/apps/first_app.zip', body=pkg1) m.get(murano_repo_url + '/apps/second_app.1.0.zip', body=pkg2) s = six.StringIO() bundle_contents = {'Packages': [ {'Name': 'first_app'}, {'Name': 'second_app', 'Version': '1.0'} ]} json.dump(bundle_contents, s) s = six.BytesIO(s.getvalue().encode('ascii')) m.get(murano_repo_url + '/bundles/test_bundle.bundle', body=s) arglist = ["test_bundle", '--murano-repo-url', murano_repo_url] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.package_mock.create.assert_has_calls( [ mock.call({'is_public': False}, {'first_app': mock.ANY}), mock.call({'is_public': False}, {'second_app': mock.ANY}), ], any_order=True, ) @requests_mock.mock() def test_import_bundle_wrong_url(self, m): url = 'http://127.0.0.2/test_bundle.bundle' m.get(url, status_code=404) arglist = [url] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.assertFalse(self.package_mock.packages.create.called) @requests_mock.mock() def test_import_bundle_no_bundle(self, m): url = 'http://127.0.0.1/bundles/test_bundle.bundle' m.get(url, status_code=404) arglist = ["test_bundle"] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.assertFalse(self.package_mock.packages.create.called) @requests_mock.mock() def test_import_bundle_by_url(self, m): """Asserts bundle import calls packages create once for each pkg.""" pkg1 = make_pkg({'FullName': 'first_app'}) pkg2 = make_pkg({'FullName': 'second_app'}) murano_repo_url = 'http://127.0.0.1' m.get(murano_repo_url + '/apps/first_app.zip', body=pkg1) m.get(murano_repo_url + '/apps/second_app.1.0.zip', body=pkg2) s = six.StringIO() bundle_contents = {'Packages': [ {'Name': 'first_app'}, {'Name': 'second_app', 'Version': '1.0'} ]} json.dump(bundle_contents, s) s = six.BytesIO(s.getvalue().encode('ascii')) url = 'http://127.0.0.2/test_bundle.bundle' m.get(url, body=s) arglist = [url, '--murano-repo-url', murano_repo_url] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.package_mock.create.assert_has_calls( [ mock.call({'is_public': False}, {'first_app': mock.ANY}), mock.call({'is_public': False}, {'second_app': mock.ANY}), ], any_order=True, ) @requests_mock.mock() def test_import_local_bundle(self, m): """Asserts local bundles are first searched locally.""" tmp_dir = tempfile.mkdtemp() bundle_file = os.path.join(tmp_dir, 'bundle.bundle') with open(os.path.join(tmp_dir, 'bundle.bundle'), 'w') as f: bundle_contents = {'Packages': [ {'Name': 'first_app'}, {'Name': 'second_app', 'Version': '1.0'} ]} json.dump(bundle_contents, f) pkg1 = make_pkg({'FullName': 'first_app', 'Require': {'third_app': None}}) pkg2 = make_pkg({'FullName': 'second_app'}) pkg3 = make_pkg({'FullName': 'third_app'}) with open(os.path.join(tmp_dir, 'first_app'), 'wb') as f: f.write(pkg1.read()) with open(os.path.join(tmp_dir, 'third_app'), 'wb') as f: f.write(pkg3.read()) murano_repo_url = "http://127.0.0.1" m.get(murano_repo_url + '/apps/first_app.zip', status_code=404) m.get(murano_repo_url + '/apps/second_app.1.0.zip', body=pkg2) m.get(murano_repo_url + '/apps/third_app.zip', status_code=404) arglist = [bundle_file, '--murano-repo-url', murano_repo_url] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.package_mock.create.assert_has_calls( [ mock.call({'is_public': False}, {'first_app': mock.ANY}), mock.call({'is_public': False}, {'second_app': mock.ANY}), mock.call({'is_public': False}, {'third_app': mock.ANY}), ], any_order=True, ) shutil.rmtree(tmp_dir) class TestShowPackage(TestPackage): def setUp(self): super(TestShowPackage, self).setUp() # Command to test self.cmd = osc_pkg.ShowPackage(self.app, None) def test_package_show(self): arglist = ['fake'] verifylist = [('id', 'fake')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) # Check that columns are correct expected_columns = ('categories', 'class_definitions', 'description', 'enabled', 'fully_qualified_name', 'id', 'is_public', 'name', 'owner_id', 'tags', 'type') self.assertEqual(expected_columns, columns) self.package_mock.get.assert_called_with('fake') class TestUpdatePackage(TestPackage): def setUp(self): super(TestUpdatePackage, self).setUp() self.package_mock.update.return_value = \ (mock.MagicMock(), mock.MagicMock()) # Command to test self.cmd = osc_pkg.UpdatePackage(self.app, None) def test_package_update(self): arglist = ['123', '--is-public', 'true'] verifylist = [('id', '123'), ('is_public', True)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.package_mock.update.assert_called_with('123', {'is_public': True}) arglist = ['123', '--enabled', 'true'] verifylist = [('id', '123'), ('enabled', True)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.package_mock.update.assert_called_with('123', {'enabled': True}) arglist = ['123', '--name', 'foo', '--description', 'bar'] verifylist = [('id', '123'), ('name', 'foo'), ('description', 'bar')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.package_mock.update.assert_called_with( '123', {'name': 'foo', 'description': 'bar'}) arglist = ['123', '--tags', 'foo'] verifylist = [('id', '123'), ('tags', ['foo'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.package_mock.update.assert_called_with( '123', {'tags': ['foo']}) class TestDownloadPackage(TestPackage): def setUp(self): super(TestDownloadPackage, self).setUp() self.package_mock.download.return_value = \ b'This is a fake package buffer' # Command to test self.cmd = osc_pkg.DownloadPackage(self.app, None) def test_package_download(self): arglist = ['1234', '/tmp/foo.zip'] verifylist = [('id', '1234'), ('filename', '/tmp/foo.zip')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.package_mock.download.assert_called_with('1234') python-muranoclient-1.3.0/muranoclient/tests/unit/osc/v1/__init__.py0000664000175000017500000000000013523272253025612 0ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/tests/unit/osc/v1/test_environment.py0000664000175000017500000005301013523272253027467 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import collections import json import tempfile import mock from muranoclient.osc.v1 import environment as osc_env from muranoclient.tests.unit.osc.v1 import fakes from muranoclient.v1 import environments as api_env ENV_INFO = {'id': '1234', 'name': 'Fake Environment', 'created': '2015-12-16T17:31:54', 'updated': '2015-12-16T17:31:54', 'networking': {}, 'services': ['fake services'], 'status': 'fake deployed', 'tenant_id': 'xyz123', 'version': '1'} ENV_MODEL = { "defaultNetworks": { "environment": { "name": "env-network", "?": { "type": "io.murano.resources.NeutronNetwork", "id": "5678" } }, "flat": None }, "region": "RegionOne", "name": "env", "?": { "updated": "2016-10-03 09:33:41.039789", "type": "io.murano.Environment", "id": "1234" } } class TestEnvironment(fakes.TestApplicationCatalog): def setUp(self): super(TestEnvironment, self).setUp() self.environment_mock = self.app.client_manager.application_catalog.\ environments self.session_mock = self.app.client_manager.application_catalog.\ sessions self.services_mock = self.app.client_manager.application_catalog.\ services self.environment_mock.reset_mock() class TestListEnvironment(TestEnvironment): def setUp(self): super(TestListEnvironment, self).setUp() self.environment_mock.list.return_value = [api_env.Environment(None, ENV_INFO)] # Command to test self.cmd = osc_env.ListEnvironments(self.app, None) @mock.patch('osc_lib.utils.get_item_properties') def test_environment_list_with_no_options(self, mock_util): arglist = [] verifylist = [] mock_util.return_value = ('1234', 'Environment of all tenants', 'fake deployed', '2015-12-16T17:31:54', '2015-12-16T17:31:54' ) parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) # Check that columns are correct expected_columns = ['Id', 'Name', 'Status', 'Created', 'Updated'] self.assertEqual(expected_columns, columns) # Check that data is correct expected_data = [('1234', 'Environment of all tenants', 'fake deployed', '2015-12-16T17:31:54', '2015-12-16T17:31:54')] self.assertEqual(expected_data, data) @mock.patch('osc_lib.utils.get_item_properties') def test_environment_list_with_all_tenants(self, mock_util): arglist = ['--all-tenants'] verifylist = [('all_tenants', True), ('tenant', None)] mock_util.return_value = ('1234', 'Environment of all tenants', 'fake deployed', '2015-12-16T17:31:54', '2015-12-16T17:31:54' ) parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) # Check that columns are correct expected_columns = ['Id', 'Name', 'Status', 'Created', 'Updated'] self.assertEqual(expected_columns, columns) # Check that data is correct expected_data = [('1234', 'Environment of all tenants', 'fake deployed', '2015-12-16T17:31:54', '2015-12-16T17:31:54')] self.assertEqual(expected_data, data) self.environment_mock.list.assert_called_once_with(True, None) @mock.patch('osc_lib.utils.get_item_properties') def test_environment_list_with_tenant(self, mock_util): arglist = ['--tenant=ABC'] verifylist = [('all_tenants', False), ('tenant', 'ABC')] mock_util.return_value = ('1234', 'Environment of tenant ABC', 'fake deployed', '2015-12-16T17:31:54', '2015-12-16T17:31:54' ) parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) # Check that columns are correct expected_columns = ['Id', 'Name', 'Status', 'Created', 'Updated'] self.assertEqual(expected_columns, columns) # Check that data is correct expected_data = [('1234', 'Environment of tenant ABC', 'fake deployed', '2015-12-16T17:31:54', '2015-12-16T17:31:54')] self.assertEqual(expected_data, data) self.environment_mock.list.assert_called_once_with(False, 'ABC') class TestShowEnvironment(TestEnvironment): def setUp(self): super(TestShowEnvironment, self).setUp() mock_to_dict = self.environment_mock.get.return_value.to_dict mock_to_dict.return_value = ENV_INFO self.cmd = osc_env.ShowEnvironment(self.app, None) @mock.patch('oslo_serialization.jsonutils.dumps') def test_environment_show_with_no_options(self, mock_json): arglist = ['fake'] verifylist = [('id', 'fake')] mock_json.return_value = ['fake services'] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) # Check that columns are correct expected_columns = ('created', 'id', 'name', 'networking', 'services', 'status', 'tenant_id', 'updated', 'version') self.assertEqual(expected_columns, columns) # Check that data is correct expected_data = ('2015-12-16T17:31:54', '1234', 'Fake Environment', {}, ['fake services'], 'fake deployed', 'xyz123', '2015-12-16T17:31:54', '1') self.assertEqual(expected_data, data) @mock.patch('oslo_serialization.jsonutils.dumps') def test_environment_show_with_only_app_option(self, mock_json): arglist = ['fake', '--only-apps'] verifylist = [('id', 'fake'), ('only_apps', True)] mock_json.return_value = ['fake services'] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) # Check that columns are correct expected_columns = ['services'] self.assertEqual(expected_columns, columns) # Check that data is correct expected_data = [['fake services']] self.assertEqual(expected_data, data) @mock.patch('oslo_serialization.jsonutils.dumps') def test_environment_show_with_session_id_option(self, mock_json): arglist = ['fake', '--session-id', 'abc123'] verifylist = [('id', 'fake'), ('session_id', 'abc123')] mock_json.return_value = ['fake services'] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) # Check that columns are correct expected_columns = ('created', 'id', 'name', 'networking', 'services', 'status', 'tenant_id', 'updated', 'version') self.assertEqual(expected_columns, columns) # Check that data is correct expected_data = ('2015-12-16T17:31:54', '1234', 'Fake Environment', {}, ['fake services'], 'fake deployed', 'xyz123', '2015-12-16T17:31:54', '1') self.assertEqual(expected_data, data) class TestRenameEnvironment(TestEnvironment): def setUp(self): super(TestRenameEnvironment, self).setUp() self.environment_mock.update.return_value = [api_env.Environment(None, ENV_INFO)] # Command to test self.cmd = osc_env.RenameEnvironment(self.app, None) @mock.patch('osc_lib.utils.get_item_properties') def test_environment_rename(self, mock_util): arglist = ['1234', 'fake-1'] verifylist = [('id', '1234'), ('name', 'fake-1')] mock_util.return_value = ('1234', 'fake-1', 'fake deployed', '2015-12-16T17:31:54', '2015-12-16T17:31:54' ) parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) # Check that columns are correct expected_columns = ['Id', 'Name', 'Status', 'Created', 'Updated'] self.assertEqual(expected_columns, columns) # Check that data is correct expected_data = [('1234', 'fake-1', 'fake deployed', '2015-12-16T17:31:54', '2015-12-16T17:31:54')] self.assertEqual(expected_data, data) class TestEnvironmentSessionCreate(TestEnvironment): def setUp(self): super(TestEnvironmentSessionCreate, self).setUp() # Command to test self.cmd = osc_env.EnvironmentSessionCreate(self.app, None) @mock.patch('muranoclient.common.utils.text_wrap_formatter') def test_environment_session_create(self, mock_util): arglist = ['1234'] verifylist = [('id', '1234')] mock_util.return_value = '1abc2xyz' parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) # Check that columns are correct expected_columns = ['id'] self.assertEqual(expected_columns, columns) # Check that data is correct expected_data = ['1abc2xyz'] self.assertEqual(expected_data, data) class TestEnvironmentCreate(TestEnvironment): def setUp(self): super(TestEnvironmentCreate, self).setUp() self.environment_mock.create.return_value = [api_env.Environment(None, ENV_INFO)] # Command to test self.cmd = osc_env.EnvironmentCreate(self.app, None) @mock.patch('osc_lib.utils.get_item_properties') def test_environment_create_with_no_option(self, mock_util): arglist = ['fake'] verifylist = [('name', 'fake')] mock_util.return_value = ('1234', 'fake', 'ready', '2015-12-16T17:31:54', '2015-12-16T17:31:54') parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) # Check that columns are correct expected_columns = ['Id', 'Name', 'Status', 'Created', 'Updated'] self.assertEqual(expected_columns, columns) # Check that data is correct expected_data = [('1234', 'fake', 'ready', '2015-12-16T17:31:54', '2015-12-16T17:31:54')] self.assertEqual(expected_data, data) @mock.patch('osc_lib.utils.get_item_properties') def test_environment_create_with_region_option(self, mock_util): arglist = ['fake', '--region', 'region_one'] verifylist = [('name', 'fake'), ('region', 'region_one')] mock_util.return_value = ('1234', 'fake', 'ready', '2015-12-16T17:31:54', '2015-12-16T17:31:54') parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) # Check that correct arguments are passed self.environment_mock.create.assert_has_calls([mock.call( {'name': 'fake', 'region': 'region_one'})]) # Check that columns are correct expected_columns = ['Id', 'Name', 'Status', 'Created', 'Updated'] self.assertEqual(expected_columns, columns) # Check that data is correct expected_data = [('1234', 'fake', 'ready', '2015-12-16T17:31:54', '2015-12-16T17:31:54')] self.assertEqual(expected_data, data) @mock.patch('osc_lib.utils.get_item_properties') def test_environment_create_with_net_option(self, mock_util): arglist = ['fake', '--join-net-id', 'x1y2z3'] verifylist = [('name', 'fake'), ('join_net_id', 'x1y2z3')] mock_util.return_value = ('1234', 'fake', 'ready', '2015-12-16T17:31:54', '2015-12-16T17:31:54') parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) expected_call = { 'defaultNetworks': { 'environment': { 'internalNetworkName': 'x1y2z3', '?': { 'type': 'io.murano.resources.ExistingNeutronNetwork', 'id': mock.ANY } }, 'flat': None }, 'name': 'fake', 'region': None } # Check that correct arguments are passed self.environment_mock.create.assert_called_with(expected_call) # Check that columns are correct expected_columns = ['Id', 'Name', 'Status', 'Created', 'Updated'] self.assertEqual(expected_columns, columns) # Check that data is correct expected_data = [('1234', 'fake', 'ready', '2015-12-16T17:31:54', '2015-12-16T17:31:54')] self.assertEqual(expected_data, data) @mock.patch('osc_lib.utils.get_item_properties') def test_environment_create_with_subnet_option(self, mock_util): arglist = ['fake', '--join-subnet-id', 'x1y2z3'] verifylist = [('name', 'fake'), ('join_subnet_id', 'x1y2z3')] mock_util.return_value = ('1234', 'fake', 'ready', '2015-12-16T17:31:54', '2015-12-16T17:31:54') parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) expected_call = { 'defaultNetworks': { 'environment': { 'internalSubnetworkName': 'x1y2z3', '?': { 'type': 'io.murano.resources.ExistingNeutronNetwork', 'id': mock.ANY } }, 'flat': None }, 'name': 'fake', 'region': None } # Check that correct arguments are passed self.environment_mock.create.assert_called_with(expected_call) # Check that columns are correct expected_columns = ['Id', 'Name', 'Status', 'Created', 'Updated'] self.assertEqual(expected_columns, columns) # Check that data is correct expected_data = [('1234', 'fake', 'ready', '2015-12-16T17:31:54', '2015-12-16T17:31:54')] self.assertEqual(expected_data, data) class TestEnvironmentDelete(TestEnvironment): def setUp(self): super(TestEnvironmentDelete, self).setUp() self.environment_mock.delete.return_value = None self.environment_mock.list.return_value = [api_env.Environment(None, ENV_INFO)] # Command to test self.cmd = osc_env.EnvironmentDelete(self.app, None) @mock.patch('osc_lib.utils.get_item_properties') def test_environment_delete(self, mock_util): arglist = ['fake1', 'fake2'] verifylist = [('id', ['fake1', 'fake2'])] mock_util.return_value = ('1234', 'Environment of all tenants', 'fake deployed', '2015-12-16T17:31:54', '2015-12-16T17:31:54' ) parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) # Check that columns are correct expected_columns = ['Id', 'Name', 'Status', 'Created', 'Updated'] self.assertEqual(expected_columns, columns) # Check that data is correct expected_data = [('1234', 'Environment of all tenants', 'fake deployed', '2015-12-16T17:31:54', '2015-12-16T17:31:54')] self.assertEqual(expected_data, data) class TestEnvironmentDeploy(TestEnvironment): def setUp(self): super(TestEnvironmentDeploy, self).setUp() mock_to_dict = self.environment_mock.get.return_value.to_dict mock_to_dict.return_value = ENV_INFO # Command to test self.cmd = osc_env.EnvironmentDeploy(self.app, None) @mock.patch('oslo_serialization.jsonutils.dumps') def test_environment_deploy(self, mock_json): arglist = ['fake', '--session-id', 'abc123'] verifylist = [('id', 'fake'), ('session_id', 'abc123')] mock_json.return_value = ['fake services'] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) # Check that columns are correct expected_columns = ('created', 'id', 'name', 'networking', 'services', 'status', 'tenant_id', 'updated', 'version') self.assertEqual(expected_columns, columns) # Check that data is correct expected_data = ('2015-12-16T17:31:54', '1234', 'Fake Environment', {}, ['fake services'], 'fake deployed', 'xyz123', '2015-12-16T17:31:54', '1') self.assertEqual(expected_data, data) class TestEnvironmentAppsEdit(TestEnvironment): def setUp(self): super(TestEnvironmentAppsEdit, self).setUp() # Command to test self.cmd = osc_env.EnvironmentAppsEdit(self.app, None) def test_environment_apps_edit(self): fake = collections.namedtuple('fakeEnv', 'services') self.environment_mock.get.side_effect = [ fake(services=[ {'?': {'name': "foo"}} ]), ] temp_file = tempfile.NamedTemporaryFile(prefix="murano-test", mode='w') json.dump([ {'op': 'replace', 'path': '/0/?/name', 'value': "dummy" } ], temp_file) temp_file.file.flush() arglist = ['fake', '--session-id', 'abc123', temp_file.name] parsed_args = self.check_parser(self.cmd, arglist, []) self.cmd.take_action(parsed_args) self.services_mock.put.assert_called_once_with( 'fake', session_id='abc123', path='/', data=[{'?': {'name': 'dummy'}}] ) class TestEnvironmentModelShow(TestEnvironment): def setUp(self): super(TestEnvironmentModelShow, self).setUp() self.env_mock = \ self.app.client_manager.application_catalog.environments self.env_mock.get_model.return_value = ENV_MODEL # Command to test self.cmd = osc_env.EnvironmentModelShow(self.app, None) def test_environment_model_show_basic(self): arglist = ['env-id'] verifylist = [('id', 'env-id')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) # Check that columns are correct expected_columns = ('?', 'defaultNetworks', 'name', 'region') self.assertEqual(expected_columns, columns) # Check that data is correct self.assertItemsEqual(ENV_MODEL.values(), data) def test_environment_model_show_full(self): arglist = ['env-id', '--path', '/path', '--session-id', 'sess-id'] verifylist = [('id', 'env-id'), ('path', '/path'), ('session_id', 'sess-id')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) # Check that columns are correct expected_columns = ('?', 'defaultNetworks', 'name', 'region') self.assertEqual(expected_columns, columns) # Check that data is correct self.assertItemsEqual(ENV_MODEL.values(), data) class TestEnvironmentModelEdit(TestEnvironment): def setUp(self): super(TestEnvironmentModelEdit, self).setUp() self.env_mock = \ self.app.client_manager.application_catalog.environments self.env_mock.update_model.return_value = ENV_MODEL # Command to test self.cmd = osc_env.EnvironmentModelEdit(self.app, None) def test_environment_model_edit(self): temp_file = tempfile.NamedTemporaryFile(prefix="murano-test", mode='w') patch = [{'op': 'replace', 'path': '/name', 'value': 'dummy'}] json.dump(patch, temp_file) temp_file.file.flush() arglist = ['env-id', temp_file.name, '--session-id', 'sess-id'] verifylist = [('id', 'env-id'), ('filename', temp_file.name), ('session_id', 'sess-id')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) # Check that columns are correct expected_columns = ('?', 'defaultNetworks', 'name', 'region') self.assertEqual(expected_columns, columns) # Check that data is correct self.assertItemsEqual(ENV_MODEL.values(), data) python-muranoclient-1.3.0/muranoclient/tests/unit/osc/v1/fakes.py0000664000175000017500000000145113523272253025157 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 osc_lib.tests import utils class TestApplicationCatalog(utils.TestCommand): def setUp(self): super(TestApplicationCatalog, self).setUp() self.app.client_manager.application_catalog = mock.Mock() python-muranoclient-1.3.0/muranoclient/tests/unit/osc/__init__.py0000664000175000017500000000000013523272253025264 0ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/tests/unit/test_shell.py0000664000175000017500000017766613523272253025150 0ustar zuulzuul00000000000000# # Copyright (c) 2013 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 collections import filecmp import json import logging import os import re import shutil import sys import tempfile import fixtures from keystoneclient import fixture from keystoneclient.fixture import v2 as ks_v2_fixture from keystoneclient.fixture import v3 as ks_v3_fixture import mock from oslo_log import handlers from oslo_log import log import requests_mock import six from testtools import matchers from muranoclient.apiclient import exceptions from muranoclient.common import exceptions as common_exceptions from muranoclient.common import utils import muranoclient.shell from muranoclient.tests.unit import base from muranoclient.tests.unit import test_utils from muranoclient.v1 import shell as v1_shell make_pkg = test_utils.make_pkg FIXTURE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), 'fixture_data')) # RESULT_PACKAGE = os.path.join(FIXTURE_DIR, 'test-app.zip') FAKE_ENV = {'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_NAME': 'tenant_name', 'OS_AUTH_URL': 'http://no.where/v2.0'} FAKE_ENV2 = {'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_ID': 'tenant_id', 'OS_AUTH_URL': 'http://no.where/v2.0'} FAKE_ENV_v3 = {'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_ID': 'tenant_id', 'OS_USER_DOMAIN_NAME': 'domain_name', 'OS_AUTH_URL': 'http://no.where/v3'} def _create_ver_list(versions): return {'versions': {'values': versions}} class TestArgs(object): package_version = '' murano_repo_url = 'http://127.0.0.1' exists_action = '' dep_exists_action = '' is_public = False categories = [] class ShellTest(base.TestCaseShell): def make_env(self, exclude=None, fake_env=FAKE_ENV): env = dict((k, v) for k, v in fake_env.items() if k != exclude) self.useFixture(fixtures.MonkeyPatch('os.environ', env)) class ShellCommandTest(ShellTest): _msg_no_tenant_project = ('You must provide a project name or project' ' id via --os-project-name, --os-project-id,' ' env[OS_PROJECT_ID] or env[OS_PROJECT_NAME].' ' You may use os-project and os-tenant' ' interchangeably.',) def setUp(self): super(ShellCommandTest, self).setUp() def get_auth_endpoint(bound_self, args): return ('test', {}) self.useFixture(fixtures.MonkeyPatch( 'muranoclient.shell.MuranoShell._get_endpoint_and_kwargs', get_auth_endpoint)) self.client = mock.MagicMock() # To prevent log descriptors from being closed during # shell tests set a custom StreamHandler self.logger = log.getLogger(None).logger self.logger.level = logging.DEBUG self.color_handler = handlers.ColorHandler(sys.stdout) self.logger.addHandler(self.color_handler) def tearDown(self): super(ShellTest, self).tearDown() self.logger.removeHandler(self.color_handler) def shell(self, argstr, exitcodes=(0,)): orig = sys.stdout orig_stderr = sys.stderr try: sys.stdout = six.StringIO() sys.stderr = six.StringIO() _shell = muranoclient.shell.MuranoShell() _shell.main(argstr.split()) except SystemExit: exc_type, exc_value, exc_traceback = sys.exc_info() self.assertIn(exc_value.code, exitcodes) finally: stdout = sys.stdout.getvalue() sys.stdout.close() sys.stdout = orig stderr = sys.stderr.getvalue() sys.stderr.close() sys.stderr = orig_stderr return (stdout, stderr) def register_keystone_discovery_fixture(self, mreq): v2_url = "http://no.where/v2.0" v2_version = fixture.V2Discovery(v2_url) mreq.register_uri('GET', v2_url, json=_create_ver_list([v2_version]), status_code=200) def register_keystone_token_fixture(self, mreq): v2_token = ks_v2_fixture.Token(token_id='token') service = v2_token.add_service('application-catalog') service.add_endpoint('http://no.where', region='RegionOne') mreq.register_uri('POST', 'http://no.where/v2.0/tokens', json=v2_token, status_code=200) def test_help_unknown_command(self): self.assertRaises(exceptions.CommandError, self.shell, 'help foofoo') def test_help(self): required = [ '.*?^usage: murano', '.*?^\s+package-create\s+Create an application package.', '.*?^See "murano help COMMAND" for help on a specific command', ] stdout, stderr = self.shell('help') for r in required: self.assertThat((stdout + stderr), matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE)) def test_help_on_subcommand(self): required = [ '.*?^usage: murano package-create', '.*?^Create an application package.', ] stdout, stderr = self.shell('help package-create') for r in required: self.assertThat((stdout + stderr), matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE)) def test_help_no_options(self): required = [ '.*?^usage: murano', '.*?^\s+package-create\s+Create an application package', '.*?^See "murano help COMMAND" for help on a specific command', ] stdout, stderr = self.shell('') for r in required: self.assertThat((stdout + stderr), matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE)) def test_no_username(self): required = ('You must provide a username via either --os-username or ' 'env[OS_USERNAME] or a token via --os-auth-token or ' 'env[OS_AUTH_TOKEN]',) self.make_env(exclude='OS_USERNAME') try: self.shell('package-list') except exceptions.CommandError as message: self.assertEqual(required, message.args) else: self.fail('CommandError not raised') def test_no_tenant_name(self): required = self._msg_no_tenant_project self.make_env(exclude='OS_TENANT_NAME') try: self.shell('package-list') except exceptions.CommandError as message: self.assertEqual(required, message.args) else: self.fail('CommandError not raised') def test_no_tenant_id(self): required = self._msg_no_tenant_project self.make_env(exclude='OS_TENANT_ID', fake_env=FAKE_ENV2) try: self.shell('package-list') except exceptions.CommandError as message: self.assertEqual(required, message.args) else: self.fail('CommandError not raised') def test_no_auth_url(self): required = ('You must provide an auth url' ' via either --os-auth-url or via env[OS_AUTH_URL]',) self.make_env(exclude='OS_AUTH_URL') try: self.shell('package-list') except exceptions.CommandError as message: self.assertEqual(required, message.args) else: self.fail('CommandError not raised') @mock.patch('muranoclient.v1.packages.PackageManager') @requests_mock.mock() def test_package_list(self, mock_package_manager, m_requests): self.client.packages = mock_package_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('package-list') self.client.packages.filter.assert_called_once_with( include_disabled=False, owned=False) @mock.patch('muranoclient.v1.packages.PackageManager') @requests_mock.mock() def test_package_list_with_limit(self, mock_package_manager, m_requests): self.client.packages = mock_package_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('package-list --limit 10') self.client.packages.filter.assert_called_once_with( include_disabled=False, limit=10, owned=False) @mock.patch('muranoclient.v1.packages.PackageManager') @requests_mock.mock() def test_package_list_with_marker(self, mock_package_manager, m_requests): self.client.packages = mock_package_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('package-list --marker 12345') self.client.packages.filter.assert_called_once_with( include_disabled=False, marker='12345', owned=False) @mock.patch('muranoclient.v1.packages.PackageManager') @requests_mock.mock() def test_package_list_with_name(self, mock_package_manager, m_requests): self.client.packages = mock_package_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('package-list --name mysql') self.client.packages.filter.assert_called_once_with( name='mysql', include_disabled=False, owned=False) @mock.patch('muranoclient.v1.packages.PackageManager') @requests_mock.mock() def test_package_list_with_fqn(self, mock_package_manager, m_requests): self.client.packages = mock_package_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('package-list --fqn mysql') self.client.packages.filter.assert_called_once_with( fqn='mysql', include_disabled=False, owned=False) @mock.patch('muranoclient.v1.packages.PackageManager') @requests_mock.mock() def test_package_show(self, mock_package_manager, m_requests): self.client.packages = mock_package_manager() mock_package = mock.MagicMock() mock_package.class_definitions = '' mock_package.categories = '' mock_package.tags = '' mock_package.description = '' self.client.packages.get.return_value = mock_package self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('package-show 1234') self.client.packages.get.assert_called_with('1234') @mock.patch('muranoclient.v1.packages.PackageManager') @requests_mock.mock() def test_package_update(self, mock_package_manager, m_requests): self.client.packages = mock_package_manager() mock_package = mock.MagicMock() mock_package.class_definitions = '' mock_package.categories = '' mock_package.tags = '' mock_package.description = '' self.client.packages.get.return_value = mock_package self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('package-update 123 --is-public true') self.shell('package-update 123 --is-public false') self.shell('package-update 123 --is-public false --enabled t') self.shell('package-update 123 --name foo --description bar') self.shell('package-update 123 --tags a') self.shell('package-update 123 --tags a ' + '--is-public f --enabled f ' + '--name foo ' + '--description bar',) self.client.packages.update.assert_has_calls([ mock.call('123', {'is_public': True}), mock.call('123', {'is_public': False}), mock.call('123', {'enabled': True, 'is_public': False}), mock.call('123', {'name': 'foo', 'description': 'bar'}), mock.call('123', {'tags': ['a']}), mock.call('123', { 'tags': ['a'], 'is_public': False, 'enabled': False, 'name': 'foo', 'description': 'bar', }), ]) @mock.patch('muranoclient.v1.packages.PackageManager') @requests_mock.mock() def test_package_delete(self, mock_package_manager, m_requests): self.client.packages = mock_package_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('package-delete 1234 4321') self.client.packages.delete.assert_has_calls([ mock.call('1234'), mock.call('4321')]) self.assertEqual(2, self.client.packages.delete.call_count) @mock.patch('muranoclient.v1.sessions.SessionManager') @requests_mock.mock() def test_environment_session_create(self, mock_manager, m_requests): self.client.sessions = mock_manager() self.client.sessions.configure.return_value.id = '123' self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('environment-session-create 1234') self.client.sessions.configure.assert_has_calls([ mock.call('1234')]) @mock.patch('muranoclient.v1.environments.EnvironmentManager') @requests_mock.mock() def test_environment_create(self, mock_manager, m_requests): self.client.environments = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('environment-create foo') self.client.environments.create.assert_has_calls( [mock.call({'name': 'foo', 'region': None})]) self.client.environments.create.reset_mock() self.shell('environment-create --join-net 123 foo --region RegionOne') cc = self.client.environments.create expected_call = mock.call({ 'defaultNetworks': { 'environment': { 'internalNetworkName': '123', '?': { 'type': 'io.murano.resources.ExistingNeutronNetwork', 'id': mock.ANY } }, 'flat': None }, 'name': 'foo', 'region': 'RegionOne' }) self.assertEqual(expected_call, cc.call_args) @mock.patch('muranoclient.v1.environments.EnvironmentManager') @requests_mock.mock() def test_environment_list(self, mock_manager, m_requests): self.client.environments = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('environment-list') self.client.environments.list.assert_called_once_with(False, None) self.client.environments.list.reset_mock() self.shell('environment-list --all-tenants') self.client.environments.list.assert_called_once_with(True, None) self.client.environments.list.reset_mock() self.shell('environment-list --tenant ABC') self.client.environments.list.assert_called_once_with(False, 'ABC') @mock.patch('muranoclient.v1.environments.EnvironmentManager') @requests_mock.mock() def test_environment_delete(self, mock_manager, m_requests): self.client.environments = mock_manager() self.client.environments.find.return_value.id = '123' self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('environment-delete env1') self.client.environments.find.assert_has_calls([ mock.call(name='env1') ]) self.client.environments.delete.assert_has_calls([ mock.call('123', False) ]) @mock.patch('muranoclient.v1.environments.EnvironmentManager') @requests_mock.mock() def test_environment_delete_with_abandon(self, mock_manager, m_requests): self.client.environments = mock_manager() self.client.environments.find.return_value.id = '123' self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('environment-delete env1 --abandon') self.client.environments.find.assert_has_calls([ mock.call(name='env1') ]) self.client.environments.delete.assert_has_calls([ mock.call('123', True) ]) @mock.patch('muranoclient.v1.environments.EnvironmentManager') @requests_mock.mock() def test_environment_rename(self, mock_manager, m_requests): self.client.environments = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('environment-rename old-name-or-id new-name') self.client.environments.find.assert_called_once_with( name='old-name-or-id') self.assertEqual(1, self.client.environments.update.call_count) @mock.patch('muranoclient.v1.environments.EnvironmentManager') @requests_mock.mock() def test_environment_show(self, mock_manager, m_requests): self.client.environments = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('environment-show env-id-or-name') self.client.environments.find.assert_called_once_with( name='env-id-or-name') @mock.patch('muranoclient.v1.environments.EnvironmentManager') @requests_mock.mock() def test_environment_model_show_basic(self, mock_manager, m_requests): self.client.environments = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('environment-model-show env-id') self.client.environments.get_model.assert_called_once_with( 'env-id', '/', None) @mock.patch('muranoclient.v1.environments.EnvironmentManager') @requests_mock.mock() def test_environment_model_show_full(self, mock_manager, m_requests): self.client.environments = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('environment-model-show env-id ' '--path /path ' '--session-id sess-id') self.client.environments.get_model.assert_called_once_with( 'env-id', '/path', 'sess-id') @mock.patch('muranoclient.v1.environments.EnvironmentManager') @requests_mock.mock() def test_environment_model_show_path_with_q(self, mock_manager, m_requests): self.client.environments = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('environment-model-show env-id ' '--path /?/path') self.client.environments.get_model.assert_called_once_with( 'env-id', '/%3F/path', None) @mock.patch('muranoclient.v1.environments.EnvironmentManager') @requests_mock.mock() def test_environment_model_edit(self, mock_manager, m_requests): self.client.environments = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.client.environments.get_model.return_value = {'name': "foo"} temp_file = tempfile.NamedTemporaryFile(prefix="murano-test", mode='w') patch = [{'op': 'replace', 'path': '/name', 'value': 'dummy'}] json.dump(patch, temp_file) temp_file.file.flush() self.shell('environment-model-edit env-id {0} ' '--session-id sess-id'.format(temp_file.name)) self.client.environments.update_model.assert_called_once_with( 'env-id', patch, 'sess-id') @mock.patch('muranoclient.v1.environments.EnvironmentManager') @mock.patch('muranoclient.v1.sessions.SessionManager') @requests_mock.mock() def test_environment_deploy(self, mock_manager, env_manager, m_requests): self.client.sessions = mock_manager() self.client.environments = env_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('environment-deploy 12345 --session-id 54321') self.client.sessions.deploy.assert_called_once_with( '12345', '54321') @mock.patch('muranoclient.v1.environments.EnvironmentManager') @requests_mock.mock() def test_environment_show_session(self, mock_manager, m_requests): self.client.environments = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('environment-show 12345 --session-id 12345') self.client.environments.get.assert_called_once_with( 12345, session_id='12345') @mock.patch('muranoclient.v1.actions.ActionManager') @requests_mock.mock() def test_environment_action_call(self, mock_manager, m_requests): self.client.actions = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('environment-action-call 12345 --action-id 54321') self.client.actions.call.assert_called_once_with( '12345', '54321', arguments={}) @mock.patch('muranoclient.v1.actions.ActionManager') @requests_mock.mock() def test_environment_action_call_args(self, mock_manager, m_requests): self.client.actions = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell("""environment-action-call 12345 --action-id 54321 --arguments foo=bar dictArg={"key1":"value1","key2":"value2"} listArg=["item1","item2","item3"] nullArg=null stringArg="null" intArg=5 compoundArg=["foo",14,{"key1":null,"key2":8}]""") self.client.actions.call.assert_called_once_with( '12345', '54321', arguments={ 'foo': 'bar', 'dictArg': {u'key1': u'value1', u'key2': u'value2'}, 'listArg': [u'item1', u'item2', u'item3'], 'nullArg': None, 'stringArg': u'null', 'intArg': 5, 'compoundArg': [u'foo', 14, {u'key1': None, u'key2': 8}] }) @mock.patch('muranoclient.v1.actions.ActionManager') @requests_mock.mock() def test_environment_action_get_result(self, mock_manager, m_requests): self.client.actions = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('environment-action-get-result 12345 --task-id 54321') self.client.actions.get_result.assert_called_once_with( '12345', '54321') @mock.patch('muranoclient.v1.static_actions.StaticActionManager') @requests_mock.mock() def test_static_action_call_basic(self, mock_manager, m_requests): self.client.static_actions = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('static-action-call class.name method.name') self.client.static_actions.call.assert_called_once_with({ "className": 'class.name', "methodName": 'method.name', "packageName": None, "classVersion": '=0', "parameters": {} }) @mock.patch('muranoclient.v1.static_actions.StaticActionManager') @requests_mock.mock() def test_static_action_call_full(self, mock_manager, m_requests): self.client.static_actions = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('static-action-call class.name method.name ' '--package-name package.name --class-version ">1"') self.client.static_actions.call.assert_called_once_with({ "className": 'class.name', "methodName": 'method.name', "packageName": 'package.name', "classVersion": '">1"', "parameters": {} }) @mock.patch('muranoclient.v1.static_actions.StaticActionManager') @requests_mock.mock() def test_static_action_call_string_args(self, mock_manager, m_requests): self.client.static_actions = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('static-action-call class.name method.name ' '--arguments food=spam parrot=dead') self.client.static_actions.call.assert_called_once_with({ "className": 'class.name', "methodName": 'method.name', "packageName": None, "classVersion": '=0', "parameters": {'food': 'spam', 'parrot': 'dead'} }) @mock.patch('muranoclient.v1.static_actions.StaticActionManager') @requests_mock.mock() def test_static_action_call_json_args(self, mock_manager, m_requests): self.client.static_actions = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell("""static-action-call class.name method.name --arguments dictArg={"key1":"value1","key2":"value2"} listArg=["item1","item2","item3"] nullArg=null stringArg="null" intArg=5 compoundArg=["foo",14,{"key1":null,"key2":8}]""") self.client.static_actions.call.assert_called_once_with({ "className": 'class.name', "methodName": 'method.name', "packageName": None, "classVersion": '=0', "parameters": { 'dictArg': {u'key1': u'value1', u'key2': u'value2'}, 'listArg': [u'item1', u'item2', u'item3'], 'nullArg': None, 'stringArg': u'null', 'intArg': 5, 'compoundArg': [u'foo', 14, {u'key1': None, u'key2': 8}] } }) @mock.patch('muranoclient.v1.schemas.SchemaManager') @requests_mock.mock() def test_class_schema(self, mock_manager, m_requests): self.client.schemas = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('class-schema class.name') self.client.schemas.get.assert_called_once_with( 'class.name', [], package_name=None, class_version='=0' ) @mock.patch('muranoclient.v1.schemas.SchemaManager') @requests_mock.mock() def test_class_schema_with_methods(self, mock_manager, m_requests): self.client.schemas = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('class-schema class.name method1 method2') self.client.schemas.get.assert_called_once_with( 'class.name', ['method1', 'method2'], package_name=None, class_version='=0' ) @mock.patch('muranoclient.v1.schemas.SchemaManager') @requests_mock.mock() def test_class_schema_full(self, mock_manager, m_requests): self.client.schemas = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('class-schema class.name method1 method2 ' '--class-version >1.2.3 --package-name foo.bar') self.client.schemas.get.assert_called_once_with( 'class.name', ['method1', 'method2'], package_name='foo.bar', class_version='>1.2.3' ) @mock.patch('muranoclient.v1.templates.EnvTemplateManager') @requests_mock.mock() def test_env_template_delete(self, mock_manager, m_requests): self.client.env_templates = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('env-template-delete env1 env2') self.client.env_templates.delete.assert_has_calls([ mock.call('env1'), mock.call('env2')]) @mock.patch('muranoclient.v1.templates.EnvTemplateManager') @requests_mock.mock() def test_env_template_create(self, mock_manager, m_requests): self.client.env_templates = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('env-template-create env-name') self.client.env_templates.create.assert_called_once_with( {'name': 'env-name', 'is_public': False}) @mock.patch('muranoclient.v1.templates.EnvTemplateManager') @requests_mock.mock() def test_env_template_create_public(self, mock_manager, m_requests): self.client.env_templates = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('env-template-create --is-public env-name') self.client.env_templates.create.assert_called_once_with( {'name': 'env-name', 'is_public': True}) @mock.patch('muranoclient.v1.templates.EnvTemplateManager') @requests_mock.mock() def test_env_template_show(self, mock_manager, m_requests): self.client.env_templates = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('env-template-show env-id') self.client.env_templates.get.assert_called_once_with('env-id') @mock.patch('muranoclient.v1.templates.EnvTemplateManager') @requests_mock.mock() def test_env_template_create_env(self, mock_manager, m_requests): self.client.env_templates = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('env-template-create-env env-id env-name') self.client.env_templates.create_env.\ assert_called_once_with('env-id', {"name": 'env-name'}) @mock.patch('muranoclient.v1.templates.EnvTemplateManager') @requests_mock.mock() def test_env_template_create_env_with_region(self, mock_manager, m_requests): self.client.env_templates = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('env-template-create-env env-id env-name --region Region') self.client.env_templates.create_env.\ assert_called_once_with('env-id', {"name": 'env-name', "region": 'Region'}) @mock.patch('muranoclient.v1.templates.EnvTemplateManager') @requests_mock.mock() def test_env_template_clone(self, mock_manager, m_requests): self.client.env_templates = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('env-template-clone env-id env-name') self.client.env_templates.clone.assert_called_once_with( 'env-id', 'env-name') @mock.patch('muranoclient.v1.environments.EnvironmentManager') @mock.patch('muranoclient.v1.deployments.DeploymentManager') @requests_mock.mock() def test_deployments_show(self, mock_deployment_manager, mock_env_manager, m_requests): self.client.deployments = mock_deployment_manager() self.client.environments = mock_env_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('deployment-list env-id-or-name') self.client.environments.find.assert_called_once_with( name='env-id-or-name') self.assertEqual(1, self.client.deployments.list.call_count) @mock.patch('muranoclient.v1.deployments.DeploymentManager') @requests_mock.mock() def test_deployments_list_all_environments(self, mock_deployment_manager, m_requests): self.client.deployments = mock_deployment_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('deployment-list --all-environments') self.client.deployments.list.assert_called_once_with( None, True) @mock.patch('muranoclient.v1.deployments.DeploymentManager') @requests_mock.mock() def test_deployments_list_negative(self, mock_deployment_manager, m_requests): self.client.deployments = mock_deployment_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) e = self.assertRaises( exceptions.CommandError, self.shell, 'deployment-list env-id --all-environments') self.assertIn( 'Environment ID and all-environments flag cannot both be set.', e.__str__()) self.assertFalse(self.client.deployments.list.called) e = self.assertRaises( exceptions.CommandError, self.shell, 'deployment-list') self.assertIn( 'Either environment ID or all-environments flag must be set.', e.__str__()) self.assertFalse(self.client.deployments.list.called) @mock.patch('muranoclient.v1.services.ServiceManager') @mock.patch('muranoclient.v1.environments.EnvironmentManager') @requests_mock.mock() def test_environment_apps_edit(self, mock_env_manager, mock_services, m_requests): self.client.environments = mock_env_manager() self.client.services = mock_services() fake = collections.namedtuple('fakeEnv', 'services') self.client.environments.get.side_effect = [ fake(services=[ {'?': {'name': "foo"}} ]), ] temp_file = tempfile.NamedTemporaryFile(prefix="murano-test", mode='w') json.dump([ {'op': 'replace', 'path': '/0/?/name', 'value': "dummy" } ], temp_file) temp_file.file.flush() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) self.shell('environment-apps-edit 12345 {0} --session-id 4321'.format( temp_file.name)) self.client.services.put.assert_called_once_with( '12345', session_id='4321', path='/', data=[{'?': {'name': 'dummy'}}] ) @mock.patch('muranoclient.v1.services.ServiceManager') @requests_mock.mock() def test_app_show(self, mock_services, m_requests): self.client.services = mock_services() mock_app = mock.MagicMock() mock_app.name = "app_name" setattr(mock_app, '?', {'type': 'app_type', 'id': 'app_id'}) self.client.services.list.return_value = [mock_app] self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) result = self.shell('app-show env-id') required = ['Id', 'Name', 'Type', 'app_id', 'app_name', 'app_type'] for r in required: self.assertIn(r, result[0]) self.client.services.list.assert_called_once_with('env-id') @mock.patch('muranoclient.v1.services.ServiceManager') @requests_mock.mock() def test_app_show_empty_list(self, mock_services, m_requests): self.client.services = mock_services() self.client.services.list.return_value = [] self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) result = self.shell('app-show env-id') required = ['Id', 'Name', 'Type'] for r in required: self.assertIn(r, result[0]) self.client.services.list.assert_called_once_with('env-id') @mock.patch('muranoclient.v1.services.ServiceManager') @requests_mock.mock() def test_app_show_with_path(self, mock_services, m_requests): self.client.services = mock_services() mock_app = mock.MagicMock() mock_app.name = "app_name" setattr(mock_app, '?', {'type': 'app_type', 'id': 'app_id'}) self.client.services.get.return_value = mock_app self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) result = self.shell('app-show env-id --path app-id') required = ['Property', 'Value'] for r in required: self.assertIn(r, result[0]) self.client.services.get.assert_called_once_with('env-id', '/app-id') @mock.patch('muranoclient.v1.categories.CategoryManager') @requests_mock.mock() def test_category_list(self, mock_manager, m_requests): self.client.categories = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) result = self.shell('category-list') required = ['ID', 'Name'] for r in required: self.assertIn(r, result[0]) self.client.categories.list.assert_called_once_with() @mock.patch('muranoclient.v1.packages.PackageManager') @mock.patch('muranoclient.v1.categories.CategoryManager') @requests_mock.mock() def test_category_show(self, category_manager, pkg_manager, m_requests): self.client.packages = pkg_manager() self.client.categories = category_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) result = self.shell('category-show category-id') required = ['Property', 'Value', 'id', 'name', 'packages'] for r in required: self.assertIn(r, result[0]) self.client.categories.get.assert_called_once_with('category-id') @mock.patch('muranoclient.v1.categories.CategoryManager') @requests_mock.mock() def test_category_create(self, mock_manager, m_requests): self.client.categories = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) result = self.shell('category-create category-name') required = ['ID', 'Name'] for r in required: self.assertIn(r, result[0]) self.client.categories.add.assert_called_once_with( {'name': 'category-name'}) @mock.patch('muranoclient.v1.categories.CategoryManager') @requests_mock.mock() def test_category_delete(self, mock_manager, m_requests): self.client.categories = mock_manager() self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) result = self.shell('category-delete category-id') required = ['ID', 'Name'] for r in required: self.assertIn(r, result[0]) self.client.categories.delete.assert_called_once_with('category-id') self.client.categories.delete.side_effect =\ common_exceptions.HTTPNotFound() ex = self.assertRaises(exceptions.CommandError, self.shell, 'category-delete category-id') expected = 'Unable to find and delete any of the specified categories.' self.assertEqual(expected, six.text_type(ex)) class ShellPackagesOperations(ShellCommandTest): @requests_mock.mock() def test_create_hot_based_package(self, m_requests): self.useFixture(fixtures.MonkeyPatch( 'muranoclient.v1.client.Client', mock.MagicMock)) heat_template = os.path.join(FIXTURE_DIR, 'heat-template.yaml') logo = os.path.join(FIXTURE_DIR, 'logo.png') self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) with tempfile.NamedTemporaryFile() as f: RESULT_PACKAGE = f.name c = "package-create --template={0} --output={1} -l={2}".format( heat_template, RESULT_PACKAGE, logo) stdout, stderr = self.shell(c) matchers.MatchesRegex((stdout + stderr), "Application package " "is available at {0}".format(RESULT_PACKAGE)) @requests_mock.mock() def test_create_mpl_package(self, m_requests): self.useFixture(fixtures.MonkeyPatch( 'muranoclient.v1.client.Client', mock.MagicMock)) classes_dir = os.path.join(FIXTURE_DIR, 'test-app', 'Classes') resources_dir = os.path.join(FIXTURE_DIR, 'test-app', 'Resources') ui = os.path.join(FIXTURE_DIR, 'test-app', 'ui.yaml') self.make_env() self.register_keystone_discovery_fixture(m_requests) self.register_keystone_token_fixture(m_requests) with tempfile.NamedTemporaryFile() as f: RESULT_PACKAGE = f.name stdout, stderr = self.shell( "package-create -c={0} -r={1} -u={2} -o={3}".format( classes_dir, resources_dir, ui, RESULT_PACKAGE)) matchers.MatchesRegex((stdout + stderr), "Application package " "is available at {0}".format(RESULT_PACKAGE)) @mock.patch('muranoclient.common.utils.Package.from_file') def test_package_import(self, from_file): args = TestArgs() with tempfile.NamedTemporaryFile() as f: RESULT_PACKAGE = f.name args.filename = [RESULT_PACKAGE] args.categories = ['Cat1', 'Cat2 with space'] args.is_public = True pkg = make_pkg({'FullName': RESULT_PACKAGE}) from_file.return_value = utils.Package(utils.File(pkg)) v1_shell.do_package_import(self.client, args) self.client.packages.create.assert_called_once_with({ 'categories': ['Cat1', 'Cat2 with space'], 'is_public': True }, {RESULT_PACKAGE: mock.ANY},) def _test_conflict(self, packages, from_file, raw_input_mock, input_action, exists_action=''): packages.create = mock.MagicMock( side_effect=[common_exceptions.HTTPConflict("Conflict"), None]) packages.filter.return_value = [mock.Mock(id='test_id')] raw_input_mock.return_value = input_action args = TestArgs() args.exists_action = exists_action with tempfile.NamedTemporaryFile() as f: args.filename = [f.name] pkg = make_pkg({'FullName': f.name}) from_file.return_value = utils.Package(utils.File(pkg)) v1_shell.do_package_import(self.client, args) return f.name @mock.patch('six.moves.input') @mock.patch('muranoclient.common.utils.Package.from_file') def test_package_import_conflict_skip(self, from_file, raw_input_mock): name = self._test_conflict( self.client.packages, from_file, raw_input_mock, 's', ) self.client.packages.create.assert_called_once_with({ 'is_public': False, }, {name: mock.ANY},) @mock.patch('six.moves.input') @mock.patch('muranoclient.common.utils.Package.from_file') def test_package_import_conflict_skip_ea(self, from_file, raw_input_mock): name = self._test_conflict( self.client.packages, from_file, raw_input_mock, '', exists_action='s', ) self.client.packages.create.assert_called_once_with({ 'is_public': False, }, {name: mock.ANY},) self.assertFalse(raw_input_mock.called) @mock.patch('six.moves.input') @mock.patch('muranoclient.common.utils.Package.from_file') def test_package_import_conflict_abort(self, from_file, raw_input_mock): self.assertRaises(SystemExit, self._test_conflict, self.client.packages, from_file, raw_input_mock, 'a', ) self.client.packages.create.assert_called_once_with({ 'is_public': False, }, mock.ANY,) @mock.patch('six.moves.input') @mock.patch('muranoclient.common.utils.Package.from_file') def test_package_import_conflict_abort_ea(self, from_file, raw_input_mock): self.assertRaises(SystemExit, self._test_conflict, self.client.packages, from_file, raw_input_mock, '', exists_action='a', ) self.client.packages.create.assert_called_once_with({ 'is_public': False, }, mock.ANY,) self.assertFalse(raw_input_mock.called) @mock.patch('six.moves.input') @mock.patch('muranoclient.common.utils.Package.from_file') def test_package_import_conflict_update(self, from_file, raw_input_mock): name = self._test_conflict( self.client.packages, from_file, raw_input_mock, 'u', ) self.client.packages.delete.assert_called_once_with('test_id') self.client.packages.create.assert_has_calls( [ mock.call({'is_public': False}, {name: mock.ANY},), mock.call({'is_public': False}, {name: mock.ANY},) ], any_order=True, ) self.assertEqual(2, self.client.packages.create.call_count) @mock.patch('six.moves.input') @mock.patch('muranoclient.common.utils.Package.from_file') def test_package_import_conflict_update_ea(self, from_file, raw_input_mock): name = self._test_conflict( self.client.packages, from_file, raw_input_mock, '', exists_action='u', ) self.client.packages.delete.assert_called_once_with('test_id') self.client.packages.create.assert_has_calls( [ mock.call({'is_public': False}, {name: mock.ANY},), mock.call({'is_public': False}, {name: mock.ANY},) ], any_order=True, ) self.assertEqual(2, self.client.packages.create.call_count) self.assertFalse(raw_input_mock.called) def _test_conflict_dep(self, packages, from_file, dep_exists_action=''): packages.create = mock.MagicMock( side_effect=[common_exceptions.HTTPConflict("Conflict"), common_exceptions.HTTPConflict("Conflict"), None]) packages.filter.return_value = [mock.Mock(id='test_id')] args = TestArgs() args.exists_action = 's' args.dep_exists_action = dep_exists_action args.filename = ['first_app'] pkg1 = make_pkg( {'FullName': 'first_app', 'Require': {'second_app': '1.0'}, }) pkg2 = make_pkg({'FullName': 'second_app', }) def side_effect(name): if 'first_app' in name: return utils.Package(utils.File(pkg1)) if 'second_app' in name: return utils.Package(utils.File(pkg2)) from_file.side_effect = side_effect v1_shell.do_package_import(self.client, args) @mock.patch('muranoclient.common.utils.Package.from_file') def test_package_import_conflict_dep_skip_ea(self, from_file): self._test_conflict_dep( self.client.packages, from_file, dep_exists_action='s', ) self.client.packages.create.assert_has_calls( [ mock.call({'is_public': False}, {'first_app': mock.ANY}), mock.call({'is_public': False}, {'second_app': mock.ANY}), ], any_order=True, ) self.assertEqual(2, self.client.packages.create.call_count) @mock.patch('muranoclient.common.utils.Package.from_file') def test_package_import_conflict_dep_abort_ea(self, from_file): self.assertRaises(SystemExit, self._test_conflict_dep, self.client.packages, from_file, dep_exists_action='a', ) self.client.packages.create.assert_called_with({ 'is_public': False, }, {'second_app': mock.ANY},) @mock.patch('muranoclient.common.utils.Package.from_file') def test_package_import_conflict_dep_update_ea(self, from_file): self.assertRaises(SystemExit, self._test_conflict_dep, self.client.packages, from_file, dep_exists_action='u', ) self.assertTrue(self.client.packages.delete.called) self.client.packages.create.assert_has_calls( [ mock.call({'is_public': False}, {'first_app': mock.ANY}), mock.call({'is_public': False}, {'second_app': mock.ANY}), mock.call({'is_public': False}, {'second_app': mock.ANY}), ], any_order=True, ) self.assertGreater(self.client.packages.create.call_count, 2) self.assertLess(self.client.packages.create.call_count, 5) @mock.patch('muranoclient.common.utils.Package.from_file') def test_package_import_no_categories(self, from_file): args = TestArgs() with tempfile.NamedTemporaryFile() as f: RESULT_PACKAGE = f.name pkg = make_pkg({'FullName': RESULT_PACKAGE}) from_file.return_value = utils.Package(utils.File(pkg)) args.filename = [RESULT_PACKAGE] args.categories = None args.is_public = False v1_shell.do_package_import(self.client, args) self.client.packages.create.assert_called_once_with( {'is_public': False}, {RESULT_PACKAGE: mock.ANY}, ) @requests_mock.mock() @mock.patch('muranoclient.common.utils.Package.from_file') def test_package_import_url(self, rm, from_file): args = TestArgs() args.filename = ["http://127.0.0.1/test_package.zip"] args.categories = None args.is_public = False pkg = make_pkg({'FullName': 'test_package'}) from_file.return_value = utils.Package(utils.File(pkg)) rm.get(args.filename[0], body=make_pkg({'FullName': 'test_package'})) v1_shell.do_package_import(self.client, args) self.client.packages.create.assert_called_once_with( {'is_public': False}, {'test_package': mock.ANY}, ) @requests_mock.mock() @mock.patch('muranoclient.common.utils.Package.from_file') def test_package_import_by_name(self, rm, from_file): args = TestArgs() args.filename = ["io.test.apps.test_application"] args.categories = None args.is_public = False args.murano_repo_url = "http://127.0.0.1" pkg = make_pkg({'FullName': args.filename[0]}) from_file.return_value = utils.Package(utils.File(pkg)) rm.get(args.murano_repo_url + '/apps/' + args.filename[0] + '.zip', body=make_pkg({'FullName': 'first_app'})) v1_shell.do_package_import(self.client, args) self.assertTrue(self.client.packages.create.called) self.client.packages.create.assert_called_once_with( {'is_public': False}, {args.filename[0]: mock.ANY}, ) @requests_mock.mock() def test_package_import_multiple(self, rm): args = TestArgs() args.filename = ["io.test.apps.test_application", "http://127.0.0.1/test_app2.zip", ] args.categories = None args.is_public = False args.murano_repo_url = "http://127.0.0.1" rm.get(args.murano_repo_url + '/apps/' + args.filename[0] + '.zip', body=make_pkg({'FullName': 'first_app'})) rm.get(args.filename[1], body=make_pkg({'FullName': 'second_app'})) v1_shell.do_package_import(self.client, args) self.assertTrue(self.client.packages.create.called) self.client.packages.create.assert_has_calls( [ mock.call({'is_public': False}, {'first_app': mock.ANY}), mock.call({'is_public': False}, {'second_app': mock.ANY}), ], any_order=True, ) @requests_mock.mock() def test_import_bundle_by_name(self, m): """Asserts bundle import calls packages create once for each pkg.""" pkg1 = make_pkg({'FullName': 'first_app'}) pkg2 = make_pkg({'FullName': 'second_app'}) m.get(TestArgs.murano_repo_url + '/apps/first_app.zip', body=pkg1) m.get(TestArgs.murano_repo_url + '/apps/second_app.1.0.zip', body=pkg2) s = six.StringIO() bundle_contents = {'Packages': [ {'Name': 'first_app'}, {'Name': 'second_app', 'Version': '1.0'} ]} json.dump(bundle_contents, s) s = six.BytesIO(s.getvalue().encode('ascii')) m.get(TestArgs.murano_repo_url + '/bundles/test_bundle.bundle', body=s) args = TestArgs() args.filename = ["test_bundle"] v1_shell.do_bundle_import(self.client, args) self.client.packages.create.assert_has_calls( [ mock.call({'is_public': False}, {'first_app': mock.ANY}), mock.call({'is_public': False}, {'second_app': mock.ANY}), ], any_order=True, ) @requests_mock.mock() def test_import_bundle_dependencies(self, m): """Test bundle import calls Asserts bundle import calls packages create once for each pkg, including dependencies. """ pkg1 = make_pkg( {'FullName': 'first_app', 'Require': {'second_app': '1.0'}, }) pkg2 = make_pkg({'FullName': 'second_app'}) m.get(TestArgs.murano_repo_url + '/apps/first_app.zip', body=pkg1) m.get(TestArgs.murano_repo_url + '/apps/second_app.1.0.zip', body=pkg2) s = six.StringIO() # bundle only contains 1st package bundle_contents = {'Packages': [ {'Name': 'first_app'}, ]} json.dump(bundle_contents, s) s = six.BytesIO(s.getvalue().encode('ascii')) m.get(TestArgs.murano_repo_url + '/bundles/test_bundle.bundle', body=s) args = TestArgs() args.filename = ["test_bundle"] v1_shell.do_bundle_import(self.client, args) self.client.packages.create.assert_has_calls( [ mock.call({'is_public': False}, {'first_app': mock.ANY}), mock.call({'is_public': False}, {'second_app': mock.ANY}), ], any_order=True, ) @requests_mock.mock() def test_import_bundle_by_url(self, m): """Asserts bundle import calls packages create once for each pkg.""" pkg1 = make_pkg({'FullName': 'first_app'}) pkg2 = make_pkg({'FullName': 'second_app'}) m.get(TestArgs.murano_repo_url + '/apps/first_app.zip', body=pkg1) m.get(TestArgs.murano_repo_url + '/apps/second_app.1.0.zip', body=pkg2) s = six.StringIO() bundle_contents = {'Packages': [ {'Name': 'first_app'}, {'Name': 'second_app', 'Version': '1.0'} ]} json.dump(bundle_contents, s) s = six.BytesIO(s.getvalue().encode('ascii')) url = 'http://127.0.0.2/test_bundle.bundle' m.get(url, body=s) args = TestArgs() args.filename = [url] v1_shell.do_bundle_import(self.client, args) self.client.packages.create.assert_has_calls( [ mock.call({'is_public': False}, {'first_app': mock.ANY}), mock.call({'is_public': False}, {'second_app': mock.ANY}), ], any_order=True, ) @requests_mock.mock() def test_import_bundle_wrong_url(self, m): url = 'http://127.0.0.2/test_bundle.bundle' m.get(url, status_code=404) args = TestArgs() args.filename = [url] v1_shell.do_bundle_import(self.client, args) self.assertFalse(self.client.packages.create.called) @requests_mock.mock() def test_import_bundle_no_bundle(self, m): url = 'http://127.0.0.1/bundles/test_bundle.bundle' m.get(url, status_code=404) args = TestArgs() args.filename = ["test_bundle"] v1_shell.do_bundle_import(self.client, args) self.assertFalse(self.client.packages.create.called) @requests_mock.mock() def test_import_local_bundle(self, m): """Asserts local bundles are first searched locally.""" tmp_dir = tempfile.mkdtemp() bundle_file = os.path.join(tmp_dir, 'bundle.bundle') with open(os.path.join(tmp_dir, 'bundle.bundle'), 'w') as f: bundle_contents = {'Packages': [ {'Name': 'first_app'}, {'Name': 'second_app', 'Version': '1.0'} ]} json.dump(bundle_contents, f) pkg1 = make_pkg({'FullName': 'first_app', 'Require': {'third_app': None}}) pkg2 = make_pkg({'FullName': 'second_app'}) pkg3 = make_pkg({'FullName': 'third_app'}) with open(os.path.join(tmp_dir, 'first_app'), 'wb') as f: f.write(pkg1.read()) with open(os.path.join(tmp_dir, 'third_app'), 'wb') as f: f.write(pkg3.read()) m.get(TestArgs.murano_repo_url + '/apps/first_app.zip', status_code=404) m.get(TestArgs.murano_repo_url + '/apps/second_app.1.0.zip', body=pkg2) m.get(TestArgs.murano_repo_url + '/apps/third_app.zip', status_code=404) args = TestArgs() args.filename = [bundle_file] v1_shell.do_bundle_import(self.client, args) self.client.packages.create.assert_has_calls( [ mock.call({'is_public': False}, {'first_app': mock.ANY}), mock.call({'is_public': False}, {'second_app': mock.ANY}), mock.call({'is_public': False}, {'third_app': mock.ANY}), ], any_order=True, ) shutil.rmtree(tmp_dir) @requests_mock.mock() def test_save_bundle(self, m): tmp_dir = tempfile.mkdtemp() pkg = make_pkg({'FullName': 'test_app'}) expected_pkg = tempfile.NamedTemporaryFile(delete=False) shutil.copyfileobj(pkg, expected_pkg) pkg.seek(0) m.get(TestArgs.murano_repo_url + '/apps/test_app.zip', body=pkg) s = six.StringIO() expected_bundle = {'Packages': [ {'Name': 'test_app'}, ]} json.dump(expected_bundle, s) s = six.BytesIO(s.getvalue().encode('ascii')) m.get(TestArgs.murano_repo_url + '/bundles/test_bundle.bundle', body=s) args = TestArgs() args.filename = "test_bundle" args.path = tmp_dir v1_shell.do_bundle_save(self.client, args) expected_pkg.seek(0) result_bundle = json.load(open(os.path.join( tmp_dir, 'test_bundle.bundle'))) result_pkg = os.path.join(tmp_dir, 'test_app.zip') self.assertEqual(expected_bundle, result_bundle) self.assertTrue(filecmp.cmp(expected_pkg.name, result_pkg)) os.remove(expected_pkg.name) shutil.rmtree(tmp_dir) @requests_mock.mock() def test_package_save(self, m): args = TestArgs() tmp_dir = tempfile.mkdtemp() args.package = ["test_app1", "http://127.0.0.1/test_app2.zip"] args.path = tmp_dir pkgs = [ make_pkg( {'FullName': 'test_app1', 'Require': {'test_app3': '1.0'}}), make_pkg({'FullName': 'test_app2'}), make_pkg({'FullName': 'test_app3'}) ] m.get(TestArgs.murano_repo_url + '/apps/' + args.package[0] + '.zip', body=pkgs[0]) m.get(args.package[1], body=pkgs[1]) m.get(TestArgs.murano_repo_url + '/apps/' + 'test_app3.1.0.zip', body=pkgs[2]) expected_pkgs = [] for i in range(0, 3): expected_pkgs.append(tempfile.NamedTemporaryFile(delete=False)) shutil.copyfileobj(pkgs[i], expected_pkgs[i]) pkgs[i].seek(0) v1_shell.do_package_save(self.client, args) file_names = ['test_app1.zip', 'test_app2.zip', 'test_app3.1.0.zip'] for i in range(0, 3): expected_pkgs[i].seek(0) result_pkg = os.path.join(tmp_dir, file_names[i]) self.assertTrue(filecmp.cmp(expected_pkgs[i].name, result_pkg)) os.remove(expected_pkgs[i].name) shutil.rmtree(tmp_dir) class ShellPackagesOperationsV3(ShellPackagesOperations): def make_env(self, exclude=None, fake_env=FAKE_ENV): if 'OS_AUTH_URL' in fake_env: fake_env.update({'OS_AUTH_URL': 'http://no.where/v3'}) env = dict((k, v) for k, v in fake_env.items() if k != exclude) self.useFixture(fixtures.MonkeyPatch('os.environ', env)) def register_keystone_discovery_fixture(self, mreq): v3_url = "http://no.where/v3" v3_version = fixture.V3Discovery(v3_url) mreq.register_uri('GET', v3_url, json=_create_ver_list([v3_version]), status_code=200) def register_keystone_token_fixture(self, mreq): v3_token = ks_v3_fixture.Token() service = v3_token.add_service('application-catalog') service.add_standard_endpoints(public='http://no.where') mreq.register_uri('POST', 'http://no.where/v3/auth/tokens', json=v3_token, headers={'X-Subject-Token': 'tokenid'}, status_code=200) python-muranoclient-1.3.0/muranoclient/tests/unit/__init__.py0000664000175000017500000000000013523272253024500 0ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/tests/unit/test_exc.py0000664000175000017500000000405013523272253024570 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 mock import testtools from muranoclient.common import exceptions as exc HTML_MSG = """ 403 Forbidden

403 Forbidden

Access was denied to this resource.

""" class TestHTTPExceptions(testtools.TestCase): def test_handles_json(self): """exc.from_response should not print JSON.""" mock_resp = mock.Mock() mock_resp.status_code = 413 mock_resp.json.return_value = { "overLimit": { "code": 413, "message": "OverLimit Retry...", "details": "Error Details...", "retryAt": "2015-08-31T21:21:06Z" } } mock_resp.headers = { "content-type": "application/json" } err = exc.from_response(mock_resp) self.assertIsInstance(err, exc.HTTPOverLimit) self.assertEqual("OverLimit Retry...", err.details) def test_handles_html(self): """exc.from_response should not print HTML.""" mock_resp = mock.Mock() mock_resp.status_code = 403 mock_resp.text = HTML_MSG mock_resp.headers = { "content-type": "text/html" } err = exc.from_response(mock_resp) self.assertIsInstance(err, exc.HTTPForbidden) self.assertEqual("403 Forbidden: Access was denied to this resource.", err.details) python-muranoclient-1.3.0/muranoclient/tests/unit/fakes.py0000664000175000017500000000242113523272253024043 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 FakeHTTPResponse(object): version = 1.1 def __init__(self, status_code, reason, headers, content): self.headers = headers self.content = 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) class FakeRaw(object): version = 110 python-muranoclient-1.3.0/muranoclient/tests/__init__.py0000664000175000017500000000000013523272253023521 0ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/client.py0000664000175000017500000000161313523272253022111 0ustar zuulzuul00000000000000# Copyright (c) 2013 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 oslo_utils import importutils def Client(version, *args, **kwargs): module = importutils.import_versioned_module('muranoclient', version, 'client') client_class = getattr(module, 'Client') return client_class(*args, **kwargs) python-muranoclient-1.3.0/muranoclient/shell.py0000664000175000017500000005352413523272253021752 0ustar zuulzuul00000000000000# Copyright (c) 2013 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. """ Command-line interface to the Murano Project. """ from __future__ import print_function import argparse import sys import glanceclient from keystoneclient.auth.identity.generic.cli import DefaultCLI from keystoneclient.auth.identity import v3 as identity from keystoneclient import discover from keystoneclient import exceptions as ks_exc from keystoneclient import session as ksession from oslo_log import handlers from oslo_log import log as logging from oslo_utils import encodeutils from oslo_utils import importutils import six import six.moves.urllib.parse as urlparse import muranoclient from muranoclient.apiclient import exceptions as exc from muranoclient import client as murano_client from muranoclient.common import utils from muranoclient.glance import client as art_client logger = logging.getLogger(__name__) DEFAULT_REPO_URL = "http://apps.openstack.org/api/v1/murano_repo/liberty/" # quick local fix for keystoneclient bug which blocks built-in reauth # functionality in case of expired token. # bug: https://bugs.launchpad.net/python-keystoneclient/+bug/1551392 # fix: https://review.opendev.org/#/c/286236/ class AuthCLI(DefaultCLI): def invalidate(self): retval = super(AuthCLI, self).invalidate() if self._token: self._token = None retval = True return retval class MuranoShell(object): def _append_global_identity_args(self, parser): # Register the CLI arguments that have moved to the session object. ksession.Session.register_cli_options(parser) identity.Password.register_argparse_arguments(parser) def get_base_parser(self, argv): parser = argparse.ArgumentParser( prog='murano', description=__doc__.strip(), epilog='See "murano help COMMAND" ' 'for help on a specific command.', add_help=False, formatter_class=HelpFormatter, ) # Global arguments parser.add_argument('-h', '--help', action='store_true', help=argparse.SUPPRESS, ) parser.add_argument('--version', action='version', version=muranoclient.__version__, help="Show program's version number and exit.") parser.add_argument('-d', '--debug', default=bool(utils.env('MURANOCLIENT_DEBUG')), action='store_true', help='Defaults to env[MURANOCLIENT_DEBUG].') parser.add_argument('-v', '--verbose', default=False, action="store_true", help="Print more verbose output.") parser.add_argument('--api-timeout', help='Number of seconds to wait for an ' 'API response, ' 'defaults to system socket timeout.') parser.add_argument('--os-tenant-id', default=utils.env('OS_TENANT_ID'), help='Defaults to env[OS_TENANT_ID].') parser.add_argument('--os-tenant-name', default=utils.env('OS_TENANT_NAME'), help='Defaults to env[OS_TENANT_NAME].') parser.add_argument('--os-region-name', default=utils.env('OS_REGION_NAME'), help='Defaults to env[OS_REGION_NAME].') parser.add_argument('--os-auth-token', default=utils.env('OS_AUTH_TOKEN'), help='Defaults to env[OS_AUTH_TOKEN].') parser.add_argument('--os-no-client-auth', default=utils.env('OS_NO_CLIENT_AUTH'), action='store_true', help="Do not contact keystone for a token. " "Defaults to env[OS_NO_CLIENT_AUTH].") parser.add_argument('--murano-url', default=utils.env('MURANO_URL'), help='Defaults to env[MURANO_URL].') parser.add_argument('--glance-url', default=utils.env('GLANCE_URL'), help='Defaults to env[GLANCE_URL].') parser.add_argument('--glare-url', default=utils.env('GLARE_URL'), help='Defaults to env[GLARE_URL].') parser.add_argument('--murano-api-version', default=utils.env( 'MURANO_API_VERSION', default='1'), help='Defaults to env[MURANO_API_VERSION] ' 'or 1.') parser.add_argument('--os-service-type', default=utils.env('OS_SERVICE_TYPE'), help='Defaults to env[OS_SERVICE_TYPE].') parser.add_argument('--os-endpoint-type', default=utils.env('OS_ENDPOINT_TYPE'), help='Defaults to env[OS_ENDPOINT_TYPE].') parser.add_argument('--include-password', default=bool(utils.env('MURANO_INCLUDE_PASSWORD')), action='store_true', help='Send os-username and os-password to murano.') parser.add_argument('--murano-repo-url', default=utils.env( 'MURANO_REPO_URL', default=DEFAULT_REPO_URL), help=('Defaults to env[MURANO_REPO_URL] ' 'or {0}'.format(DEFAULT_REPO_URL))) parser.add_argument('--murano-packages-service', choices=['murano', 'glance', 'glare'], default=utils.env('MURANO_PACKAGES_SERVICE', default='murano'), help='Specifies if murano-api ("murano") or ' 'Glance Artifact Repository ("glare") ' 'should be used to store murano packages. ' 'Defaults to env[MURANO_PACKAGES_SERVICE] or ' 'to "murano"') # The following 3 arguments are deprecated and are all added # by keystone session register_cli_opts later. Only add these # arguments if they are present on the command line. if '--cert-file' in argv: parser.add_argument('--cert-file', dest='os_cert', help='DEPRECATED! Use --os-cert.') if '--key-file' in argv: parser.add_argument('--key-file', dest='os_key', help='DEPRECATED! Use --os-key.') if '--ca-file' in argv: parser.add_argument('--ca-file', dest='os_cacert', help='DEPRECATED! Use --os-cacert.') self._append_global_identity_args(parser) return parser def get_subcommand_parser(self, version, argv): parser = self.get_base_parser(argv) self.subcommands = {} subparsers = parser.add_subparsers(metavar='') submodule = importutils.import_versioned_module('muranoclient', version, 'shell') self._find_actions(subparsers, submodule) self._find_actions(subparsers, self) return parser def _find_actions(self, subparsers, actions_module): for attr in (a for a in dir(actions_module) if a.startswith('do_')): # I prefer to be hypen-separated instead of underscores. command = attr[3:].replace('_', '-') callback = getattr(actions_module, attr) desc = callback.__doc__ or '' help = desc.strip().split('\n')[0] arguments = getattr(callback, 'arguments', []) subparser = subparsers.add_parser(command, help=help, description=desc, add_help=False, formatter_class=HelpFormatter) subparser.add_argument('-h', '--help', action='help', help=argparse.SUPPRESS) self.subcommands[command] = subparser for (args, kwargs) in arguments: subparser.add_argument(*args, **kwargs) subparser.set_defaults(func=callback) def _discover_auth_versions(self, session, auth_url): # discover the API versions the server is supporting base on the # given URL v2_auth_url = None v3_auth_url = None try: ks_discover = discover.Discover(session=session, auth_url=auth_url) v2_auth_url = ks_discover.url_for('2.0') v3_auth_url = ks_discover.url_for('3.0') except ks_exc.ClientException as e: # Identity service may not support discover API version. # Lets trying to figure out the API version from the original URL. url_parts = urlparse.urlparse(auth_url) (scheme, netloc, path, params, query, fragment) = url_parts path = path.lower() if path.startswith('/v3'): v3_auth_url = auth_url elif path.startswith('/v2'): v2_auth_url = auth_url else: # not enough information to determine the auth version msg = ('Unable to determine the Keystone version ' 'to authenticate with using the given ' 'auth_url. Identity service may not support API ' 'version discovery. Please provide a versioned ' 'auth_url instead. error=%s') % (e) raise exc.CommandError(msg) return (v2_auth_url, v3_auth_url) def _setup_logging(self, debug): # Output the logs to command-line interface color_handler = handlers.ColorHandler(sys.stdout) logger_root = logging.getLogger(None).logger logger_root.level = logging.DEBUG if debug else logging.WARNING logger_root.addHandler(color_handler) # Set the logger level of special library logging.getLogger('iso8601') \ .logger.setLevel(logging.WARNING) logging.getLogger('urllib3.connectionpool') \ .logger.setLevel(logging.WARNING) def main(self, argv): # Parse args once to find version parser = self.get_base_parser(argv) (options, args) = parser.parse_known_args(argv) self._setup_logging(options.debug) # build available subcommands based on version api_version = options.murano_api_version subcommand_parser = self.get_subcommand_parser(api_version, argv) self.parser = subcommand_parser keystone_session = None keystone_auth = None # Handle top-level --help/-h before attempting to parse # a command off the command line. if (not args and options.help) or not argv: self.do_help(options) return 0 # Parse args again and call whatever callback was selected. args = subcommand_parser.parse_args(argv) # Short-circuit and deal with help command right away. if args.func == self.do_help: self.do_help(args) return 0 elif args.func == self.do_bash_completion: self.do_bash_completion(args) return 0 if not args.os_username and not args.os_auth_token: raise exc.CommandError("You must provide a username via" " either --os-username or env[OS_USERNAME]" " or a token via --os-auth-token or" " env[OS_AUTH_TOKEN]") if args.murano_packages_service == 'glance': args.murano_packages_service = 'glare' if args.os_no_client_auth: if not args.murano_url: raise exc.CommandError( "If you specify --os-no-client-auth" " you must also specify a Murano API URL" " via either --murano-url or env[MURANO_URL]") if (not args.glare_url and args.murano_packages_service == 'glare'): raise exc.CommandError( "If you specify --os-no-client-auth and" " set murano-packages-service to 'glare'" " you must also specify a glare API URL" " via either --glare-url or env[GLARE_API]") if (not any([args.os_tenant_id, args.os_project_id]) and args.murano_packages_service == 'glare'): # TODO(kzaitsev): see if we can use project's name here # NOTE(kzaitsev): glare v0.1 needs project_id to operate # correctly raise exc.CommandError( "If you specify --os-no-client-auth and" " set murano-packages-service to 'glare'" " you must also specify your project's id" " via either --os-project-id or env[OS_PROJECT_ID] or" " --os-tenant-id or env[OS_TENANT_ID]") else: # Tenant name or ID is needed to make keystoneclient retrieve a # service catalog, it's not required if os_no_client_auth is # specified, neither is the auth URL. if not any([args.os_tenant_name, args.os_tenant_id, args.os_project_id, args.os_project_name]): raise exc.CommandError("You must provide a project name or" " project id via --os-project-name," " --os-project-id, env[OS_PROJECT_ID]" " or env[OS_PROJECT_NAME]. You may" " use os-project and os-tenant" " interchangeably.") if not args.os_auth_url: raise exc.CommandError("You must provide an auth url via" " either --os-auth-url or via" " env[OS_AUTH_URL]") endpoint_type = args.os_endpoint_type or 'publicURL' endpoint = args.murano_url glance_endpoint = args.glance_url if args.os_no_client_auth: # Authenticate through murano, don't use session kwargs = { 'username': args.os_username, 'password': args.os_password, 'auth_token': args.os_auth_token, 'auth_url': args.os_auth_url, 'token': args.os_auth_token, 'insecure': args.insecure, 'timeout': args.api_timeout, 'tenant': args.os_project_id or args.os_tenant_id, } glance_kwargs = kwargs.copy() if args.os_region_name: kwargs['region_name'] = args.os_region_name glance_kwargs['region_name'] = args.os_region_name else: # Create a keystone session and keystone auth keystone_session = ksession.Session.load_from_cli_options(args) args.os_project_name = args.os_project_name or args.os_tenant_name args.os_project_id = args.os_project_id or args.os_tenant_id # make args compatible with DefaultCLI/AuthCLI args.os_token = args.os_auth_token args.os_endpoint = '' # avoid password prompt if no password given args.os_password = args.os_password or '' (v2_auth_url, v3_auth_url) = self._discover_auth_versions( keystone_session, args.os_auth_url) if v3_auth_url: if (not args.os_user_domain_id and not args.os_user_domain_name): args.os_user_domain_name = 'default' if (not args.os_project_domain_id and not args.os_project_domain_name): args.os_project_domain_name = 'default' keystone_auth = AuthCLI.load_from_argparse_arguments(args) service_type = args.os_service_type or 'application-catalog' if not endpoint: endpoint = keystone_auth.get_endpoint( keystone_session, service_type=service_type, interface=endpoint_type, region_name=args.os_region_name) kwargs = { 'session': keystone_session, 'auth': keystone_auth, 'service_type': service_type, 'region_name': args.os_region_name, } glance_kwargs = kwargs.copy() # glance doesn't need endpoint_type kwargs['endpoint_type'] = endpoint_type kwargs['tenant'] = keystone_auth.get_project_id(keystone_session) if args.api_timeout: kwargs['timeout'] = args.api_timeout if not glance_endpoint: try: glance_endpoint = keystone_auth.get_endpoint( keystone_session, service_type='image', interface=endpoint_type, region_name=args.os_region_name) except Exception: pass glance_client = None if glance_endpoint: try: # TODO(starodubcevna): switch back to glance APIv2 when it will # be ready for use. glance_client = glanceclient.Client( '1', glance_endpoint, **glance_kwargs) except Exception: pass if glance_client: kwargs['glance_client'] = glance_client else: logger.warning("Could not initialize glance client. " "Image creation will be unavailable.") kwargs['glance_client'] = None if args.murano_packages_service == 'glare': glare_endpoint = args.glare_url if not glare_endpoint: # no glare_endpoint and we requested to store packages in glare # let's check keystone try: glare_endpoint = keystone_auth.get_endpoint( keystone_session, service_type='artifact', interface=endpoint_type, region_name=args.os_region_name) except Exception: raise exc.CommandError( "You set murano-packages-service to {}" " but there is not 'artifact' endpoint in keystone" " Either register one or specify endpoint " " via either --glare-url or env[GLARE_API]".format( args.murano_packages_service)) auth_token = \ args.os_auth_token or keystone_auth.get_token(keystone_session) artifacts_client = art_client.Client(endpoint=glare_endpoint, type_name='murano', type_version=1, token=auth_token, insecure=args.insecure) kwargs['artifacts_client'] = artifacts_client client = murano_client.Client(api_version, endpoint, **kwargs) args.func(client, args) def do_bash_completion(self, args): """Prints all of the commands and options to stdout.""" commands = set() options = set() for sc_str, sc in self.subcommands.items(): commands.add(sc_str) for option in list(sc._optionals._option_string_actions): options.add(option) commands.remove('bash-completion') print(' '.join(commands | options)) @utils.arg('command', metavar='', nargs='?', help='Display help for ') def do_help(self, args): """Display help about this program or one of its subcommands.""" if getattr(args, 'command', None): if args.command in self.subcommands: self.subcommands[args.command].print_help() else: msg = "'%s' is not a valid subcommand" raise exc.CommandError(msg % args.command) else: self.parser.print_help() class HelpFormatter(argparse.HelpFormatter): def start_section(self, heading): # Title-case the headings heading = '%s%s' % (heading[0].upper(), heading[1:]) super(HelpFormatter, self).start_section(heading) def main(args=sys.argv[1:]): try: MuranoShell().main(args) except KeyboardInterrupt: print('... terminating murano client', file=sys.stderr) sys.exit(1) except Exception as e: if '--debug' in args or '-d' in args: raise else: print(encodeutils.safe_encode(six.text_type(e)), file=sys.stderr) sys.exit(1) if __name__ == "__main__": main() python-muranoclient-1.3.0/muranoclient/v1/0000775000175000017500000000000013523272337020611 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/v1/package_creator/0000775000175000017500000000000013523272337023723 5ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/v1/package_creator/mpl_package.py0000664000175000017500000001767613523272253026556 0ustar zuulzuul00000000000000# Copyright (c) 2014 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 import shutil import tempfile import yaml import muranoclient from muranoclient.apiclient import exceptions from muranoclient.common import utils def prepare_package(args): """Prepare for application package Prepare all files and directories for that application package. Generates manifest file and all required parameters for that. :param args: list of command line arguments :returns: absolute path to directory with prepared files """ if args.type and args.type not in ['Application', 'Library']: raise exceptions.CommandError( "--type should be set to 'Application' or 'Library'") manifest = generate_manifest(args) if args.type == 'Application': if not args.ui: raise exceptions.CommandError("'--ui' is required parameter") if not os.path.exists(args.ui) or not os.path.isfile(args.ui): raise exceptions.CommandError( "{0} is not a file or doesn`t exist".format(args.ui)) temp_dir = tempfile.mkdtemp() manifest_file = os.path.join(temp_dir, 'manifest.yaml') classes_directory = os.path.join(temp_dir, 'Classes') resource_directory = os.path.join(temp_dir, 'Resources') with open(manifest_file, 'w') as f: f.write(yaml.dump(manifest, default_flow_style=False)) logo_file = os.path.join(temp_dir, 'logo.png') if not args.logo or(args.logo and not os.path.isfile(args.logo)): shutil.copyfile(muranoclient.get_resource('mpl_logo.png'), logo_file) else: shutil.copyfile(args.logo, logo_file) shutil.copytree(args.classes_dir, classes_directory) if args.resources_dir: if not os.path.isdir(args.resources_dir): raise exceptions.CommandError( "'--resources-dir' parameter should be a directory") shutil.copytree(args.resources_dir, resource_directory) if args.ui: ui_directory = os.path.join(temp_dir, 'UI') os.mkdir(ui_directory) shutil.copyfile(args.ui, os.path.join(ui_directory, 'ui.yaml')) return temp_dir def generate_manifest(args): """Generates application manifest file. If some parameters are missed - they we be generated automatically. :param args: :returns: dictionary, contains manifest file data """ if not os.path.isdir(args.classes_dir): raise exceptions.CommandError( "'--classes-dir' parameter should be a directory") args = update_args(args) if not args.type: raise exceptions.CommandError( "Too few arguments: --type and --full-name is required") if not args.author: args.author = args.os_username if not args.description: args.description = "Description for the application is not provided" if not args.full_name: raise exceptions.CommandError( "Please, provide --full-name parameter") manifest = { 'Format': 'MuranoPL/1.0', 'Type': args.type, 'FullName': args.full_name, 'Name': args.name, 'Description': args.description, 'Author': args.author, 'Classes': args.classes } if args.tags: manifest['Tags'] = args.tags return manifest def update_args(args): """Add and update arguments if possible. Some parameters are not required and would be guessed from muranoPL classes: thus, if class extends system application class fully qualified and require names could be calculated. Also, in that case type of a package could be set to 'Application'. """ classes = {} extends_from_application = False for root, dirs, files in os.walk(args.classes_dir): for class_file in files: class_file_path = os.path.join(root, class_file) try: with open(class_file_path) as f: content = yaml.load(f, utils.YaqlYamlLoader) if not content.get('Name'): raise exceptions.CommandError( "Error in class definition: 'Name' " "section is required") class_name = get_fqn_for_name(content.get('Namespaces'), content['Name']) if root == args.classes_dir: relative_path = class_file else: relative_path = os.path.join( root.replace(args.classes_dir, "")[1:], class_file) classes[class_name] = relative_path extends_from_application = check_derived_from_application( content, extends_from_application) if extends_from_application: if not args.type: args.type = 'Application' if not args.name: args.name = class_name.split('.')[-1] if not args.full_name: args.full_name = class_name except yaml.YAMLError: raise exceptions.CommandError( "MuranoPL class {0} should be" " a valid yaml file".format(class_file_path)) except IOError: raise exceptions.CommandError( "Could not open file {0}".format(class_file_path)) if not classes: raise exceptions.CommandError("Application should have " "at least one class") args.classes = classes return args def get_fqn_for_name(namespaces, name): """Analyze name for namespace reference. If namespaces are used - return a full name :param namespaces: content of 'Namespaces' section of muranoPL class :param name: name that should be checked :returns: generated name according to namespaces """ values = name.split(':') if len(values) == 1: if '=' in namespaces: return namespaces['='] + '.' + values[0] return values[0] if len(values) > 2: raise exceptions.CommandError( "Error in class definition: Wrong usage of ':' is " "reserved for namespace referencing and could " "be used only once " "for each name") if not namespaces: raise exceptions.CommandError( "Error in {0} class definition: " "'Namespaces' section is missed") result = namespaces.get(values[0]) if not result: raise exceptions.CommandError( "Error in class definition: namespaces " "reference is not correct at the 'Extends'" " section") return result + '.' + values[1] def check_derived_from_application(content, extends_from_application): """Look up for system 'io.murano.Application' class in extends section""" if content.get('Extends'): extends = content['Extends'] if not isinstance(extends, list): extends = [extends] for name in extends: parent_class_name = get_fqn_for_name( content.get('Namespaces'), name) if parent_class_name == 'io.murano.Application': if not extends_from_application: return True else: raise exceptions.CommandError( "Murano package should have only one class" " extends 'io.murano.Application' class") return False python-muranoclient-1.3.0/muranoclient/v1/package_creator/hot_package.py0000664000175000017500000000676613523272253026556 0ustar zuulzuul00000000000000# Copyright (c) 2014 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 import shutil import tempfile import yaml import muranoclient from muranoclient.apiclient import exceptions def generate_manifest(args): """Generates application manifest file. If some parameters are missed - they we be generated automatically. :param args: :returns: dictionary, contains manifest file data """ if not os.path.isfile(args.template): raise exceptions.CommandError( "Template '{0}' doesn`t exist".format(args.template)) filename = os.path.basename(args.template) if not args.name: args.name = os.path.splitext(filename)[0] if not args.full_name: prefix = 'io.murano.apps.generated' normalized_name = args.name.replace('_', ' ').replace('-', ' ') normalized_name = normalized_name.title().replace(' ', '') args.full_name = '{0}.{1}'.format(prefix, normalized_name) try: with open(args.template, 'rb') as heat_file: yaml_content = yaml.safe_load(heat_file) if not args.description: args.description = yaml_content.get( 'description', 'Heat-defined application for a template "{0}"'.format( filename)) except yaml.YAMLError: raise exceptions.CommandError( "Heat template, represented by --'template' parameter" " should be a valid yaml file") if not args.author: args.author = args.os_username if not args.tags: args.tags = ['Heat-generated'] manifest = { 'Format': 'Heat.HOT/1.0', 'Type': 'Application', 'FullName': args.full_name, 'Name': args.name, 'Description': args.description, 'Author': args.author, 'Tags': args.tags } return manifest def prepare_package(args): """Compose required files for murano application package. :param args: list of command line arguments :returns: absolute path to directory with prepared files """ manifest = generate_manifest(args) temp_dir = tempfile.mkdtemp() manifest_file = os.path.join(temp_dir, 'manifest.yaml') template_file = os.path.join(temp_dir, 'template.yaml') if args.resources_dir: if not os.path.isdir(args.resources_dir): raise exceptions.CommandError( "'--resources-dir' parameter should be a directory") resource_directory = os.path.join(temp_dir, 'Resources') shutil.copytree(args.resources_dir, resource_directory) logo_file = os.path.join(temp_dir, 'logo.png') if not args.logo: shutil.copyfile(muranoclient.get_resource('heat_logo.png'), logo_file) else: if os.path.isfile(args.logo): shutil.copyfile(args.logo, logo_file) with open(manifest_file, 'w') as f: f.write(yaml.dump(manifest, default_flow_style=False)) shutil.copyfile(args.template, template_file) return temp_dir python-muranoclient-1.3.0/muranoclient/v1/package_creator/__init__.py0000664000175000017500000000000013523272253026017 0ustar zuulzuul00000000000000python-muranoclient-1.3.0/muranoclient/v1/environments.py0000664000175000017500000000660313523272253023714 0ustar zuulzuul00000000000000# Copyright (c) 2013 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 six.moves import urllib from muranoclient.common import base class Environment(base.Resource): def __repr__(self): return "" % self._info def data(self, **kwargs): return self.manager.data(self, **kwargs) class Status(base.Resource): def __repr__(self): return '' % self._info def data(self, **kwargs): return self.manager.data(self, **kwargs) class EnvironmentManager(base.ManagerWithFind): resource_class = Environment def list(self, all_tenants=False, tenant_id=None): params = {'all_tenants': all_tenants} if tenant_id: params['tenant'] = tenant_id path = '/v1/environments?{query}'.format( query=urllib.parse.urlencode(params)) return self._list(path, 'environments') def create(self, data): return self._create('/v1/environments', data) def update(self, environment_id, name): return self._update('/v1/environments/{id}'.format(id=environment_id), data={'name': name}) def delete(self, environment_id, abandon=False): path = '/v1/environments/{id}?{query}'.format( id=environment_id, query=urllib.parse.urlencode({'abandon': abandon})) return self._delete(path) def get(self, environment_id, session_id=None): if session_id: headers = {'X-Configuration-Session': session_id} else: headers = {} return self._get("/v1/environments/{id}".format(id=environment_id), headers=headers) def last_status(self, environment_id, session_id): headers = {'X-Configuration-Session': session_id} path = '/v1/environments/{id}/lastStatus' path = path.format(id=environment_id) status_dict = self._get(path, return_raw=True, response_key='lastStatuses', headers=headers) result = {} for k, v in status_dict.items(): if v: result[k] = Status(self, v, loaded=True) return result def get_model(self, environment_id, path, session_id=None): headers = {'X-Configuration-Session': session_id} url = '/v1/environments/{id}/model/{path}' url = url.format(id=environment_id, path=path) return self._get(url, return_raw=True, headers=headers) def update_model(self, environment_id, data, session_id): headers = {'X-Configuration-Session': session_id} url = '/v1/environments/{id}/model/' url = url.format(id=environment_id) return self._update(url, data, return_raw=True, headers=headers, method='PATCH', content_type='application/env-model-json-patch') python-muranoclient-1.3.0/muranoclient/v1/actions.py0000664000175000017500000000263713523272253022630 0ustar zuulzuul00000000000000# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # Not a true manager yet; should be changed to be one if CRUD # functionality becomes available for actions. class ActionManager(object): def __init__(self, api): self.api = api def call(self, environment_id, action_id, arguments=None): if arguments is None: arguments = {} url = '/v1/environments/{environment_id}/actions/{action_id}'.format( environment_id=environment_id, action_id=action_id) resp, body = self.api.json_request(url, 'POST', body=arguments) return body['task_id'] def get_result(self, environment_id, task_id): url = '/v1/environments/{environment_id}/actions/{task_id}'.format( environment_id=environment_id, task_id=task_id) resp, body = self.api.json_request(url, 'GET') return body or None python-muranoclient-1.3.0/muranoclient/v1/templates.py0000664000175000017500000000704713523272253023166 0ustar zuulzuul00000000000000# Copyright (c) 2013 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 muranoclient.common import base class Template(base.Resource): """Involves the template resource.""" def __repr__(self): return "