python-mistralclient-3.3.0/0000775000175100017510000000000013241061116015725 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/setup.py0000666000175100017510000000200613241060623017441 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-mistralclient-3.3.0/requirements.txt0000666000175100017510000000076313241060623021223 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. cliff!=2.9.0,>=2.8.0 # Apache-2.0 osc-lib>=1.8.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 pbr!=2.1.0,>=2.0.0 # Apache-2.0 keystoneauth1>=3.3.0 # Apache-2.0 PyYAML>=3.10 # MIT requests>=2.14.2 # Apache-2.0 six>=1.10.0 # MIT stevedore>=1.20.0 # Apache-2.0 python-mistralclient-3.3.0/functionaltests/0000775000175100017510000000000013241061116021152 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/functionaltests/run_tests.sh0000777000175100017510000000373113241060623023547 0ustar zuulzuul00000000000000#!/bin/bash # # 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. # How many seconds to wait for the API to be responding before giving up API_RESPONDING_TIMEOUT=20 if ! timeout ${API_RESPONDING_TIMEOUT} sh -c "until curl --output /dev/null --silent --head --fail http://localhost:8989; do sleep 1; done"; then echo "Mistral API failed to respond within ${API_RESPONDING_TIMEOUT} seconds" exit 1 fi echo "Successfully contacted Mistral API" export BASE=/opt/stack export MISTRALCLIENT_DIR="$BASE/new/python-mistralclient" # Get demo credentials. cd ${BASE}/new/devstack source openrc alt_demo alt_demo export OS_ALT_USERNAME=${OS_USERNAME} export OS_ALT_TENANT_NAME=${OS_TENANT_NAME} export OS_ALT_USER_DOMAIN_NAME=${OS_USER_DOMAIN_NAME} export OS_ALT_PROJECT_DOMAIN_NAME=${OS_PROJECT_DOMAIN_NAME} export OS_ALT_PASSWORD=${OS_PASSWORD} # Get admin credentials. source openrc admin admin # Store these credentials into the config file. CREDS_FILE=${MISTRALCLIENT_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 user_domain = $OS_USER_DOMAIN_NAME project_domain = $OS_PROJECT_DOMAIN_NAME [demo] user = $OS_ALT_USERNAME tenant = $OS_ALT_TENANT_NAME pass = $OS_ALT_PASSWORD user_domain = $OS_ALT_USER_DOMAIN_NAME project_domain = $OS_ALT_PROJECT_DOMAIN_NAME EOF cd $MISTRALCLIENT_DIR # Run tests tox -efunctional -- nosetests -sv mistralclient/tests/functional python-mistralclient-3.3.0/functionaltests/resources/0000775000175100017510000000000013241061116023164 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/functionaltests/resources/v2/0000775000175100017510000000000013241061116023513 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/functionaltests/resources/v2/action_v2_tags.yaml0000666000175100017510000000032713241060623027307 0ustar zuulzuul00000000000000--- version: "2.0" greeting: description: "This action says 'Hello'" tags: [tag, tag1] base: std.echo base-input: output: 'Hello, <% $.name %>' input: - name output: string: <% $.output %> python-mistralclient-3.3.0/functionaltests/resources/v2/wf_v2.yaml0000666000175100017510000000112113241060623025421 0ustar zuulzuul00000000000000--- version: '2.0' wf: type: direct tasks: hello: action: std.echo output="Hello" wait-before: 1 publish: result: <% task().result %> on-success: bye bye: action: std.echo output="Bye" publish: result: <% $.result + ', ' + task().result %> wf1: type: reverse tags: [tag] input: - farewell tasks: addressee: action: std.echo output="John" publish: name: <% task(addressee).result %> goodbye: action: std.echo output="<% $.farewell %>, <% $.name %>" requires: [addressee] python-mistralclient-3.3.0/functionaltests/resources/v2/action_v2.yaml0000666000175100017510000000044413241060623026271 0ustar zuulzuul00000000000000--- version: "2.0" greeting: description: "This action says 'Hello'" base: std.echo base-input: output: 'Hello, <% $.name %>' input: - name output: string: <% $.output %> farewell: base: std.echo base-input: output: 'Bye!' output: info: <% $.output %> python-mistralclient-3.3.0/functionaltests/resources/v2/for_namespaces/0000775000175100017510000000000013241061116026500 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/functionaltests/resources/v2/for_namespaces/middle_wf.yaml0000666000175100017510000000014213241060623031317 0ustar zuulzuul00000000000000--- version: '2.0' middle_wf: type: direct tasks: hello: workflow: lowest_level_wf python-mistralclient-3.3.0/functionaltests/resources/v2/for_namespaces/top_level_wf.yaml0000666000175100017510000000013713241060623032056 0ustar zuulzuul00000000000000--- version: '2.0' top_level_wf: type: direct tasks: hello: workflow: middle_wf python-mistralclient-3.3.0/functionaltests/resources/v2/for_namespaces/lowest_level_wf.yaml0000666000175100017510000000013713241060623032571 0ustar zuulzuul00000000000000--- version: '2.0' lowest_level_wf: type: direct tasks: hello: action: std.noop python-mistralclient-3.3.0/functionaltests/resources/v2/async.yaml0000666000175100017510000000014313241060623025516 0ustar zuulzuul00000000000000--- version: '2.0' async_wf: type: direct tasks: async_task: action: std.async_noop python-mistralclient-3.3.0/functionaltests/resources/v2/wb_with_tags_v2.yaml0000666000175100017510000000031213241060623027467 0ustar zuulzuul00000000000000--- version: '2.0' name: wb tags: [tag] workflows: wf1: type: direct tasks: hello: action: std.echo output="Hello" publish: result: <% task(hello).result %> python-mistralclient-3.3.0/functionaltests/resources/v2/wf_single_v2.yaml0000666000175100017510000000024113241060623026764 0ustar zuulzuul00000000000000--- version: '2.0' wf_single: type: direct tasks: hello: action: std.echo output="Hello" publish: result: <% task(hello).result %> python-mistralclient-3.3.0/functionaltests/resources/v2/wb_v2.yaml0000666000175100017510000000027613241060623025427 0ustar zuulzuul00000000000000--- version: '2.0' name: wb workflows: wf1: type: direct tasks: hello: action: std.echo output="Hello" publish: result: <% task(hello).result %> python-mistralclient-3.3.0/functionaltests/resources/v2/wf_delay_v2.yaml0000666000175100017510000000051013241060623026600 0ustar zuulzuul00000000000000--- version: '2.0' wf: type: direct tags: [tag] tasks: hello: action: std.echo output="Hello" publish: result: <% task(hello).result %> wait-after: 1 on-success: - task2 task2: action: std.echo output="Task 2" publish: task2: <% task(task2).result %> python-mistralclient-3.3.0/functionaltests/resources/v2/wf_wrapping_wf_v2.yaml0000666000175100017510000000012713241060623030031 0ustar zuulzuul00000000000000--- version: '2.0' wrapping_wf: type: direct tasks: hello: workflow: wf python-mistralclient-3.3.0/functionaltests/post_test_hook.sh0000777000175100017510000000146613241060623024570 0ustar zuulzuul00000000000000#!/bin/bash # # 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. RETVAL=0 sudo chmod -R a+rw /opt/stack/new/ cd /opt/stack/new/python-mistralclient echo "Running CLI python-mistralclient tests" ./functionaltests/run_tests.sh RETVAL=$? exit $RETVAL python-mistralclient-3.3.0/python_mistralclient.egg-info/0000775000175100017510000000000013241061116023672 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/python_mistralclient.egg-info/not-zip-safe0000664000175100017510000000000113241061071026120 0ustar zuulzuul00000000000000 python-mistralclient-3.3.0/python_mistralclient.egg-info/dependency_links.txt0000664000175100017510000000000113241061114027736 0ustar zuulzuul00000000000000 python-mistralclient-3.3.0/python_mistralclient.egg-info/top_level.txt0000664000175100017510000000001613241061114026417 0ustar zuulzuul00000000000000mistralclient python-mistralclient-3.3.0/python_mistralclient.egg-info/SOURCES.txt0000664000175100017510000001302613241061116025560 0ustar zuulzuul00000000000000AUTHORS CONTRIBUTING.rst ChangeLog LICENSE README.rst functional_creds.conf.sample requirements.txt run_functional_tests.sh setup.cfg setup.py test-requirements.txt tox.ini doc/source/class_reference.rst doc/source/conf.py doc/source/index.rst doc/source/cli/cli_usage_source_execution.rst doc/source/cli/cli_usage_with_keycloak.rst doc/source/cli/cli_usage_with_openstack.rst doc/source/cli/cli_usage_without_auth.rst functionaltests/post_test_hook.sh functionaltests/run_tests.sh functionaltests/resources/v2/action_v2.yaml functionaltests/resources/v2/action_v2_tags.yaml functionaltests/resources/v2/async.yaml functionaltests/resources/v2/wb_v2.yaml functionaltests/resources/v2/wb_with_tags_v2.yaml functionaltests/resources/v2/wf_delay_v2.yaml functionaltests/resources/v2/wf_single_v2.yaml functionaltests/resources/v2/wf_v2.yaml functionaltests/resources/v2/wf_wrapping_wf_v2.yaml functionaltests/resources/v2/for_namespaces/lowest_level_wf.yaml functionaltests/resources/v2/for_namespaces/middle_wf.yaml functionaltests/resources/v2/for_namespaces/top_level_wf.yaml mistralclient/__init__.py mistralclient/exceptions.py mistralclient/i18n.py mistralclient/shell.py mistralclient/utils.py mistralclient/api/__init__.py mistralclient/api/base.py mistralclient/api/client.py mistralclient/api/httpclient.py mistralclient/api/v2/__init__.py mistralclient/api/v2/action_executions.py mistralclient/api/v2/actions.py mistralclient/api/v2/client.py mistralclient/api/v2/cron_triggers.py mistralclient/api/v2/environments.py mistralclient/api/v2/event_triggers.py mistralclient/api/v2/executions.py mistralclient/api/v2/members.py mistralclient/api/v2/services.py mistralclient/api/v2/tasks.py mistralclient/api/v2/workbooks.py mistralclient/api/v2/workflows.py mistralclient/auth/__init__.py mistralclient/auth/auth_types.py mistralclient/auth/keycloak.py mistralclient/auth/keystone.py mistralclient/commands/__init__.py mistralclient/commands/v2/__init__.py mistralclient/commands/v2/action_executions.py mistralclient/commands/v2/actions.py mistralclient/commands/v2/base.py mistralclient/commands/v2/cron_triggers.py mistralclient/commands/v2/environments.py mistralclient/commands/v2/event_triggers.py mistralclient/commands/v2/executions.py mistralclient/commands/v2/members.py mistralclient/commands/v2/services.py mistralclient/commands/v2/tasks.py mistralclient/commands/v2/workbooks.py mistralclient/commands/v2/workflows.py mistralclient/osc/__init__.py mistralclient/osc/plugin.py mistralclient/tests/__init__.py mistralclient/tests/functional/__init__.py mistralclient/tests/functional/cli/__init__.py mistralclient/tests/functional/cli/base.py mistralclient/tests/functional/cli/v2/__init__.py mistralclient/tests/functional/cli/v2/base_v2.py mistralclient/tests/functional/cli/v2/cli_multi_tenancy_tests.py mistralclient/tests/functional/cli/v2/cli_tests_v2.py mistralclient/tests/unit/__init__.py mistralclient/tests/unit/base.py mistralclient/tests/unit/base_shell_test.py mistralclient/tests/unit/test_client.py mistralclient/tests/unit/test_httpclient.py mistralclient/tests/unit/test_shell.py mistralclient/tests/unit/test_utils.py mistralclient/tests/unit/resources/action_v2.yaml mistralclient/tests/unit/resources/ctx.json mistralclient/tests/unit/resources/env_v2.json mistralclient/tests/unit/resources/env_v2.yaml mistralclient/tests/unit/resources/wb_v2.yaml mistralclient/tests/unit/resources/wf_v2.yaml mistralclient/tests/unit/v2/__init__.py mistralclient/tests/unit/v2/base.py mistralclient/tests/unit/v2/test_action_executions.py mistralclient/tests/unit/v2/test_actions.py mistralclient/tests/unit/v2/test_cli_action_execs.py mistralclient/tests/unit/v2/test_cli_actions.py mistralclient/tests/unit/v2/test_cli_bash_completion.py mistralclient/tests/unit/v2/test_cli_cron_triggers.py mistralclient/tests/unit/v2/test_cli_environments.py mistralclient/tests/unit/v2/test_cli_event_triggers.py mistralclient/tests/unit/v2/test_cli_executions.py mistralclient/tests/unit/v2/test_cli_members.py mistralclient/tests/unit/v2/test_cli_services.py mistralclient/tests/unit/v2/test_cli_tasks.py mistralclient/tests/unit/v2/test_cli_workbooks.py mistralclient/tests/unit/v2/test_cli_workflows.py mistralclient/tests/unit/v2/test_environments.py mistralclient/tests/unit/v2/test_executions.py mistralclient/tests/unit/v2/test_keystone.py mistralclient/tests/unit/v2/test_members.py mistralclient/tests/unit/v2/test_services.py mistralclient/tests/unit/v2/test_tasks.py mistralclient/tests/unit/v2/test_workbooks.py mistralclient/tests/unit/v2/test_workflows.py python_mistralclient.egg-info/PKG-INFO python_mistralclient.egg-info/SOURCES.txt python_mistralclient.egg-info/dependency_links.txt python_mistralclient.egg-info/entry_points.txt python_mistralclient.egg-info/not-zip-safe python_mistralclient.egg-info/pbr.json python_mistralclient.egg-info/requires.txt python_mistralclient.egg-info/top_level.txt releasenotes/notes/.placeholder releasenotes/notes/add-namespaces-to-cli-9d20e7fcb07c223f.yaml releasenotes/notes/fix-region-name-2031ff4b83b6308e.yaml releasenotes/notes/remove-keystoneclient-dependency-f2981f29e6673f71.yaml releasenotes/notes/set-default-limit-value-7e293d843d6d85ac.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/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder tools/mistral.bash_completion tools/run_pep8 tools/update_env_deps tools/config/generate_sample.shpython-mistralclient-3.3.0/python_mistralclient.egg-info/entry_points.txt0000664000175100017510000001021113241061114027161 0ustar zuulzuul00000000000000[console_scripts] mistral = mistralclient.shell:main [mistralclient.auth] keycloak-oidc = mistralclient.auth.keycloak:KeycloakAuthHandler keystone = mistralclient.auth.keystone:KeystoneAuthHandler [openstack.cli.extension] workflow_engine = mistralclient.osc.plugin [openstack.workflow_engine.v2] action_definition_create = mistralclient.commands.v2.actions:Create action_definition_definition_show = mistralclient.commands.v2.actions:GetDefinition action_definition_delete = mistralclient.commands.v2.actions:Delete action_definition_list = mistralclient.commands.v2.actions:List action_definition_show = mistralclient.commands.v2.actions:Get action_definition_update = mistralclient.commands.v2.actions:Update action_execution_delete = mistralclient.commands.v2.action_executions:Delete action_execution_input_show = mistralclient.commands.v2.action_executions:GetInput action_execution_list = mistralclient.commands.v2.action_executions:List action_execution_output_show = mistralclient.commands.v2.action_executions:GetOutput action_execution_run = mistralclient.commands.v2.action_executions:Create action_execution_show = mistralclient.commands.v2.action_executions:Get action_execution_update = mistralclient.commands.v2.action_executions:Update cron_trigger_create = mistralclient.commands.v2.cron_triggers:Create cron_trigger_delete = mistralclient.commands.v2.cron_triggers:Delete cron_trigger_list = mistralclient.commands.v2.cron_triggers:List cron_trigger_show = mistralclient.commands.v2.cron_triggers:Get event_trigger_create = mistralclient.commands.v2.event_triggers:Create event_trigger_delete = mistralclient.commands.v2.event_triggers:Delete event_trigger_list = mistralclient.commands.v2.event_triggers:List event_trigger_show = mistralclient.commands.v2.event_triggers:Get resource_member_create = mistralclient.commands.v2.members:Create resource_member_delete = mistralclient.commands.v2.members:Delete resource_member_list = mistralclient.commands.v2.members:List resource_member_show = mistralclient.commands.v2.members:Get resource_member_update = mistralclient.commands.v2.members:Update task_execution_list = mistralclient.commands.v2.tasks:List task_execution_published_show = mistralclient.commands.v2.tasks:GetPublished task_execution_rerun = mistralclient.commands.v2.tasks:Rerun task_execution_result_show = mistralclient.commands.v2.tasks:GetResult task_execution_show = mistralclient.commands.v2.tasks:Get workbook_create = mistralclient.commands.v2.workbooks:Create workbook_definition_show = mistralclient.commands.v2.workbooks:GetDefinition workbook_delete = mistralclient.commands.v2.workbooks:Delete workbook_list = mistralclient.commands.v2.workbooks:List workbook_show = mistralclient.commands.v2.workbooks:Get workbook_update = mistralclient.commands.v2.workbooks:Update workbook_validate = mistralclient.commands.v2.workbooks:Validate workflow_create = mistralclient.commands.v2.workflows:Create workflow_definition_show = mistralclient.commands.v2.workflows:GetDefinition workflow_delete = mistralclient.commands.v2.workflows:Delete workflow_engine_service_list = mistralclient.commands.v2.services:List workflow_env_create = mistralclient.commands.v2.environments:Create workflow_env_delete = mistralclient.commands.v2.environments:Delete workflow_env_list = mistralclient.commands.v2.environments:List workflow_env_show = mistralclient.commands.v2.environments:Get workflow_env_update = mistralclient.commands.v2.environments:Update workflow_execution_create = mistralclient.commands.v2.executions:Create workflow_execution_delete = mistralclient.commands.v2.executions:Delete workflow_execution_input_show = mistralclient.commands.v2.executions:GetInput workflow_execution_list = mistralclient.commands.v2.executions:List workflow_execution_output_show = mistralclient.commands.v2.executions:GetOutput workflow_execution_show = mistralclient.commands.v2.executions:Get workflow_execution_update = mistralclient.commands.v2.executions:Update workflow_list = mistralclient.commands.v2.workflows:List workflow_show = mistralclient.commands.v2.workflows:Get workflow_update = mistralclient.commands.v2.workflows:Update workflow_validate = mistralclient.commands.v2.workflows:Validate python-mistralclient-3.3.0/python_mistralclient.egg-info/PKG-INFO0000664000175100017510000001141213241061114024764 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: python-mistralclient Version: 3.3.0 Summary: Mistral Client Library Home-page: https://docs.openstack.org/python-mistralclient/latest/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: Apache Software License Description-Content-Type: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/badges/python-mistralclient.svg :target: https://governance.openstack.org/reference/tags/index.html Mistral ======= .. image:: https://img.shields.io/pypi/v/python-mistralclient.svg :target: https://pypi.python.org/pypi/python-mistralclient/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/python-mistralclient.svg :target: https://pypi.python.org/pypi/python-mistralclient/ :alt: Downloads Mistral is a workflow service. Most business processes consist of multiple distinct interconnected steps that need to be executed in a particular order in a distributed environment. A user can describe such a process as a set of tasks and their transitions. After that, it is possible to upload such a description to Mistral, which will take care of state management, correct execution order, parallelism, synchronization and high availability. Mistral also provides flexible task scheduling so that it can run a process according to a specified schedule (for example, every Sunday at 4.00pm) instead of running it immediately. In Mistral terminology such a set of tasks and relations between them is called a workflow. Mistral client ============== Python client for Mistral REST API. Includes python library for Mistral API and Command Line Interface (CLI) library. Installation ------------ First of all, clone the repo and go to the repo directory:: $ git clone git://git.openstack.org/openstack/python-mistralclient.git $ cd python-mistralclient Then just run:: $ pip install -e . or:: $ pip install -r requirements.txt $ python setup.py install Running Mistral client ---------------------- If Mistral authentication is enabled, provide the information about OpenStack auth to environment variables. Type:: $ export OS_AUTH_URL=http://:5000/v2.0 $ export OS_USERNAME=admin $ export OS_TENANT_NAME=tenant $ export OS_PASSWORD=secret $ export OS_MISTRAL_URL=http://:8989/v2 (optional, by default URL=http://localhost:8989/v2) and in the case that you are authenticating against keystone over https: $ export OS_CACERT= .. note:: In client, we can use both Keystone auth versions - v2.0 and v3. But server supports only v3.* To make sure Mistral client works, type:: $ mistral workbook-list You can see the list of available commands typing:: $ mistral --help Useful Links ============ * `PyPi`_ - package installation * `Launchpad project`_ - release management * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ * `Specs`_ * `How to Contribute`_ .. _PyPi: https://pypi.python.org/pypi/python-mistralclient .. _Launchpad project: https://launchpad.net/python-mistralclient .. _Blueprints: https://blueprints.launchpad.net/python-mistralclient .. _Bugs: https://bugs.launchpad.net/python-mistralclient .. _Source: https://git.openstack.org/cgit/openstack/python-mistralclient .. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html .. _Specs: https://specs.openstack.org/openstack/mistral-specs/ Platform: UNKNOWN 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.5 Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux python-mistralclient-3.3.0/python_mistralclient.egg-info/requires.txt0000664000175100017510000000025513241061114026272 0ustar zuulzuul00000000000000cliff!=2.9.0,>=2.8.0 osc-lib>=1.8.0 oslo.utils>=3.33.0 oslo.i18n>=3.15.3 pbr!=2.1.0,>=2.0.0 keystoneauth1>=3.3.0 PyYAML>=3.10 requests>=2.14.2 six>=1.10.0 stevedore>=1.20.0 python-mistralclient-3.3.0/python_mistralclient.egg-info/pbr.json0000664000175100017510000000005613241061114025347 0ustar zuulzuul00000000000000{"git_version": "1008e4e", "is_release": true}python-mistralclient-3.3.0/functional_creds.conf.sample0000666000175100017510000000026513241060623023405 0ustar zuulzuul00000000000000# Credentials for functional testing [auth] uri = http://10.42.0.50:5000/v2.0 [admin] user = admin tenant = admin pass = secrete [demo] user = demo tenant = demo pass = demo_secretpython-mistralclient-3.3.0/setup.cfg0000666000175100017510000001276213241061116017560 0ustar zuulzuul00000000000000[metadata] name = python-mistralclient summary = Mistral Client Library description-file = README.rst license = Apache Software License classifiers = Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.5 Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = https://docs.openstack.org/python-mistralclient/latest/ [files] packages = mistralclient [build_sphinx] builders = html,man source-dir = doc/source build-dir = doc/build all_files = 1 warning-is-error = 1 [upload_sphinx] upload-dir = doc/build/html [entry_points] console_scripts = mistral = mistralclient.shell:main openstack.cli.extension = workflow_engine = mistralclient.osc.plugin openstack.workflow_engine.v2 = workbook_list = mistralclient.commands.v2.workbooks:List workbook_show = mistralclient.commands.v2.workbooks:Get workbook_create = mistralclient.commands.v2.workbooks:Create workbook_delete = mistralclient.commands.v2.workbooks:Delete workbook_update = mistralclient.commands.v2.workbooks:Update workbook_definition_show = mistralclient.commands.v2.workbooks:GetDefinition workbook_validate = mistralclient.commands.v2.workbooks:Validate workflow_list = mistralclient.commands.v2.workflows:List workflow_show = mistralclient.commands.v2.workflows:Get workflow_create = mistralclient.commands.v2.workflows:Create workflow_delete = mistralclient.commands.v2.workflows:Delete workflow_update = mistralclient.commands.v2.workflows:Update workflow_definition_show = mistralclient.commands.v2.workflows:GetDefinition workflow_validate = mistralclient.commands.v2.workflows:Validate workflow_env_create = mistralclient.commands.v2.environments:Create workflow_env_delete = mistralclient.commands.v2.environments:Delete workflow_env_update = mistralclient.commands.v2.environments:Update workflow_env_list = mistralclient.commands.v2.environments:List workflow_env_show = mistralclient.commands.v2.environments:Get action_execution_run = mistralclient.commands.v2.action_executions:Create action_execution_list = mistralclient.commands.v2.action_executions:List action_execution_show = mistralclient.commands.v2.action_executions:Get action_execution_input_show = mistralclient.commands.v2.action_executions:GetInput action_execution_output_show = mistralclient.commands.v2.action_executions:GetOutput action_execution_update = mistralclient.commands.v2.action_executions:Update action_execution_delete = mistralclient.commands.v2.action_executions:Delete workflow_execution_create = mistralclient.commands.v2.executions:Create workflow_execution_delete = mistralclient.commands.v2.executions:Delete workflow_execution_update = mistralclient.commands.v2.executions:Update workflow_execution_list = mistralclient.commands.v2.executions:List workflow_execution_show = mistralclient.commands.v2.executions:Get workflow_execution_input_show = mistralclient.commands.v2.executions:GetInput workflow_execution_output_show = mistralclient.commands.v2.executions:GetOutput task_execution_list = mistralclient.commands.v2.tasks:List task_execution_show = mistralclient.commands.v2.tasks:Get task_execution_published_show = mistralclient.commands.v2.tasks:GetPublished task_execution_result_show = mistralclient.commands.v2.tasks:GetResult task_execution_rerun = mistralclient.commands.v2.tasks:Rerun action_definition_list = mistralclient.commands.v2.actions:List action_definition_show = mistralclient.commands.v2.actions:Get action_definition_create = mistralclient.commands.v2.actions:Create action_definition_delete = mistralclient.commands.v2.actions:Delete action_definition_update = mistralclient.commands.v2.actions:Update action_definition_definition_show = mistralclient.commands.v2.actions:GetDefinition cron_trigger_list = mistralclient.commands.v2.cron_triggers:List cron_trigger_show = mistralclient.commands.v2.cron_triggers:Get cron_trigger_create = mistralclient.commands.v2.cron_triggers:Create cron_trigger_delete = mistralclient.commands.v2.cron_triggers:Delete event_trigger_list = mistralclient.commands.v2.event_triggers:List event_trigger_show = mistralclient.commands.v2.event_triggers:Get event_trigger_create = mistralclient.commands.v2.event_triggers:Create event_trigger_delete = mistralclient.commands.v2.event_triggers:Delete workflow_engine_service_list = mistralclient.commands.v2.services:List resource_member_list = mistralclient.commands.v2.members:List resource_member_show = mistralclient.commands.v2.members:Get resource_member_create = mistralclient.commands.v2.members:Create resource_member_delete = mistralclient.commands.v2.members:Delete resource_member_update = mistralclient.commands.v2.members:Update mistralclient.auth = # Standard Keystone authentication. keystone = mistralclient.auth.keystone:KeystoneAuthHandler # Authentication using OpenID Connect protocol but specific to KeyCloak # server regarding multi-tenancy support. KeyCloak has a notion of realm # used as an analog of Keystone project/tenant. keycloak-oidc = mistralclient.auth.keycloak:KeycloakAuthHandler [nosetests] cover-package = mistralclient [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext [pbr] autodoc_index_modules = True warnerrors = True [egg_info] tag_build = tag_date = 0 python-mistralclient-3.3.0/tools/0000775000175100017510000000000013241061116017065 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/tools/config/0000775000175100017510000000000013241061116020332 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/tools/config/generate_sample.sh0000777000175100017510000000536113241060623024035 0ustar zuulzuul00000000000000#!/usr/bin/env bash print_hint() { echo "Try \`${0##*/} --help' for more information." >&2 } PARSED_OPTIONS=$(getopt -n "${0##*/}" -o hb:p:o: \ --long help,base-dir:,package-name:,output-dir: -- "$@") if [ $? != 0 ] ; then print_hint ; exit 1 ; fi eval set -- "$PARSED_OPTIONS" while true; do case "$1" in -h|--help) echo "${0##*/} [options]" echo "" echo "options:" echo "-h, --help show brief help" echo "-b, --base-dir=DIR project base directory" echo "-p, --package-name=NAME project package name" echo "-o, --output-dir=DIR file output directory" exit 0 ;; -b|--base-dir) shift BASEDIR=`echo $1 | sed -e 's/\/*$//g'` shift ;; -p|--package-name) shift PACKAGENAME=`echo $1` shift ;; -o|--output-dir) shift OUTPUTDIR=`echo $1 | sed -e 's/\/*$//g'` shift ;; --) break ;; esac done BASEDIR=${BASEDIR:-`pwd`} if ! [ -d $BASEDIR ] then echo "${0##*/}: missing project base directory" >&2 ; print_hint ; exit 1 elif [[ $BASEDIR != /* ]] then BASEDIR=$(cd "$BASEDIR" && pwd) fi PACKAGENAME=${PACKAGENAME:-${BASEDIR##*/}} TARGETDIR=$BASEDIR/$PACKAGENAME if ! [ -d $TARGETDIR ] then echo "${0##*/}: invalid project package name" >&2 ; print_hint ; exit 1 fi OUTPUTDIR=${OUTPUTDIR:-$BASEDIR/etc} # NOTE(bnemec): Some projects put their sample config in etc/, # some in etc/$PACKAGENAME/ if [ -d $OUTPUTDIR/$PACKAGENAME ] then OUTPUTDIR=$OUTPUTDIR/$PACKAGENAME elif ! [ -d $OUTPUTDIR ] then echo "${0##*/}: cannot access \`$OUTPUTDIR': No such file or directory" >&2 exit 1 fi BASEDIRESC=`echo $BASEDIR | sed -e 's/\//\\\\\//g'` find $TARGETDIR -type f -name "*.pyc" -delete FILES=$(find $TARGETDIR -type f -name "*.py" ! -path "*/tests/*" \ -exec grep -l "Opt(" {} + | sed -e "s/^$BASEDIRESC\///g" | sort -u) EXTRA_MODULES_FILE="`dirname $0`/oslo.config.generator.rc" if test -r "$EXTRA_MODULES_FILE" then source "$EXTRA_MODULES_FILE" fi export EVENTLET_NO_GREENDNS=yes OS_VARS=$(set | sed -n '/^OS_/s/=[^=]*$//gp' | xargs) [ "$OS_VARS" ] && eval "unset \$OS_VARS" DEFAULT_MODULEPATH=mistral.openstack.common.config.generator MODULEPATH=${MODULEPATH:-$DEFAULT_MODULEPATH} OUTPUTFILE=$OUTPUTDIR/$PACKAGENAME.conf.sample python -m $MODULEPATH $FILES > $OUTPUTFILE # Hook to allow projects to append custom config file snippets CONCAT_FILES=$(ls $BASEDIR/tools/config/*.conf.sample 2>/dev/null) for CONCAT_FILE in $CONCAT_FILES; do cat $CONCAT_FILE >> $OUTPUTFILE done python-mistralclient-3.3.0/tools/run_pep80000777000175100017510000000002613241060623020555 0ustar zuulzuul00000000000000#!/bin/sh tox -epep8 python-mistralclient-3.3.0/tools/update_env_deps0000777000175100017510000000102513241060623022162 0ustar zuulzuul00000000000000TOX_ENVLIST=`grep envlist tox.ini | cut -d '=' -f 2 | tr ',' ' '` TESTENVS=`grep testenv tox.ini | awk -F ':' '{print $2}' | tr '[]' ' '` UNFILTERED_ENVLIST=`echo "$TOX_ENVLIST $TESTENVS"` ENVLIST=$( awk 'BEGIN{RS=ORS=" "}!a[$0]++' <<<$UNFILTERED_ENVLIST ); for env in $ENVLIST do ENV_PATH=.tox/$env PIP_PATH=$ENV_PATH/bin/pip echo -e "\nUpdate environment ${env}...\n" if [ ! -d $ENV_PATH -o ! -f $PIP_PATH ] then tox --notest -e$env else $PIP_PATH install -r requirements.txt -r test-requirements.txt fi done python-mistralclient-3.3.0/tools/mistral.bash_completion0000666000175100017510000000142013241060623023631 0ustar zuulzuul00000000000000_mistral_opts="" # lazy init _mistral_flags="" # lazy init _mistral_opts_exp="" # lazy init _mistral() { local cur prev mbc cflags COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" if [ "x$_mistral_opts" == "x" ] ; then mbc="`mistral bash-completion`" _mistral_opts="`echo "$mbc" | sed -e "s/\s-[a-z0-9_-]*//g" -e "s/\s\s*/ /g"`" _mistral_flags="`echo " $mbc" | sed -e "s/ [^-][^-][a-z0-9_-]*//g" -e "s/\s\s*/ /g"`" _mistral_opts_exp="`echo "$_mistral_opts" | sed -e "s/\s/|/g"`" fi if [[ " ${COMP_WORDS[@]} " =~ " "($_mistral_opts_exp)" " && "$prev" != "help" ]] ; then COMPREPLY=($(compgen -W "${_mistral_flags}" -- ${cur})) else COMPREPLY=($(compgen -W "${_mistral_opts}" -- ${cur})) fi return 0 } complete -F _mistral mistral python-mistralclient-3.3.0/doc/0000775000175100017510000000000013241061116016472 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/doc/source/0000775000175100017510000000000013241061116017772 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/doc/source/cli/0000775000175100017510000000000013241061116020541 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/doc/source/cli/cli_usage_with_openstack.rst0000666000175100017510000000233013241060623026332 0ustar zuulzuul00000000000000Using Mistral with OpenStack ============================ The **mistral** shell utility interacts with OpenStack Mistral API from the command-line. It supports the features in the OpenStack Mistral API. Basic Usage ----------- In order to use the CLI, you must provide your OpenStack credentials (for both user and project), and auth endpoint. Use the corresponding configuration options (``--os-username``, ``--os-password``, ``--os-project-name``, ``--os-user-domain-id``, ``os-project-domain-id``, and ``--os-auth-url``), but it is easier to set them in environment variables. .. code-block:: shell $ export OS_AUTH_URL=http://:5000/v2.0 $ export OS_USERNAME=admin $ export OS_TENANT_NAME=tenant $ export OS_PASSWORD=secret $ export OS_MISTRAL_URL=http://:8989/v2 When authenticating against keystone over https: .. code-block:: shell $ export OS_CACERT= Once you've configured your authentication parameters, you can run **mistral** commands. All commands take the form of:: mistral [arguments...] Run **mistral --help** to get a full list of all possible commands, and run **mistral help ** to get detailed help for that command. python-mistralclient-3.3.0/doc/source/cli/cli_usage_with_keycloak.rst0000666000175100017510000000010613241060623026144 0ustar zuulzuul00000000000000Using Mistral with KeyCloak Server ================================== python-mistralclient-3.3.0/doc/source/cli/cli_usage_without_auth.rst0000666000175100017510000000323113241060623026035 0ustar zuulzuul00000000000000Using Mistral without Authentication ==================================== It is possible to execute a workflow on any arbitrary cloud without additional configuration on the Mistral server side. If authentication is turned off in the Mistral server (Pecan's `auth_enable = False` option in `mistral.conf`), there is no need to set the `keystone_authtoken` section. It is possible to have Mistral use an external OpenStack cloud even when it isn't deployed in an OpenStack environment (i.e. no Keystone integration). This setup is particularly useful when Mistral is used in standalone mode, where the Mistral service is not part of the OpenStack cloud and runs separately. To enable this operation, the user can use ``--os-target-username``, ``--os-target-password``, ``--os-target-tenant-id``, ``--os-target-tenant-name``, ``--os-target-auth-token``, ``--os-target-auth-url``, ``--os-target_cacert``, and ``--os-target-region-name`` parameters. For example, the user can return the heat stack list with this setup as shown below: .. code-block:: shell $ mistral \ --os-target-auth-url=http://keystone2.example.com:5000/v3 \ --os-target-username=testuser \ --os-target-tenant=testtenant \ --os-target-password="MistralRuleZ" \ --os-mistral-url=http://mistral.example.com:8989/v2 \ run-action heat.stacks_list The OS-TARGET-* parameters can be set in environment variables as: .. code-block:: shell $ export OS_TARGET_AUTH_URL=http://keystone2.example.com:5000/v3 $ export OS_TARGET_USERNAME=admin $ export OS_TARGET_TENANT_NAME=tenant $ export OS_TARGET_PASSWORD=secret $ export OS_TARGET_REGION_NAME=region python-mistralclient-3.3.0/doc/source/cli/cli_usage_source_execution.rst0000666000175100017510000000506013241060623026676 0ustar zuulzuul00000000000000Replicating Workflows with Mistral ================================== The new command line switch '-s' will allow the operator to replicate / clone an existing workflow execution based on its ID. Once id is given mistral will create a new workflow execution based on the parameters of the first, which will provide a simple approach to spawning a number of workflow executions without having to specify inputs or parameters. Otherwise you can override some of the parameters (e.g. some of the input variables) Basic Usage ----------- From the command line the operator will issue the following. The first step would be to list the current executions, which is done with "execution-list". The following step is to take the listed execution id and pass it to the source execution switch "-s". .. code-block:: shell mistral execution-list mistral execution-create -s Once the workflow execution is selected and the replicate command used you should see a newly created workflow execution based on an existing one with a new execution id. .. code-block:: shell mistral execution-create -s 123e4567-e89b-12d3-a456-426655440000 +--------------------+---------------------------------------+ | Field | Value | +--------------------+---------------------------------------+ | ID | 123e4567-e89b-12d3-a456-77046883182e | | | | | Workflow ID | 123e4567-e89b-12d3-a456-45411dfa33af | | | | | Workflow name | some.workflow.name.goes.here | | | | | Workflow namespace | | | | | | Description | | | | | | Task Execution ID | | | | | | State | RUNNING | | | | | State info | None | | | | | Created at | 2018-01-25 18:41:07 | | | | | Updated at | 2018-01-25 18:41:07 | +--------------------+---------------------------------------+ python-mistralclient-3.3.0/doc/source/class_reference.rst0000666000175100017510000000017613241060623023657 0ustar zuulzuul00000000000000Python Mistral bindings class refrence ====================================== .. toctree:: :maxdepth: 1 api/autoindex python-mistralclient-3.3.0/doc/source/conf.py0000666000175100017510000000713013241060623021276 0ustar zuulzuul00000000000000# Mistral documentation build configuration file import os import pbr.version import sys on_rtd = os.environ.get('READTHEDOCS', None) == 'True' # 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('../../')) sys.path.insert(0, os.path.abspath('../')) sys.path.insert(0, os.path.abspath('./')) # -- 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', ] # Add any paths that contain templates here, relative to this directory. # templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Mistral Client' copyright = u'2016, Mistral Contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. version_info = pbr.version.VersionInfo('python-mistralclient') release = version_info.release_string() version = version_info.version_string() # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # 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 = ['mistralclient.'] # -- 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' # 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' # Must set this variable to include year, month, day, hours, and minutes. html_last_updated_fmt = '%Y-%m-%d %H:%M' # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". html_title = 'MistralClient' # Custom sidebar templates, maps document names to template names. html_sidebars = { 'index': [ 'sidebarlinks.html', 'localtoc.html', 'searchbox.html', 'sourcelink.html' ], '**': [ 'localtoc.html', 'relations.html', 'searchbox.html', 'sourcelink.html' ] } # Output file base name for HTML help builder. htmlhelp_basename = 'Mistraldoc' # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'mistral_client', u'Mistral Client Documentation', [u'Mistral Contributors'], 1) ] # -- Options for openstackdocstheme ------------------------------------------- repository_name = 'openstack/python-mistralclient' bug_project = 'python-mistralclient' bug_tag = '' python-mistralclient-3.3.0/doc/source/index.rst0000666000175100017510000000233413241060623021641 0ustar zuulzuul00000000000000Python bindings to the OpenStack Workflow API ============================================= This is a client for OpenStack Mistral API. There's a Python API (the :mod:`mistralclient` module), and a command-line script (installed as :program:`mistral`). Using mistralclient ------------------- .. toctree:: :maxdepth: 2 cli/cli_usage_with_openstack cli/cli_usage_with_keycloak cli/cli_usage_without_auth cli/cli_usage_source_execution class_reference For information about using the mistral command-line client, see `Workflow service command-line client`_. .. _Workflow service command-line client: https://docs.openstack.org/mistral/latest/cli/index.html Python API Reference -------------------- * `REST API Specification`_ .. _REST API Specification: https://docs.openstack.org/mistral/latest/api/v2.html Contributing ------------ Code is hosted `on GitHub`_. Submit bugs to the python-mistralclient project on `Launchpad`_. Submit code to the openstack/python-mistralclient project using `Gerrit`_. .. _on GitHub: https://github.com/openstack/python-mistralclient .. _Launchpad: https://launchpad.net/python-mistralclient .. _Gerrit: https://docs.openstack.org/infra/manual/developers.html#development-workflow python-mistralclient-3.3.0/releasenotes/0000775000175100017510000000000013241061116020416 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/releasenotes/notes/0000775000175100017510000000000013241061116021546 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/releasenotes/notes/.placeholder0000666000175100017510000000000013241060623024023 0ustar zuulzuul00000000000000python-mistralclient-3.3.0/releasenotes/notes/remove-keystoneclient-dependency-f2981f29e6673f71.yaml0000666000175100017510000000035513241060623033211 0ustar zuulzuul00000000000000--- other: - The dependency to python-keystoneclient was removed. Relying solely on keystoneauth1. - The user has to set the "OS_USER_DOMAIN_NAME" and "OS_PROJECT_DOMAIN_NAME" explicitly if keystone v3 version is being used. python-mistralclient-3.3.0/releasenotes/notes/fix-region-name-2031ff4b83b6308e.yaml0000666000175100017510000000030713241060623027553 0ustar zuulzuul00000000000000--- fixes: - | ``--os-region-name`` or ``OS_REGION_NAME`` is fully supported now and will be passed to Mistral service in order to get OpenStack service client for the specific region. python-mistralclient-3.3.0/releasenotes/notes/set-default-limit-value-7e293d843d6d85ac.yaml0000666000175100017510000000041213241060623031327 0ustar zuulzuul00000000000000--- critical: - | The "--limit" parameter of heavy CLI commands like "task-list" and "execution-list" and "action-execution-list" is set to "100" by default to avoid the huge load on server. To fetch the full result set, user may use "--limit -1". python-mistralclient-3.3.0/releasenotes/notes/add-namespaces-to-cli-9d20e7fcb07c223f.yaml0000666000175100017510000000102713241060623030762 0ustar zuulzuul00000000000000--- features: - | Using namespaces is now supported from the client. It is possible to use the namespace flag with all workflows commands and when creating a workflow execution. Namespace feature allows a user to add two workflows with the same name to Mistral as long as they are within two different namespaces. Namespace will also be visible in the table printed when using the CLI to get info about workflows. Plus, the CLI will print the workflow namespace along with the workflow name where needed. python-mistralclient-3.3.0/releasenotes/source/0000775000175100017510000000000013241061116021716 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/releasenotes/source/newton.rst0000666000175100017510000000021113241060623023760 0ustar zuulzuul00000000000000=========================== Newton Series Release Notes =========================== .. release-notes:: :branch: origin/stable/newton python-mistralclient-3.3.0/releasenotes/source/_templates/0000775000175100017510000000000013241061116024053 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/releasenotes/source/_templates/.placeholder0000666000175100017510000000000013241060623026330 0ustar zuulzuul00000000000000python-mistralclient-3.3.0/releasenotes/source/ocata.rst0000666000175100017510000000023013241060623023536 0ustar zuulzuul00000000000000=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: origin/stable/ocata python-mistralclient-3.3.0/releasenotes/source/mitaka.rst0000666000175100017510000000021113241060623023714 0ustar zuulzuul00000000000000=========================== Mitaka Series Release Notes =========================== .. release-notes:: :branch: origin/stable/mitaka python-mistralclient-3.3.0/releasenotes/source/unreleased.rst0000666000175100017510000000015313241060623024602 0ustar zuulzuul00000000000000============================ Current Series Release Notes ============================ .. release-notes:: python-mistralclient-3.3.0/releasenotes/source/liberty.rst0000666000175100017510000000021513241060623024124 0ustar zuulzuul00000000000000============================ Liberty Series Release Notes ============================ .. release-notes:: :branch: origin/stable/liberty python-mistralclient-3.3.0/releasenotes/source/conf.py0000666000175100017510000002120413241060623023220 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. # # Mistral documentation build configuration file, created by # sphinx-quickstart on Fri Nov 1 02:06:28 2013. # # This file is execfile()d with the current directory set to its containing # dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.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 = [ 'openstackdocstheme', 'reno.sphinxext', ] # Add any paths that contain templates here, relative to this directory. # templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Mistral Client Release Notes' copyright = u'2016, Mistral developers' # Release notes are version independent. # The short X.Y version. version = '' # The full version, including alpha/beta/rc tags. release = '' # 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' # Must set this variable to include year, month, day, hours, and minutes. html_last_updated_fmt = '%Y-%m-%d %H:%M' # 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 = [] # -- 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 = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_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 = 'MistralreleaseNotesdoc' # -- Options for LaTeX output ------------------------------------------------ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, # documentclass [howto/manual]). latex_documents = [ ('index', 'MistralClientReleaseNotes.tex', u'Mistral Client Release Notes Documentation', u'Mistral 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', 'mistral_clientreleasenotes', u'Mistral Client Release Notes Documentation', [u'Mistral 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', 'MistralClientReleaseNotes', u'Mistral Client Release Notes Documentation', u'Mistral developers', 'MistralClientReleaseNotes', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] # -- Options for openstackdocstheme ------------------------------------------- repository_name = 'openstack/python-mistralclient' bug_project = 'python-mistralclient' bug_tag = '' python-mistralclient-3.3.0/releasenotes/source/_static/0000775000175100017510000000000013241061116023344 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/releasenotes/source/_static/.placeholder0000666000175100017510000000000013241060623025621 0ustar zuulzuul00000000000000python-mistralclient-3.3.0/releasenotes/source/index.rst0000666000175100017510000000025213241060641023562 0ustar zuulzuul00000000000000Mistral Client Release Notes ============================ Contents ======== .. toctree:: :maxdepth: 2 unreleased pike ocata newton mitaka liberty python-mistralclient-3.3.0/releasenotes/source/pike.rst0000666000175100017510000000021713241060623023404 0ustar zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike python-mistralclient-3.3.0/README.rst0000666000175100017510000000625013241060623017423 0ustar zuulzuul00000000000000======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/badges/python-mistralclient.svg :target: https://governance.openstack.org/reference/tags/index.html Mistral ======= .. image:: https://img.shields.io/pypi/v/python-mistralclient.svg :target: https://pypi.python.org/pypi/python-mistralclient/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/python-mistralclient.svg :target: https://pypi.python.org/pypi/python-mistralclient/ :alt: Downloads Mistral is a workflow service. Most business processes consist of multiple distinct interconnected steps that need to be executed in a particular order in a distributed environment. A user can describe such a process as a set of tasks and their transitions. After that, it is possible to upload such a description to Mistral, which will take care of state management, correct execution order, parallelism, synchronization and high availability. Mistral also provides flexible task scheduling so that it can run a process according to a specified schedule (for example, every Sunday at 4.00pm) instead of running it immediately. In Mistral terminology such a set of tasks and relations between them is called a workflow. Mistral client ============== Python client for Mistral REST API. Includes python library for Mistral API and Command Line Interface (CLI) library. Installation ------------ First of all, clone the repo and go to the repo directory:: $ git clone git://git.openstack.org/openstack/python-mistralclient.git $ cd python-mistralclient Then just run:: $ pip install -e . or:: $ pip install -r requirements.txt $ python setup.py install Running Mistral client ---------------------- If Mistral authentication is enabled, provide the information about OpenStack auth to environment variables. Type:: $ export OS_AUTH_URL=http://:5000/v2.0 $ export OS_USERNAME=admin $ export OS_TENANT_NAME=tenant $ export OS_PASSWORD=secret $ export OS_MISTRAL_URL=http://:8989/v2 (optional, by default URL=http://localhost:8989/v2) and in the case that you are authenticating against keystone over https: $ export OS_CACERT= .. note:: In client, we can use both Keystone auth versions - v2.0 and v3. But server supports only v3.* To make sure Mistral client works, type:: $ mistral workbook-list You can see the list of available commands typing:: $ mistral --help Useful Links ============ * `PyPi`_ - package installation * `Launchpad project`_ - release management * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ * `Specs`_ * `How to Contribute`_ .. _PyPi: https://pypi.python.org/pypi/python-mistralclient .. _Launchpad project: https://launchpad.net/python-mistralclient .. _Blueprints: https://blueprints.launchpad.net/python-mistralclient .. _Bugs: https://bugs.launchpad.net/python-mistralclient .. _Source: https://git.openstack.org/cgit/openstack/python-mistralclient .. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html .. _Specs: https://specs.openstack.org/openstack/mistral-specs/ python-mistralclient-3.3.0/tox.ini0000666000175100017510000000277713241060641017261 0ustar zuulzuul00000000000000[tox] envlist = py35,py27,pep8 minversion = 1.6 skipsdist = True [testenv] usedevelop = True install_command = pip install {opts} {packages} setenv = VIRTUAL_ENV={envdir} NOSE_WITH_OPENSTACK=1 NOSE_OPENSTACK_COLOR=1 NOSE_OPENSTACK_RED=0.05 NOSE_OPENSTACK_YELLOW=0.025 NOSE_OPENSTACK_SHOW_ELAPSED=1 NOSE_OPENSTACK_STDOUT=1 NOSE_XUNIT=1 DISCOVER_DIRECTORY=mistralclient/tests/unit passenv = http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/queens} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = find . -type f -name "*.pyc" -delete nosetests mistralclient/tests/unit whitelist_externals = find rm [testenv:functional] setenv = OS_TEST_PATH = ./mistralclient/tests/functional commands = {posargs} [testenv:pep8] commands = flake8 {posargs} [testenv:venv] commands = {posargs} [testenv:docs] commands = rm -rf doc/html doc/build rm -rf doc/source/apidoc doc/source/api python setup.py build_sphinx [flake8] # H106: Don’t put vim configuration in source files # H203: Use assertIs(Not)None to check for None enable-extensions=H106,H203 show-source = true builtins = _ exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,tools [testenv:releasenotes] commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html python-mistralclient-3.3.0/ChangeLog0000664000175100017510000004277713241061114017516 0ustar zuulzuul00000000000000CHANGES ======= 3.3.0 ----- * Don't override session during auth * Running new workflow based on existing execution * Update UPPER\_CONSTRAINTS\_FILE for stable/queens * Update .gitreview for stable/queens 3.2.0 ----- * Clean up keystone authentication * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Avoid tox\_install.sh for constraints support * Fix limit handling to not send value of -1 * Remove setting of version/release from releasenotes * Updated from global requirements * Updated from global requirements * Support no\_auth mode in mistral client * Fix several problems in keycloak auth module * Don't create client for help and bash completion * Remove "insecure" in token-based authentication * Updated from global requirements * Fix a typo in env variable name * Add default values for domain related options * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Enable ssl support for keycloak auth module * Support for Project\_domain\_id and user\_domain\_id * Updated from global requirements * Updated from global requirements * Namespaces release note * Create and run a workflow within a namespace * Updated from global requirements * Task list now only queries the displayed fields * Use keystoneauth plugins and session instead of keystoneclient * Fix how "--limit" is passed to the server for action executions * mistral execution-list -f value should be empty * Add test for target parameters and fix requests lib error * Update reno for stable/pike * Updated from global requirements * Make README better 3.1.1 ----- * String interpolation should be delayed * Give better tox output * Apply Pike document structure * Updated from global requirements * Update and optimize documentation links * Enable warning-is-error in doc build * Updated from global requirements * Add CLI for event trigger operations * Set the default value of --limit parameter * Switch from oslosphinx to openstackdocstheme * Updated from global requirements * Enable some off-by-default checks * Make --profile load from environment variables * Updated from global requirements * Updated from global requirements * Replace request mocking in test\_httpclient with requests-mock * Updated from global requirements * Updated from global requirements * Change service type to workflowv2 * Updated from global requirements * Updated from global requirements * Updated from global requirements * Add a missing space to the help message for execution-create * fix release note formatting * Explicitly set 'builders' option * doc: Remove cruft from conf.py * Fix doc generation for python 3 3.1.0 ----- * Updated from global requirements * Optimize the link address * Add release note for region name bugfix * Take DST into account when converting to UTC * Fix region support in mistralclient * Update the gitingore * Remove log translations * Use session from OSC plugin * Accept keystone session in client * Use generic keystone client instead of versioned * Add --utc flag to cron trigger create * Updated from global requirements * Update test requirement * Updated from global requirements * Remove support for py34 * Updated from global requirements * Updated from global requirements * Add project\_id and project\_name options * Read project\_id and project\_name in env * Update reno for stable/ocata * Format the list of auth types in the Mistral client help * Added link for modindex * Fix doc build if git is absent * Changed the README.rst 3.0.0 ----- * Pass target insecure flag to msitral service * Updated from global requirements * Add user and project domain name parameters for target cloud * Update .gitignore * Removes unnecessary utf-8 encoding * Initial commit for mistral-i18n support * Add tests for filters in CLI list commands * Cosmetic changes in CLI tests * Add '--filter' parameter to list commands in CLI * Add filters to client Python APIs * Use assertGreater() or assertLess() * Keystone v3 needs extra parameters * Initial commit for python-mistralclient document * Remove commented-out Apache 2 classifier from setup.cfg * Make python mistralclient readme better * Filter workflow executions by creating task execution id * Updated from global requirements * Fix for failing dsvm gate * Move json.loads() method to utils.py, and use "with" to deal with file objects * Make python mistralclient readme better * Show team and repo badges on README * Updated tox.ini to pick up requirements from upper constraints * Region name related command line arguments are added * Replace uuid4() with generate\_uuid() from oslo\_utils * Removing deprecation warnings to pass py35 * Removed the extra space from tox.ini * Updated from global requirements * Remove unused pylint * Added the reno for stable/mitaka stable/newton and stable/liberty * Add cancelled state to action executions * Remove unused scripts in tools * Updated from global requirements * remove apiclient from mistralclient * remove openstack/common/cliutils.py * Updated from global requirements * Remove unused openstack/common/apiclient/client * Removed openstack/common/importutils.py * Updated from global requirements * Fix python35 job failures * Adding files to .gitignore * Enable DeprecationWarning in test environments * Add Python 3.5 classifier and venv * Updated from global requirements * Fixed errors while generating releasenotes * Replaced savanna word with mistralclient * Updated from global requirements * Fix warning when running tox -e docs * Remove white space between print () in cliutils.py and lintstack.py * Add plug-in summary for osc doc * Updated from global requirements * Fix PEP8 issues and incorrect version/release details * Enable release notes translation * Updated from global requirements * Expose the --run-sync Action Execution parameter on the CLI * Added the # -\*- coding: utf-8 -\*- to the conf.py * Updated from global requirements * Updated from global requirements * Send access info to server * Maintain releasenotes for python-mistralclient * Refactor common parts of client tests * Updated from global requirements * Avoid use xx=[] for parameter to initialize it's value * Trivial: Remove vim header from source files * Remove clashes of openstackclient command in mistral * Abstract authentication function * Add 'created\_at' and 'updated\_at' to action-execution-get and action-execution-list command Closes-bug: 1618767 Change-Id: I422fdcdfa66d6b7a781542c7acc458f8c46edb18 * Added sphinix config to setup.cfg * Updated from global requirements * Add 'created\_at' and 'updated\_at' to task-get command * Pass httpclient to managers * Use requests-mock for testing * Use oslotest instead of testtools/unittest * Fixing auth for keystone v2.0 * Fix wrong error message for environment operation * Make osprofiler dependency really "soft" * Fixing getting mistral\_url from keystone catalog 2.1.1 ----- * Make osprofiler dependency "soft" * Clean imports in code * TrivialFix: Remove logging import unused * Updated from global requirements 2.1.0 ----- * Updated from global requirements * Updated from global requirements * Added ID option to update Action Definition * Add error message when OS\_USERNAME or OS\_PASSWORD not provided * Add target-\* parameters to python-mistralclient * Different formatters for "action-execution-get" and "action-execution-list" * Add "Task ID" field for "action-execution-get" command output * Remove usage of private '\_url' property from OSC * Add cancelled state to executions * Added 'pip install -r requirements.txt' instruction * Updated from global requirements * Fixed ssl options for httpclient * Changed argument names as per other python clients * Change action-get help to get action info by ID * Updated from global requirements * Add KeyCloak OpenID Connect authentication * Updated from global requirements * Use osc\_lib instead of cliff * Use osc-lib instead of openstackclient * Updated from global requirements * Removed use of tempest\_lib and used tempest instead * Updated from global requirements * Remove .mailmap since it's no longer needed * Updated from global requirements * Add osprofiler option to trace operations * Remove AUTHORS file from git tracking * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Added CONTRIBUTING.rst file * Validate ad-hoc action via cli * Updated from global requirements * Updated from global requirements * Updated from global requirements * Fix task result syntax in workflows used for functional tests * Updated from global requirements * Fix cacert and insecure options on HTTP requests * Change the mistralclient for Mistral action pack 2.0.0 ----- * Functional tests for workflow sharing * Fix show member error * Updated from global requirements * Support resource sharing CLI * Updated from global requirements * Support workflow id for execution CLI * Updated from global requirements * Support ID for workflow operations in CLI * Support workflow id for cron trigger creation * Add task\_execution\_id to workflow execution in CLI * Updated from global requirements * Updated from global requirements * Fix db error when running python34 unit tests * Add env option to CLI for executions and tasks update * Updated from global requirements * Updated from global requirements * Drop py33 support * Updated from global requirements * Updated from global requirements * Pass environment variables of proxy to tox * Updated from global requirements * Delete python bytecode before every test run * Remove py26 support 1.2.0 ----- * Updated from global requirements * Introduce openstackclient plugin * Show project id when retrieving workflow(s) * Show workflow\_id when getting workflow(s) * Add post\_test\_hook script that will run tests on the gate * Fix all H201 pep8 errors * Adding --insecure flag * Make help message of execution pagination query more readable * Create pagination for the mistral client * Wrapped long lines in the "Tags" column * Truncate 'input' data in the wf/action update command output * Changing attribute of run\_tests script * Added home-page link in setup.cfg file * Fix dict ordering issue in action executions CLI test * Updated from global requirements * Updated from global requirements * Adding correct version info to mistralclient * Fixing dict comparison in task CLI tests * Updated from global requirements * Show exact error message when authentication falied instead of HTML body * Fix support of dev use case with no auth * Add state info to tasks list * Add .mailmap for pbr AUTHORS generation * Update AUTHORS file * Updated from global requirements * Fix order of arguments in assertEqual * Corrected output when env description is not provided * Output format is corrected for wf-validate and wb-validate * Adding pagination support to mistral python client * Added some tests for environment * Default auth\_url is set, if it is not provided * Updated from global requirements 1.1.0 ----- * Fixing test failing on workflow-delete * Updated from global requirements * Provide an ability to make action/workflow public * Removed unused functional test * Fix order of arguments in assertEqual 1.0.2 ----- * Remove invalid test case of mistralclient * Updated from global requirements * Fix failure of run\_action CLI command * Support action\_execution deletion on client side * Fixing dict ordering issue in task tests * Update README.md for project namespace and repo change * Add service API support in client side 1.0.1 ----- * Add CLI for rerunning failed task execution * Add python 3 classifiers * Fix PY3 compatibility * Adding to\_dict() method to Resource class * Changing check of mistral availability in functional tests * User 'alt\_demo' user instead of 'demo' for cli tests * Fixing launching mistralclient tests * Configure logging with debug flag 1.0.0 ----- * Update requirements to unwedge solum * Append newlines to two new test files * Implementing run-action command in client * fix mistral-client use does not use endpoint keystone: * Add description param for execution create/update * Update .gitreview file for project rename * Remove unnecessary validation in update environment form the client * Make create commands cut long output * Mistral bash completion script optimization * Add bash-completion command support * Fix cli integration tests * Update requirements for master branch * Delete bash completion script auto placement * Adding '--params' to cron-trigger CLI * Support resource deletion in batches * Update cli integration test due to last changes * Modified help display format to present a more user-friendly display * Update commands bash completion script * Adding 'Task name' in Action Execution CLI * Fixing state\_info column in CLI * Delete requirement on unneeded arguments 2015.1.0rc1 ----------- * Remove "policies" keyword from test resources * Fixing a type in execution-create command help * Removing v1 stuff (API methods, commands, tests) * Add workbook and workflow validate commands * Updating AUTHORS * Add functional CLI tests for 'action-execution' endpoint * Fixing task CLI * Added tempest-lib to test-requirements.txt * Fixing tasks operations in mistralclient * Add action execution client lib and CLI * Fix failing after refactoring CLI test for execution * Trigger remaining-executions and first-exec-date * Rename execution\_id to workflow\_execution\_id * add bash completion script 2015.1.0b3 ---------- * Refactor task output: "wf\_name" -> "workflow\_name" * Deleting command 'get-task-output' from CLI * Adjust all test examples in mistralclient * Import 'decorators' from tempest\_lib instead of 'test' from tempest * Add support for auth against keystone on https * Fix tempest gate, add tempest import * Add tests which check env isolation * Add CLI tests for environments 2015.1.0b2 ---------- * Extend cli integration tests for update * Add error code to APIException * Fix import in functional tests * Implement commands for execution environment * Fix client initialization in the integration tests 2015.1.0b1 ---------- * Add mistralclient documentation * Add new functional CLI tests * Use YAML text instead of JSON in HTTP body (client side) * Add CLI multi tenancy tests * Fix pep8 issues * Fix execution-update CLI test * Refactor CLI tests * Add CLI tests for actions * Updating AUTHORS file 0.1.1 ----- * Fix CLI cron-trigger tests * Fix passing workflow input via UI * Fix execution state choices and commenting workflow input in triggers * Add CLI tests for triggers * Adding cron triggers * Provide workflow input * Adjust action commands (CLI) * Add script to run functional CLI and client tests locally * Fix 'is\_system' property in action commands * Refactoring CLI commands returning lists * Adding 'tags' to actions * Update requirements according to global requirements (master) 0.1 --- * Fix CLI v2 tests for workflows * Fix CLI v2 test for workbooks and executions * Getting rid of 'name' and 'tags' for workbook create/update * Removing 'name' and 'tags' from action API and CLI * Removing 'name' and 'tags' from workflow API and CLI * Add 'name' to test workbook * Renaming 'parameters' to 'input' * Fix client tests due to changes in code from 'mistral' repository * Add task-get-parameters command * Minor changes in tests files * Fix workflow-update command * Support naive filtering in python API * Fixed execution create method * Add actions API and CLI commands * Add CLI integration tests for v2 * Add hacking to the flake8 tests * Add unit tests on client CLI v2 * Add CLI for client v2 * Add unit tests on v2 client * Implement python-mistralclient v2 * Work toward Python 3.4 support and testing * Move integration tests under mistralclient/tests folder * Add negative tests for CLI * Fix failing 'get\_task' test * Add CLI tests for workbook, execution and task * Add simple tests for Mistral CLI * Remove unneeded definitions of Python Source Code Encoding * Fix typo in base.py * Fix path to integrational tests * Support v2 Keystone API in client * Override configure\_logging to quieten iso8601 and requests * Add integrational tests for executions and tasks * Add integration tests (actions with workbooks) * Update requirements due to global-requirements 0.0.4 ----- * Modify API to make use of /executions endpoint * Removing horizon plugin code base from client * Correct the name that the client is installed as * Remove the log module and all dependencies * Update openstack/common code * Don't use oslo logging in the client * change assertEquals to assertEqual * "target\_task" -> "task" in tests * cleaning up index.rst file * Handle error from upload definition command * Rename Target to Task in execution format * Print status code of requests * Return response as error if not valid json object 0.0.2 ----- * Fixed issues with tarballs 0.0.1 ----- * Replace Task text field with drop-down list * Add list of tasks for an execution * Add authentication to dashboard * Temporarily harbouring Horizon Dashboard * Add upload workbook definition * Add uploading context as a file * Update oslo-incubator dependencies * Add script to allow update dependencies in all envs * Fix context serialization in Execution.create * Modify API tests to check the calls * Fix a bug with empty context * Made keystone authentication optional * Add command-line interface * Add context parameter in execution creation * Rename "target\_task" to "task" * Adding changes related to Data Flow * Fix installation with requirements * Moving Mistral Client code from main Mistral repository * Adding .gitignore file * Initial commit python-mistralclient-3.3.0/run_functional_tests.sh0000777000175100017510000000233513241060623022543 0ustar zuulzuul00000000000000#! /usr/bin/env bash ARG=$1 export USER_AUTH_SETTING=$(echo $OS_AUTH_URL) function pre_hook() { export WITHOUT_AUTH="True" IS_TEMPEST=$(pip freeze | grep tempest) if [ -z "$IS_TEMPEST" ] then echo "$(tput setaf 4)No such module 'tempest' in the system. Before running this script please install tempest module using : pip install git+http://github.com/openstack/tempest.git$(tput sgr 0)" fi } function run_tests_by_version() { export OS_AUTH_URL="" echo "$(tput setaf 4)Running integration CLI and python-mistralclient tests for v$1$(tput sgr 0)" export VERSION="v$1" nosetests -v mistralclient/tests/functional/cli/v$1/ nosetests -v mistralclient/tests/functional/client/v$1/ unset VERSION } function run_tests() { if [ -z "$ARG" ] then run_tests_by_version 1 run_tests_by_version 2 elif [ "$ARG" == "v1" ] then run_tests_by_version 1 elif [ "$ARG" == "v2" ] then run_tests_by_version 2 fi } function post_hook () { unset LOCAL_RUN export OS_AUTH_URL=$USER_AUTH_SETTING unset USER_AUTH_SETTING } #----------main-part---------- echo "$(tput setaf 4)Preparation for tests running...$(tput sgr 0)" pre_hook echo "$(tput setaf 4)Running tests...$(tput sgr 0)" run_tests post_hook python-mistralclient-3.3.0/LICENSE0000666000175100017510000002363613241060623016750 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-mistralclient-3.3.0/PKG-INFO0000664000175100017510000001141213241061116017021 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: python-mistralclient Version: 3.3.0 Summary: Mistral Client Library Home-page: https://docs.openstack.org/python-mistralclient/latest/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: Apache Software License Description-Content-Type: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/badges/python-mistralclient.svg :target: https://governance.openstack.org/reference/tags/index.html Mistral ======= .. image:: https://img.shields.io/pypi/v/python-mistralclient.svg :target: https://pypi.python.org/pypi/python-mistralclient/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/python-mistralclient.svg :target: https://pypi.python.org/pypi/python-mistralclient/ :alt: Downloads Mistral is a workflow service. Most business processes consist of multiple distinct interconnected steps that need to be executed in a particular order in a distributed environment. A user can describe such a process as a set of tasks and their transitions. After that, it is possible to upload such a description to Mistral, which will take care of state management, correct execution order, parallelism, synchronization and high availability. Mistral also provides flexible task scheduling so that it can run a process according to a specified schedule (for example, every Sunday at 4.00pm) instead of running it immediately. In Mistral terminology such a set of tasks and relations between them is called a workflow. Mistral client ============== Python client for Mistral REST API. Includes python library for Mistral API and Command Line Interface (CLI) library. Installation ------------ First of all, clone the repo and go to the repo directory:: $ git clone git://git.openstack.org/openstack/python-mistralclient.git $ cd python-mistralclient Then just run:: $ pip install -e . or:: $ pip install -r requirements.txt $ python setup.py install Running Mistral client ---------------------- If Mistral authentication is enabled, provide the information about OpenStack auth to environment variables. Type:: $ export OS_AUTH_URL=http://:5000/v2.0 $ export OS_USERNAME=admin $ export OS_TENANT_NAME=tenant $ export OS_PASSWORD=secret $ export OS_MISTRAL_URL=http://:8989/v2 (optional, by default URL=http://localhost:8989/v2) and in the case that you are authenticating against keystone over https: $ export OS_CACERT= .. note:: In client, we can use both Keystone auth versions - v2.0 and v3. But server supports only v3.* To make sure Mistral client works, type:: $ mistral workbook-list You can see the list of available commands typing:: $ mistral --help Useful Links ============ * `PyPi`_ - package installation * `Launchpad project`_ - release management * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ * `Specs`_ * `How to Contribute`_ .. _PyPi: https://pypi.python.org/pypi/python-mistralclient .. _Launchpad project: https://launchpad.net/python-mistralclient .. _Blueprints: https://blueprints.launchpad.net/python-mistralclient .. _Bugs: https://bugs.launchpad.net/python-mistralclient .. _Source: https://git.openstack.org/cgit/openstack/python-mistralclient .. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html .. _Specs: https://specs.openstack.org/openstack/mistral-specs/ Platform: UNKNOWN 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.5 Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux python-mistralclient-3.3.0/test-requirements.txt0000666000175100017510000000104713241060623022174 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 python-openstackclient>=3.12.0 # Apache-2.0 sphinx!=1.6.6,>=1.6.2 # BSD mock>=2.0.0 # BSD nose>=1.3.7 # LGPL oslotest>=3.2.0 # Apache-2.0 requests-mock>=1.1.0 # Apache-2.0 tempest>=17.1.0 # Apache-2.0 osprofiler>=1.4.0 # Apache-2.0 reno>=2.5.0 # Apache-2.0 openstackdocstheme>=1.18.1 # Apache-2.0 python-mistralclient-3.3.0/CONTRIBUTING.rst0000666000175100017510000000105213241060623020370 0ustar zuulzuul00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps documented at: https://docs.openstack.org/infra/manual/developers.html Once those steps have been completed, changes to OpenStack should be submitted for review via the Gerrit tool, following the workflow documented at: https://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. Bugs should be filed on Launchpad, not GitHub: https://bugs.launchpad.net/python-mistralclient python-mistralclient-3.3.0/mistralclient/0000775000175100017510000000000013241061116020577 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/mistralclient/i18n.py0000666000175100017510000000152313241060623021735 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. """oslo.i18n integration module. See https://docs.openstack.org/oslo.i18n/latest/user/usage.html """ import oslo_i18n _translators = oslo_i18n.TranslatorFactory(domain='mistralclient') # The primary translation function using the well-known name "_" _ = _translators.primary python-mistralclient-3.3.0/mistralclient/auth/0000775000175100017510000000000013241061116021540 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/mistralclient/auth/keycloak.py0000666000175100017510000001511513241060623023723 0ustar zuulzuul00000000000000# Copyright 2016 - Nokia Networks # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import logging import os import pprint import requests from six.moves import urllib from mistralclient import auth LOG = logging.getLogger(__name__) class KeycloakAuthHandler(auth.AuthHandler): def authenticate(self, req, session=None): """Performs authentication using Keycloak OpenID Protocol. :param req: Request dict containing list of parameters required for Keycloak authentication. * auth_url: Base authentication url of KeyCloak server (e.g. "https://my.keycloak:8443/auth" * client_id: Client ID (according to OpenID Connect protocol). * client_secret: Client secret (according to OpenID Connect protocol). * project_name: KeyCloak realm name. * username: User name (Optional, if None then access_token must be provided). * api_key: Password (Optional). * access_token: Access token. If passed, username and password are not used and this method just validates the token and refreshes it if needed (Optional, if None then username must be provided). * cacert: SSL certificate file (Optional). * insecure: If True, SSL certificate is not verified (Optional). :param session: Keystone session object. Not used by this plugin. """ if not isinstance(req, dict): raise TypeError('The input "req" is not typeof dict.') auth_url = req.get('auth_url') client_id = req.get('client_id') client_secret = req.get('client_secret') realm_name = req.get('project_name') username = req.get('username') password = req.get('api_key') access_token = req.get('access_token') cacert = req.get('cacert') insecure = req.get('insecure', False) if not auth_url: raise ValueError('Base authentication url is not provided.') if not client_id: raise ValueError('Client ID is not provided.') if not realm_name: raise ValueError('Project(realm) name is not provided.') if username and access_token: raise ValueError( "User name and access token can't be " "provided at the same time." ) if not username and not access_token: raise ValueError( 'Either user name or access token must be provided.' ) if access_token: response = self._authenticate_with_token( auth_url, client_id, client_secret, access_token, cacert, insecure ) else: response = self._authenticate_with_password( auth_url, client_id, client_secret, realm_name, username, password, cacert, insecure ) return {'auth_token': response, 'project_id': realm_name} @staticmethod def _authenticate_with_token(auth_url, client_id, client_secret, auth_token, cacert=None, insecure=None): # TODO(rakhmerov): Implement. raise NotImplementedError @staticmethod def _authenticate_with_password(auth_url, client_id, client_secret, realm_name, username, password, cacert=None, insecure=None): access_token_endpoint = ( "%s/realms/%s/protocol/openid-connect/token" % (auth_url, realm_name) ) verify = None if urllib.parse.urlparse(access_token_endpoint).scheme == "https": verify = False if insecure else cacert if cacert else True body = { 'grant_type': 'password', 'username': username, 'password': password, 'client_id': client_id, 'scope': 'profile' } if client_secret: body['client_secret'] = client_secret, resp = requests.post( access_token_endpoint, data=body, verify=verify ) try: resp.raise_for_status() except Exception as e: raise Exception("Failed to get access token:\n %s" % str(e)) LOG.debug("HTTP response from OIDC provider: %s", pprint.pformat(resp.json())) return resp.json()['access_token'] def get_system_ca_file(): """Return path to system default CA file.""" # Standard CA file locations for Debian/Ubuntu, RedHat/Fedora, # Suse, FreeBSD/OpenBSD, MacOSX, and the bundled ca ca_path = ['/etc/ssl/certs/ca-certificates.crt', '/etc/pki/tls/certs/ca-bundle.crt', '/etc/ssl/ca-bundle.pem', '/etc/ssl/cert.pem', '/System/Library/OpenSSL/certs/cacert.pem', requests.certs.where()] for ca in ca_path: LOG.debug("Looking for ca file %s", ca) if os.path.exists(ca): LOG.debug("Using ca file %s", ca) return ca LOG.warning("System ca file could not be found.") # An example of working curl request to keycloak # curl -d "client_id=admin-cli" -d "client_secret=secret" # -d "username=admin" -d "password=qwerty" -d "grant_type=password" # "http://localhost:8080/auth/realms/master/protocol/openid-connect/token" # An example of using KeyCloak OpenID authentication. if __name__ == '__main__': print("Using username/password to get access token from KeyCloak...") auth_handler = KeycloakAuthHandler() a_token = auth_handler.authenticate( dict( "https://my.keycloak:8443/auth", client_id="mistral_client", client_secret="secret", project_name="mistral", username="user", api_key="secret", insecure=True ) )['auth_token'] print("Auth token: %s" % a_token) python-mistralclient-3.3.0/mistralclient/auth/auth_types.py0000666000175100017510000000141013241060623024277 0ustar zuulzuul00000000000000# Copyright 2016 - Nokia Networks # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from stevedore import extension # Valid authentication types. ALL = extension.ExtensionManager( namespace='mistralclient.auth', invoke_on_load=False ).names() python-mistralclient-3.3.0/mistralclient/auth/keystone.py0000666000175100017510000002142013241060623023756 0ustar zuulzuul00000000000000# Copyright 2016 - Nokia Networks # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import logging import keystoneauth1.access.service_catalog as sc import keystoneauth1.identity.generic as auth_plugin from keystoneauth1 import session as ks_session import mistralclient.api.httpclient as api from mistralclient import auth as mistral_auth from oslo_serialization import jsonutils LOG = logging.getLogger(__name__) class KeystoneAuthHandler(mistral_auth.AuthHandler): def authenticate(self, req, session=None): """Perform authentication via Keystone. :param req: Request dict containing the parameters required for Keystone authentication. """ reqs = self._separate_target_reqs(req) try: result = self._authenticate(reqs, session) except Exception as e: if "Cannot use v2 authentication with domain scope" in str(e): LOG.warning("Client tried to use v2 authentication with " "domain scope. Domain parameters are assumed " "to be erroneously set. Retrying " "authentication without them. " "Request parameters: %s" % str(reqs)) domainless_reqs = [reqs[0], self._remove_domain(reqs[1])] result = self._authenticate(domainless_reqs, session) else: raise return result @staticmethod def _separate_target_reqs(req): """Separates parameters into target and non-target ones. target_* parameters are rekeyed by removing the prefix. :param req: Request dict containing the parameters for Keystone authentication. :return: list of [non-target, target] request parameters """ r = {} target_r = {} target_prefix = "target_" for key in req: if key.startswith(target_prefix): target_r[key[len(target_prefix):]] = req[key] else: r[key] = req[key] return [r, target_r] @staticmethod def _remove_domain(req): """Remove all domain parameters from req. Keystoneauth with V2 does not accept domain parameters. This is an incompatible change from Keystoneclient but it would unnecessarily break clients of Mistral. It is safe to remove domain parameters if V2 auth is targeted. :param req: Request dict containing list of parameters required :return: Request dict without domains """ r = {} for key in req: if "domain" not in key: r[key] = req[key] return r @staticmethod def _get_auth(api_key=None, auth_token=None, auth_url=None, project_domain_id=None, project_domain_name=None, project_id=None, project_name=None, user_domain_id=None, user_domain_name=None, user_id=None, username=None, **kwargs): if project_name and project_id: raise RuntimeError( 'Only one of project_name or project_id should be set' ) if username and user_id: raise RuntimeError( 'Only one of username or user_id should be set' ) auth = {} if auth_token: auth = auth_plugin.Token( auth_url=auth_url, project_domain_id=project_domain_id, project_domain_name=project_domain_name, project_id=project_id, project_name=project_name, token=auth_token ) elif api_key and (username or user_id): auth = auth_plugin.Password( auth_url=auth_url, password=api_key, project_domain_id=project_domain_id, project_domain_name=project_domain_name, project_id=project_id, project_name=project_name, user_domain_id=user_domain_id, user_domain_name=user_domain_name, user_id=user_id, username=username ) return auth def _authenticate(self, reqs, session=None): """Performs authentication via Keystone. :param reqs: Request dict containing list of parameters required for Keystone authentication. :return: Auth response dict """ if not isinstance(reqs[0], dict): raise TypeError('The input "req" is not typeof dict.') if not isinstance(reqs[1], dict): raise TypeError('The input "req" is not typeof dict.') auth_response = {} req = reqs[0] cacert = req.get('cacert') endpoint_type = req.get('endpoint_type', 'publicURL') insecure = req.get('insecure') mistral_url = req.get('mistral_url') region_name = req.get('region_name') service_type = req.get('service_type', 'workflowv2') verify = self._verification_needed(cacert, insecure) if not session: auth = self._get_auth(**req) if auth: session = ks_session.Session(auth=auth, verify=verify) if session: if not mistral_url: try: mistral_url = session.get_endpoint( service_type=service_type, endpoint_type=endpoint_type, region_name=region_name ) except Exception: mistral_url = None auth_response['mistral_url'] = mistral_url auth_response['session'] = session target_req = reqs[1] if "auth_url" in target_req: target_auth = self._get_auth(**target_req) if target_auth: # target cacert and insecure cacert = target_req.get('cacert') insecure = target_req.get('insecure') verify = self._verification_needed(cacert, insecure) target_session = ks_session.Session( auth=target_auth, verify=verify ) target_auth_headers = target_session.get_auth_headers() or {} target_auth_token = target_auth_headers.get('X-Auth-Token') auth_response.update({ api.TARGET_AUTH_TOKEN: target_auth_token, api.TARGET_PROJECT_ID: target_session.get_project_id(), api.TARGET_USER_ID: target_session.get_user_id(), api.TARGET_AUTH_URI: target_auth._plugin.auth_url, }) access = target_auth.get_access(target_session) service_catalog = access.service_catalog if self._is_service_catalog_v2(service_catalog): access_data = access._data["access"] if not len(access_data['serviceCatalog']): LOG.warning( "Service Catalog empty, some authentication" "credentials may be missing. This can cause" "malfunction in the Mistral action executions.") sc_json = jsonutils.dumps(access_data) auth_response[api.TARGET_SERVICE_CATALOG] = sc_json if not auth_response: LOG.debug("No valid token or password + user provided. " "Continuing without authentication") return {} return auth_response @staticmethod def _verification_needed(cacert, insecure): """Return the verify parameter. The value of verify can be either True/False or a cacert. :param cacert None or path to CA cert file :param insecure truthy value to switch on SSL verification """ if insecure is False or insecure is None: verify = cacert or True else: verify = False return verify @staticmethod def _is_service_catalog_v2(catalog): """Check if the service catalog is of type ServiceCatalogV2 :param catalog: the service catalog :return: True if V2, False otherwise """ return type(catalog) is sc.ServiceCatalogV2 python-mistralclient-3.3.0/mistralclient/auth/__init__.py0000666000175100017510000000205613241060623023660 0ustar zuulzuul00000000000000# Copyright 2016 - Brocade Communications Systems, 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 abc import six from stevedore import driver def get_auth_handler(auth_type): mgr = driver.DriverManager( 'mistralclient.auth', auth_type, invoke_on_load=True ) return mgr.driver @six.add_metaclass(abc.ABCMeta) class AuthHandler(object): """Abstract base class for an authentication plugin.""" @abc.abstractmethod def authenticate(self, req): raise NotImplementedError() python-mistralclient-3.3.0/mistralclient/commands/0000775000175100017510000000000013241061116022400 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/mistralclient/commands/__init__.py0000666000175100017510000000000013241060623024503 0ustar zuulzuul00000000000000python-mistralclient-3.3.0/mistralclient/commands/v2/0000775000175100017510000000000013241061116022727 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/mistralclient/commands/v2/workbooks.py0000666000175100017510000001210313241060623025322 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import argparse from osc_lib.command import command from mistralclient.commands.v2 import base from mistralclient import utils def format(workbook=None): columns = ( 'Name', 'Tags', 'Created at', 'Updated at' ) if workbook: data = ( workbook.name, base.wrap(', '.join(workbook.tags or '')) or '', workbook.created_at, ) if hasattr(workbook, 'updated_at'): data += (workbook.updated_at,) else: data += (None,) else: data = (tuple('' for _ in range(len(columns))),) return columns, data class List(base.MistralLister): """List all workbooks.""" def _get_format_function(self): return format def _get_resources(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine return mistral_client.workbooks.list() class Get(command.ShowOne): """Show specific workbook.""" def get_parser(self, prog_name): parser = super(Get, self).get_parser(prog_name) parser.add_argument( 'workbook', help='Workbook name' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine workbook = mistral_client.workbooks.get(parsed_args.workbook) return format(workbook) class Create(command.ShowOne): """Create new workbook.""" def get_parser(self, prog_name): parser = super(Create, self).get_parser(prog_name) parser.add_argument( 'definition', type=argparse.FileType('r'), help='Workbook definition file' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine workbook = mistral_client.workbooks.create( parsed_args.definition.read() ) return format(workbook) class Delete(command.Command): """Delete workbook.""" def get_parser(self, prog_name): parser = super(Delete, self).get_parser(prog_name) parser.add_argument('workbook', nargs='+', help='Name of workbook(s).') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine utils.do_action_on_many( lambda s: mistral_client.workbooks.delete(s), parsed_args.workbook, "Request to delete workbook %s has been accepted.", "Unable to delete the specified workbook(s)." ) class Update(command.ShowOne): """Update workbook.""" def get_parser(self, prog_name): parser = super(Update, self).get_parser(prog_name) parser.add_argument( 'definition', type=argparse.FileType('r'), help='Workbook definition file' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine workbook = mistral_client.workbooks.update( parsed_args.definition.read() ) return format(workbook) class GetDefinition(command.Command): """Show workbook definition.""" def get_parser(self, prog_name): parser = super(GetDefinition, self).get_parser(prog_name) parser.add_argument('name', help='Workbook name') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine definition = mistral_client.workbooks.get(parsed_args.name).definition self.app.stdout.write(definition or "\n") class Validate(command.ShowOne): """Validate workbook.""" def _format(self, result=None): columns = ('Valid', 'Error') if result: data = (result.get('valid'), result.get('error'),) else: data = (tuple('' for _ in range(len(columns))),) return columns, data def get_parser(self, prog_name): parser = super(Validate, self).get_parser(prog_name) parser.add_argument( 'definition', type=argparse.FileType('r'), help='Workbook definition file' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine result = mistral_client.workbooks.validate( parsed_args.definition.read() ) return self._format(result) python-mistralclient-3.3.0/mistralclient/commands/v2/__init__.py0000666000175100017510000000000013241060623025032 0ustar zuulzuul00000000000000python-mistralclient-3.3.0/mistralclient/commands/v2/action_executions.py0000666000175100017510000002257213241060623027040 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2016 - Brocade Communications Systems, 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 logging from osc_lib.command import command from mistralclient.commands.v2 import base from mistralclient import utils LOG = logging.getLogger(__name__) def format_list(action_ex=None): columns = ( 'ID', 'Name', 'Workflow name', 'Workflow namespace', 'Task name', 'Task ID', 'State', 'Accepted', 'Created at', 'Updated at' ) if action_ex: data = ( action_ex.id, action_ex.name, action_ex.workflow_name, action_ex.workflow_namespace, action_ex.task_name if hasattr(action_ex, 'task_name') else None, action_ex.task_execution_id, action_ex.state, action_ex.accepted, action_ex.created_at, action_ex.updated_at or '' ) else: data = (tuple('' for _ in range(len(columns))),) return columns, data def format(action_ex=None): columns = ( 'ID', 'Name', 'Workflow name', 'Workflow namespace', 'Task name', 'Task ID', 'State', 'State info', 'Accepted', 'Created at', 'Updated at', ) if action_ex: data = ( action_ex.id, action_ex.name, action_ex.workflow_name, action_ex.workflow_namespace, action_ex.task_name if hasattr(action_ex, 'task_name') else None, action_ex.task_execution_id, action_ex.state, action_ex.state_info, action_ex.accepted, action_ex.created_at, action_ex.updated_at or '' ) else: data = (tuple('' for _ in range(len(columns))),) return columns, data class Create(command.ShowOne): """Create new Action execution or just run specific action.""" def produce_output(self, parsed_args, column_names, data): if not column_names: return 0 return super(Create, self).produce_output( parsed_args, column_names, data ) def get_parser(self, prog_name): parser = super(Create, self).get_parser(prog_name) parser.add_argument( 'name', help='Action name to execute.' ) parser.add_argument( dest='input', nargs='?', help='Action input.' ) parser.add_argument( '-s', '--save-result', dest='save_result', action='store_true', help='Save the result into DB.' ) parser.add_argument( '--run-sync', dest='run_sync', action='store_true', help='Run the action synchronously.' ) parser.add_argument( '-t', '--target', dest='target', help='Action will be executed on executor.' ) return parser def take_action(self, parsed_args): params = {} if parsed_args.save_result: params['save_result'] = parsed_args.save_result if parsed_args.run_sync: params['run_sync'] = parsed_args.run_sync if parsed_args.target: params['target'] = parsed_args.target action_input = None if parsed_args.input: action_input = utils.load_json(parsed_args.input) mistral_client = self.app.client_manager.workflow_engine action_ex = mistral_client.action_executions.create( parsed_args.name, action_input, **params ) if not parsed_args.run_sync and parsed_args.save_result: return format(action_ex) else: self.app.stdout.write("%s\n" % action_ex.output) return None, None class List(base.MistralLister): """List all Action executions.""" def _get_format_function(self): return format_list def get_parser(self, prog_name): parser = super(List, self).get_parser(prog_name) parser.add_argument( 'task_execution_id', nargs='?', help='Task execution ID.' ) parser.add_argument( '--limit', type=int, help='Maximum number of action-executions to return in a single ' 'result. limit is set to %s by default. Use --limit -1 to ' 'fetch the full result set.' % base.DEFAULT_LIMIT, nargs='?' ) return parser def _get_resources(self, parsed_args): if parsed_args.limit is None: parsed_args.limit = base.DEFAULT_LIMIT LOG.info( "limit is set to %s by default. Set " "the limit explicitly using \'--limit\', if required. " "Use \'--limit\' -1 to fetch the full result set.", base.DEFAULT_LIMIT ) mistral_client = self.app.client_manager.workflow_engine return mistral_client.action_executions.list( parsed_args.task_execution_id, limit=parsed_args.limit, ) class Get(command.ShowOne): """Show specific Action execution.""" def get_parser(self, prog_name): parser = super(Get, self).get_parser(prog_name) parser.add_argument('action_execution', help='Action execution ID.') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine execution = mistral_client.action_executions.get( parsed_args.action_execution ) return format(execution) class Update(command.ShowOne): """Update specific Action execution.""" def get_parser(self, prog_name): parser = super(Update, self).get_parser(prog_name) parser.add_argument( 'id', help='Action execution ID.') parser.add_argument( '--state', dest='state', choices=['IDLE', 'RUNNING', 'SUCCESS', 'ERROR', 'CANCELLED'], help='Action execution state') parser.add_argument( '--output', dest='output', help='Action execution output') return parser def take_action(self, parsed_args): output = None if parsed_args.output: output = utils.load_json(parsed_args.output) mistral_client = self.app.client_manager.workflow_engine execution = mistral_client.action_executions.update( parsed_args.id, parsed_args.state, output ) return format(execution) class GetOutput(command.Command): """Show Action execution output data.""" def get_parser(self, prog_name): parser = super(GetOutput, self).get_parser(prog_name) parser.add_argument( 'id', help='Action execution ID.') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine output = mistral_client.action_executions.get(parsed_args.id).output try: output = json.loads(output) output = json.dumps(output, indent=4) + "\n" except Exception: LOG.debug("Task result is not JSON.") self.app.stdout.write(output or "\n") class GetInput(command.Command): """Show Action execution input data.""" def get_parser(self, prog_name): parser = super(GetInput, self).get_parser(prog_name) parser.add_argument( 'id', help='Action execution ID.' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine result = mistral_client.action_executions.get(parsed_args.id).input try: result = json.loads(result) result = json.dumps(result, indent=4) + "\n" except Exception: LOG.debug("Task result is not JSON.") self.app.stdout.write(result or "\n") class Delete(command.Command): """Delete action execution.""" def get_parser(self, prog_name): parser = super(Delete, self).get_parser(prog_name) parser.add_argument( 'action_execution', nargs='+', help='Id of action execution identifier(s).' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine utils.do_action_on_many( lambda s: mistral_client.action_executions.delete(s), parsed_args.action_execution, "Request to delete action execution %s has been accepted.", "Unable to delete the specified action execution(s)." ) python-mistralclient-3.3.0/mistralclient/commands/v2/members.py0000666000175100017510000001436513241060623024750 0ustar zuulzuul00000000000000# Copyright 2016 - Catalyst IT Limited # # 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 osc_lib.command import command from mistralclient.commands.v2 import base from mistralclient import exceptions def format_list(member=None): return format(member, lister=True) def format(member=None, lister=False): columns = ( 'Resource ID', 'Resource Type', 'Resource Owner', 'Member ID', 'Status', 'Created at', 'Updated at' ) if member: data = ( member.resource_id, member.resource_type, member.project_id, member.member_id, member.status, member.created_at, ) if hasattr(member, 'updated_at'): data += (member.updated_at,) else: data += (None,) else: data = (tuple('' for _ in range(len(columns))),) return columns, data class List(base.MistralLister): """List all members.""" def _get_format_function(self): return format_list def get_parser(self, parsed_args): parser = super(List, self).get_parser(parsed_args) parser.add_argument( 'resource_id', help='Resource id to be shared.' ) parser.add_argument( 'resource_type', help='Resource type.' ) return parser def _get_resources(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine return mistral_client.members.list( parsed_args.resource_id, parsed_args.resource_type ) class Get(command.ShowOne): """Show specific member information.""" def get_parser(self, prog_name): parser = super(Get, self).get_parser(prog_name) parser.add_argument( 'resource', help='Resource ID to be shared.' ) parser.add_argument( 'resource_type', help='Resource type.' ) parser.add_argument( '-m', '--member-id', default='', help='Project ID to whom the resource is shared to. No need to ' 'provide this param if you are the resource member.' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine member = mistral_client.members.get( parsed_args.resource, parsed_args.resource_type, parsed_args.member_id, ) return format(member) class Create(command.ShowOne): """Shares a resource to another tenant.""" def get_parser(self, prog_name): parser = super(Create, self).get_parser(prog_name) parser.add_argument( 'resource_id', help='Resource ID to be shared.' ) parser.add_argument( 'resource_type', help='Resource type.' ) parser.add_argument( 'member_id', help='Project ID to whom the resource is shared to.' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine member = mistral_client.members.create( parsed_args.resource_id, parsed_args.resource_type, parsed_args.member_id, ) return format(member) class Delete(command.Command): """Delete a resource sharing relationship.""" def get_parser(self, prog_name): parser = super(Delete, self).get_parser(prog_name) parser.add_argument( 'resource', help='Resource ID to be shared.' ) parser.add_argument( 'resource_type', help='Resource type.' ) parser.add_argument( 'member_id', help='Project ID to whom the resource is shared to.' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine try: mistral_client.members.delete( parsed_args.resource, parsed_args.resource_type, parsed_args.member_id, ) print( "Request to delete %s member %s has been accepted." % (parsed_args.resource_type, parsed_args.member_id) ) except Exception as e: print(e) error_msg = "Unable to delete the specified member." raise exceptions.MistralClientException(error_msg) class Update(command.ShowOne): """Update resource sharing status.""" def get_parser(self, prog_name): parser = super(Update, self).get_parser(prog_name) parser.add_argument( 'resource_id', help='Resource ID to be shared.' ) parser.add_argument( 'resource_type', help='Resource type.' ) parser.add_argument( '-m', '--member-id', default='', help='Project ID to whom the resource is shared to. No need to ' 'provide this param if you are the resource member.' ) parser.add_argument( '-s', '--status', default='accepted', choices=['pending', 'accepted', 'rejected'], help='status of the sharing.' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine member = mistral_client.members.update( parsed_args.resource_id, parsed_args.resource_type, parsed_args.member_id, status=parsed_args.status ) return format(member) python-mistralclient-3.3.0/mistralclient/commands/v2/executions.py0000666000175100017510000002432213241060623025476 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, Inc. # Copyright 2016 - Brocade Communications Systems, Inc. # # 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 json import logging import os.path from osc_lib.command import command from mistralclient.commands.v2 import base from mistralclient import utils LOG = logging.getLogger(__name__) def format_list(execution=None): return format(execution, lister=True) def format(execution=None, lister=False): columns = ( 'ID', 'Workflow ID', 'Workflow name', 'Workflow namespace', 'Description', 'Task Execution ID', 'State', 'State info', 'Created at', 'Updated at' ) # TODO(nmakhotkin) Add parent task id when it's implemented in API. if execution: state_info = (execution.state_info if not lister else base.cut(execution.state_info)) data = ( execution.id, execution.workflow_id, execution.workflow_name, execution.workflow_namespace, execution.description, execution.task_execution_id or '', execution.state, state_info, execution.created_at, execution.updated_at or '' ) else: data = (tuple('' for _ in range(len(columns))),) return columns, data class List(base.MistralLister): """List all executions.""" def _get_format_function(self): return format_list def get_parser(self, parsed_args): parser = super(List, self).get_parser(parsed_args) parser.add_argument( '--task', nargs='?', help="Parent task execution ID associated with workflow " "execution list.", ) parser.add_argument( '--marker', type=str, help='The last execution uuid of the previous page, displays list ' 'of executions after "marker".', default='', nargs='?' ) parser.add_argument( '--limit', type=int, help='Maximum number of executions to return in a single result. ' 'limit is set to %s by default. Use --limit -1 to fetch the ' 'full result set.' % base.DEFAULT_LIMIT, nargs='?' ) parser.add_argument( '--sort_keys', help='Comma-separated list of sort keys to sort results by. ' 'Default: created_at. ' 'Example: mistral execution-list --sort_keys=id,description', default='created_at', nargs='?' ) parser.add_argument( '--sort_dirs', help='Comma-separated list of sort directions. Default: asc. ' 'Example: mistral execution-list --sort_keys=id,description ' '--sort_dirs=asc,desc', default='asc', nargs='?' ) parser.add_argument( '--filter', dest='filters', action='append', help='Filters. Can be repeated.' ) return parser def _get_resources(self, parsed_args): if parsed_args.limit is None: parsed_args.limit = base.DEFAULT_LIMIT LOG.info( "limit is set to %s by default. Set " "the limit explicitly using \'--limit\', if required. " "Use \'--limit\' -1 to fetch the full result set.", base.DEFAULT_LIMIT ) mistral_client = self.app.client_manager.workflow_engine return mistral_client.executions.list( task=parsed_args.task, marker=parsed_args.marker, limit=parsed_args.limit, sort_keys=parsed_args.sort_keys, sort_dirs=parsed_args.sort_dirs, **base.get_filters(parsed_args) ) class Get(command.ShowOne): """Show specific execution.""" def get_parser(self, prog_name): parser = super(Get, self).get_parser(prog_name) parser.add_argument('execution', help='Execution identifier') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine execution = mistral_client.executions.get(parsed_args.execution) return format(execution) class Create(command.ShowOne): """Create new execution.""" def get_parser(self, prog_name): parser = super(Create, self).get_parser(prog_name) parser.add_argument( 'workflow_identifier', nargs='?', help='Workflow ID or name. Workflow name will be deprecated since ' 'Mitaka.' ) parser.add_argument( '--namespace', nargs='?', default='', help="Workflow namespace." ) parser.add_argument( 'workflow_input', nargs='?', help='Workflow input' ) parser.add_argument( 'params', nargs='?', help='Workflow additional parameters' ) parser.add_argument( '-d', '--description', dest='description', default='', help='Execution description' ) parser.add_argument( '-s', dest='source_execution_id', nargs='?', help="Workflow Execution id which will allow operators to create " "a new workflow execution based on the previously successful " "executed workflow. Example: mistral execution-create -s " "123e4567-e89b-12d3-a456-426655440000") return parser def take_action(self, parsed_args): if parsed_args.workflow_input: wf_input = utils.load_json(parsed_args.workflow_input) else: wf_input = {} if parsed_args.params: params = utils.load_json(parsed_args.params) else: params = {} mistral_client = self.app.client_manager.workflow_engine execution = mistral_client.executions.create( parsed_args.workflow_identifier, parsed_args.namespace, wf_input, parsed_args.description, parsed_args.source_execution_id, **params ) return format(execution) class Delete(command.Command): """Delete execution.""" def get_parser(self, prog_name): parser = super(Delete, self).get_parser(prog_name) parser.add_argument( 'execution', nargs='+', help='Id of execution identifier(s).' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine utils.do_action_on_many( lambda s: mistral_client.executions.delete(s), parsed_args.execution, "Request to delete execution %s has been accepted.", "Unable to delete the specified execution(s)." ) class Update(command.ShowOne): """Update execution.""" def get_parser(self, prog_name): parser = super(Update, self).get_parser(prog_name) parser.add_argument( 'id', help='Execution identifier' ) parser.add_argument( '-s', '--state', dest='state', choices=['RUNNING', 'PAUSED', 'SUCCESS', 'ERROR', 'CANCELLED'], help='Execution state' ) parser.add_argument( '-e', '--env', dest='env', help='Environment variables' ) parser.add_argument( '-d', '--description', dest='description', help='Execution description' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine env = ( utils.load_file(parsed_args.env) if parsed_args.env and os.path.isfile(parsed_args.env) else utils.load_content(parsed_args.env) ) execution = mistral_client.executions.update( parsed_args.id, parsed_args.state, description=parsed_args.description, env=env ) return format(execution) class GetInput(command.Command): """Show execution input data.""" def get_parser(self, prog_name): parser = super(GetInput, self).get_parser(prog_name) parser.add_argument('id', help='Execution ID') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine ex_input = mistral_client.executions.get(parsed_args.id).input try: ex_input = json.loads(ex_input) ex_input = json.dumps(ex_input, indent=4) + "\n" except Exception: LOG.debug("Execution input is not JSON.") self.app.stdout.write(ex_input or "\n") class GetOutput(command.Command): """Show execution output data.""" def get_parser(self, prog_name): parser = super(GetOutput, self).get_parser(prog_name) parser.add_argument('id', help='Execution ID') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine output = mistral_client.executions.get(parsed_args.id).output try: output = json.loads(output) output = json.dumps(output, indent=4) + "\n" except Exception: LOG.debug("Execution output is not JSON.") self.app.stdout.write(output or "\n") python-mistralclient-3.3.0/mistralclient/commands/v2/cron_triggers.py0000666000175100017510000001416213241060623026160 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # 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 datetime import time from osc_lib.command import command from mistralclient.commands.v2 import base from mistralclient import utils def format_list(trigger=None): return format(trigger, lister=True) def format(trigger=None, lister=False): columns = ( 'Name', 'Workflow', 'Params', 'Pattern', # TODO(rakhmerov): Uncomment when passwords are handled properly. # TODO(rakhmerov): Add 'Workflow input' column. 'Next execution time', 'Remaining executions', 'Created at', 'Updated at' ) if trigger: # TODO(rakhmerov): Add following here: # TODO(rakhmerov): wf_input = trigger.workflow_input if not lister # TODO(rakhmerov:): else base.cut(trigger.workflow_input) data = ( trigger.name, trigger.workflow_name, trigger.workflow_params, trigger.pattern, # TODO(rakhmerov): Uncomment when passwords are handled properly. # TODo(rakhmerov): Add 'wf_input' here. trigger.next_execution_time, trigger.remaining_executions, trigger.created_at, ) if hasattr(trigger, 'updated_at'): data += (trigger.updated_at,) else: data += (None,) else: data = (tuple('' for _ in range(len(columns))),) return columns, data class List(base.MistralLister): """List all cron triggers.""" def _get_format_function(self): return format_list def _get_resources(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine return mistral_client.cron_triggers.list() class Get(command.ShowOne): """Show specific cron trigger.""" def get_parser(self, prog_name): parser = super(Get, self).get_parser(prog_name) parser.add_argument('cron_trigger', help='Cron trigger name') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine return format(mistral_client.cron_triggers.get( parsed_args.cron_trigger )) class Create(command.ShowOne): """Create new trigger.""" def get_parser(self, prog_name): parser = super(Create, self).get_parser(prog_name) parser.add_argument('name', help='Cron trigger name') parser.add_argument('workflow_identifier', help='Workflow name or ID') parser.add_argument( 'workflow_input', nargs='?', help='Workflow input' ) parser.add_argument( '--params', help='Workflow params', ) parser.add_argument( '--pattern', type=str, help='Cron trigger pattern', metavar='<* * * * *>' ) parser.add_argument( '--first-time', type=str, default=None, help=("Date and time of the first execution. Time is treated as " "local time unless --utc is also specified"), metavar='' ) parser.add_argument( '--count', type=int, help="Number of wanted executions", metavar='' ) parser.add_argument( '--utc', action='store_true', help="All times specified should be treated as UTC" ) return parser @staticmethod def _get_file_content_or_dict(string): if string: return utils.load_json(string) else: return {} @staticmethod def _convert_time_string_to_utc(time_string): datetime_format = '%Y-%m-%d %H:%M' the_time = time_string if the_time: the_time = datetime.datetime.strptime( the_time, datetime_format) is_dst = time.daylight and time.localtime().tm_isdst > 0 utc_offset = - (time.altzone if is_dst else time.timezone) the_time = (the_time - datetime.timedelta( 0, utc_offset)).strftime(datetime_format) return the_time def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine wf_input = self._get_file_content_or_dict(parsed_args.workflow_input) wf_params = self._get_file_content_or_dict(parsed_args.params) first_time = parsed_args.first_time if not parsed_args.utc: first_time = self._convert_time_string_to_utc( parsed_args.first_time) trigger = mistral_client.cron_triggers.create( parsed_args.name, parsed_args.workflow_identifier, wf_input, wf_params, parsed_args.pattern, first_time, parsed_args.count ) return format(trigger) class Delete(command.Command): """Delete trigger.""" def get_parser(self, prog_name): parser = super(Delete, self).get_parser(prog_name) parser.add_argument( 'cron_trigger', nargs='+', help='Name of cron trigger(s).' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine utils.do_action_on_many( lambda s: mistral_client.cron_triggers.delete(s), parsed_args.cron_trigger, "Request to delete cron trigger %s has been accepted.", "Unable to delete the specified cron trigger(s)." ) python-mistralclient-3.3.0/mistralclient/commands/v2/tasks.py0000666000175100017510000001530113241060623024432 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, Inc. # 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 json import logging import os.path from osc_lib.command import command from mistralclient.commands.v2 import base from mistralclient import utils LOG = logging.getLogger(__name__) class TaskFormatter(object): COLUMNS = [ ('id', 'ID'), ('name', 'Name'), ('workflow_name', 'Workflow name'), ('workflow_namespace', 'Workflow namespace'), ('workflow_execution_id', 'Execution ID'), ('state', 'State'), ('state_info', 'State info'), ('created_at', 'Created at'), ('updated_at', 'Updated at'), ] COLUMN_FIELD_NAMES = list(zip(*COLUMNS))[0] COLUMN_HEADING_NAMES = list(zip(*COLUMNS))[1] @staticmethod def format_list(task=None): return TaskFormatter.format(task, lister=True) @staticmethod def format(task=None, lister=False): if task: state_info = (task.state_info if not lister else base.cut(task.state_info)) data = ( task.id, task.name, task.workflow_name, task.workflow_namespace, task.workflow_execution_id, task.state, state_info, task.created_at, task.updated_at or '' ) else: data = (tuple('' for _ in range(len(TaskFormatter.COLUMNS))),) return TaskFormatter.COLUMN_HEADING_NAMES, data class List(base.MistralLister): """List all tasks.""" def get_parser(self, prog_name): parser = super(List, self).get_parser(prog_name) parser.add_argument( 'workflow_execution', nargs='?', help='Workflow execution ID associated with list of Tasks.' ) parser.add_argument( '--filter', dest='filters', action='append', help='Filters. Can be repeated.' ) parser.add_argument( '--limit', type=int, help='Maximum number of tasks to return in a single result. ' 'limit is set to %s by default. Use --limit -1 to fetch the ' 'full result set.' % base.DEFAULT_LIMIT, nargs='?' ) return parser def _get_format_function(self): return TaskFormatter.format_list def _get_resources(self, parsed_args): if parsed_args.limit is None: parsed_args.limit = base.DEFAULT_LIMIT LOG.info( "limit is set to %s by default. Set " "the limit explicitly using \'--limit\', if required. " "Use \'--limit\' -1 to fetch the full result set.", base.DEFAULT_LIMIT ) mistral_client = self.app.client_manager.workflow_engine return mistral_client.tasks.list( parsed_args.workflow_execution, limit=parsed_args.limit, fields=TaskFormatter.COLUMN_FIELD_NAMES, **base.get_filters(parsed_args) ) class Get(command.ShowOne): """Show specific task.""" def get_parser(self, prog_name): parser = super(Get, self).get_parser(prog_name) parser.add_argument('task', help='Task identifier') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine execution = mistral_client.tasks.get(parsed_args.task) return TaskFormatter.format(execution) class GetResult(command.Command): """Show task output data.""" def get_parser(self, prog_name): parser = super(GetResult, self).get_parser(prog_name) parser.add_argument( 'id', help='Task ID') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine result = mistral_client.tasks.get(parsed_args.id).result try: result = json.loads(result) result = json.dumps(result, indent=4) + "\n" except Exception: LOG.debug("Task result is not JSON.") self.app.stdout.write(result or "\n") class GetPublished(command.Command): """Show task published variables.""" def get_parser(self, prog_name): parser = super(GetPublished, self).get_parser(prog_name) parser.add_argument( 'id', help='Task ID') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine result = mistral_client.tasks.get(parsed_args.id).published try: result = json.loads(result) result = json.dumps(result, indent=4) + "\n" except Exception: LOG.debug("Task result is not JSON.") self.app.stdout.write(result or "\n") class Rerun(command.ShowOne): """Rerun an existing task.""" def get_parser(self, prog_name): parser = super(Rerun, self).get_parser(prog_name) parser.add_argument( 'id', help='Task identifier' ) parser.add_argument( '--resume', action='store_true', dest='resume', default=False, help=('rerun only failed or unstarted action ' 'executions for with-items task') ) parser.add_argument( '-e', '--env', dest='env', help='Environment variables' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine env = ( utils.load_file(parsed_args.env) if parsed_args.env and os.path.isfile(parsed_args.env) else utils.load_content(parsed_args.env) ) execution = mistral_client.tasks.rerun( parsed_args.id, reset=(not parsed_args.resume), env=env ) return TaskFormatter.format(execution) python-mistralclient-3.3.0/mistralclient/commands/v2/workflows.py0000666000175100017510000001662113241060623025350 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import argparse from cliff import command from cliff import show from mistralclient.commands.v2 import base from mistralclient import utils def format_list(workflow=None): return format(workflow, lister=True) def format(workflow=None, lister=False): columns = ( 'ID', 'Name', 'Namespace', 'Project ID', 'Tags', 'Input', 'Created at', 'Updated at' ) if workflow: tags = getattr(workflow, 'tags', None) or [] data = ( workflow.id, workflow.name, workflow.namespace, workflow.project_id, base.wrap(', '.join(tags)) or '', workflow.input if not lister else base.cut(workflow.input), workflow.created_at ) if hasattr(workflow, 'updated_at'): data += (workflow.updated_at,) else: data += (None,) else: data = (tuple('' for _ in range(len(columns))),) return columns, data class List(base.MistralLister): """List all workflows.""" def _get_format_function(self): return format_list def get_parser(self, prog_name): parser = super(List, self).get_parser(prog_name) parser.add_argument( '--filter', dest='filters', action='append', help='Filters. Can be repeated.' ) return parser def _get_resources(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine return mistral_client.workflows.list( **base.get_filters(parsed_args) ) class Get(show.ShowOne): """Show specific workflow.""" def get_parser(self, prog_name): parser = super(Get, self).get_parser(prog_name) parser.add_argument('workflow', help='Workflow ID or name.') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine wf = mistral_client.workflows.get(parsed_args.workflow) return format(wf) class Create(base.MistralLister): """Create new workflow.""" def get_parser(self, prog_name): parser = super(Create, self).get_parser(prog_name) parser.add_argument( 'definition', type=argparse.FileType('r'), help='Workflow definition file.' ) parser.add_argument( '--namespace', nargs='?', default='', help="Namespace to create the workflow within.", ) parser.add_argument( '--public', action='store_true', help='With this flag workflow will be marked as "public".' ) return parser def _get_format_function(self): return format_list def _validate_parsed_args(self, parsed_args): if not parsed_args.definition: raise RuntimeError("You must provide path to workflow " "definition file.") def _get_resources(self, parsed_args): scope = 'public' if parsed_args.public else 'private' mistral_client = self.app.client_manager.workflow_engine return mistral_client.workflows.create( parsed_args.definition.read(), namespace=parsed_args.namespace, scope=scope ) class Delete(command.Command): """Delete workflow.""" def get_parser(self, prog_name): parser = super(Delete, self).get_parser(prog_name) parser.add_argument( 'workflow', nargs='+', help='Name or ID of workflow(s).' ) parser.add_argument( '--namespace', nargs='?', default=None, help="Parent task execution ID associated with workflow " "execution list.", ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine utils.do_action_on_many( lambda s: mistral_client.workflows.delete(s, parsed_args.namespace), parsed_args.workflow, "Request to delete workflow %s has been accepted.", "Unable to delete the specified workflow(s)." ) class Update(base.MistralLister): """Update workflow.""" def get_parser(self, prog_name): parser = super(Update, self).get_parser(prog_name) parser.add_argument( 'definition', type=argparse.FileType('r'), help='Workflow definition' ) parser.add_argument('--id', help='Workflow ID.') parser.add_argument( '--namespace', nargs='?', default='', help="Parent task execution ID associated with workflow " "execution list.", ) parser.add_argument( '--public', action='store_true', help='With this flag workflow will be marked as "public".' ) return parser def _get_format_function(self): return format_list def _get_resources(self, parsed_args): scope = 'public' if parsed_args.public else 'private' mistral_client = self.app.client_manager.workflow_engine return mistral_client.workflows.update( parsed_args.definition.read(), scope=scope, id=parsed_args.id, namespace=parsed_args.namespace ) class GetDefinition(command.Command): """Show workflow definition.""" def get_parser(self, prog_name): parser = super(GetDefinition, self).get_parser(prog_name) parser.add_argument('identifier', help='Workflow ID or name.') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine wf = mistral_client.workflows.get(parsed_args.identifier) self.app.stdout.write(wf.definition or "\n") class Validate(show.ShowOne): """Validate workflow.""" def _format(self, result=None): columns = ('Valid', 'Error') if result: data = (result.get('valid'), result.get('error'),) else: data = (tuple('' for _ in range(len(columns))),) return columns, data def get_parser(self, prog_name): parser = super(Validate, self).get_parser(prog_name) parser.add_argument( 'definition', type=argparse.FileType('r'), help='Workflow definition file' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine result = mistral_client.workflows.validate( parsed_args.definition.read() ) return self._format(result) python-mistralclient-3.3.0/mistralclient/commands/v2/base.py0000666000175100017510000000406613241060623024225 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # 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 abc import textwrap from osc_lib.command import command import six DEFAULT_LIMIT = 100 @six.add_metaclass(abc.ABCMeta) class MistralLister(command.Lister): @abc.abstractmethod def _get_format_function(self): raise NotImplementedError @abc.abstractmethod def _get_resources(self, parsed_args): """Gets a list of API resources (e.g. using client).""" raise NotImplementedError def _validate_parsed_args(self, parsed_args): # No-op by default. pass def take_action(self, parsed_args): self._validate_parsed_args(parsed_args) f = self._get_format_function() ret = self._get_resources(parsed_args) if not isinstance(ret, list): ret = [ret] data = [f(r)[1] for r in ret] if data: return f()[0], data else: return f() def cut(string, length=25): if string and len(string) > length: return "%s..." % string[:length] else: return string def wrap(string, width=25): if string and len(string) > width: return textwrap.fill(string, width) else: return string def get_filters(parsed_args): filters = {} if parsed_args.filters: for f in parsed_args.filters: arr = f.split('=') if len(arr) != 2: raise ValueError('Invalid filter: %s' % f) filters[arr[0]] = arr[1] return filters python-mistralclient-3.3.0/mistralclient/commands/v2/services.py0000666000175100017510000000221413241060623025127 0ustar zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from mistralclient.commands.v2 import base def format_list(service=None): columns = ('Name', 'Type') if service: data = (service.name, service.type) else: data = (tuple('' for _ in range(len(columns))),) return columns, data class List(base.MistralLister): """List all services.""" def _get_format_function(self): return format_list def _get_resources(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine return mistral_client.services.list() python-mistralclient-3.3.0/mistralclient/commands/v2/environments.py0000666000175100017510000001135213241060623026036 0ustar zuulzuul00000000000000# Copyright 2015 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import argparse import json from osc_lib.command import command from mistralclient.commands.v2 import base from mistralclient import utils def format_list(environment=None): columns = ( 'Name', 'Description', 'Scope', 'Created at', 'Updated at' ) if environment: data = ( environment.name, environment.description, environment.scope, environment.created_at, ) if hasattr(environment, 'updated_at'): data += (environment.updated_at or '',) else: data += (None,) else: data = (tuple('' for _ in range(len(columns))),) return columns, data def format(environment=None): columns = ( 'Name', 'Description', 'Variables', 'Scope', 'Created at', 'Updated at' ) if environment: data = (environment.name,) if hasattr(environment, 'description'): data += (environment.description or '',) else: data += (None,) data += ( json.dumps(environment.variables, indent=4), environment.scope, environment.created_at, ) if hasattr(environment, 'updated_at'): data += (environment.updated_at or '',) else: data += (None,) else: data = (tuple('' for _ in range(len(columns))),) return columns, data class List(base.MistralLister): """List all environments.""" def _get_format_function(self): return format_list def _get_resources(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine return mistral_client.environments.list() class Get(command.ShowOne): """Show specific environment.""" def get_parser(self, prog_name): parser = super(Get, self).get_parser(prog_name) parser.add_argument( 'environment', help='Environment name' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine environment = mistral_client.environments.get(parsed_args.environment) return format(environment) class Create(command.ShowOne): """Create new environment.""" def get_parser(self, prog_name): parser = super(Create, self).get_parser(prog_name) parser.add_argument( 'file', type=argparse.FileType('r'), help='Environment configuration file in JSON or YAML' ) return parser def take_action(self, parsed_args): data = utils.load_content(parsed_args.file.read()) mistral_client = self.app.client_manager.workflow_engine environment = mistral_client.environments.create(**data) return format(environment) class Delete(command.Command): """Delete environment.""" def get_parser(self, prog_name): parser = super(Delete, self).get_parser(prog_name) parser.add_argument( 'environment', nargs='+', help='Name of environment(s).' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine utils.do_action_on_many( lambda s: mistral_client.environments.delete(s), parsed_args.environment, "Request to delete environment %s has been accepted.", "Unable to delete the specified environment(s)." ) class Update(command.ShowOne): """Update environment.""" def get_parser(self, prog_name): parser = super(Update, self).get_parser(prog_name) parser.add_argument( 'file', type=argparse.FileType('r'), help='Environment configuration file in JSON or YAML' ) return parser def take_action(self, parsed_args): data = utils.load_content(parsed_args.file.read()) mistral_client = self.app.client_manager.workflow_engine environment = mistral_client.environments.update(**data) return format(environment) python-mistralclient-3.3.0/mistralclient/commands/v2/event_triggers.py0000666000175100017510000001106113241060623026333 0ustar zuulzuul00000000000000# Copyright 2017, OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # from osc_lib.command import command from mistralclient.commands.v2 import base from mistralclient import utils def format_list(trigger=None): return format(trigger, lister=True) def format(trigger=None, lister=False): columns = ( 'ID', 'Name', 'Workflow ID', 'Params', 'Exchange', 'Topic', 'Event', 'Created at', 'Updated at' ) if trigger: data = ( trigger.id, trigger.name, trigger.workflow_id, trigger.workflow_params, trigger.exchange, trigger.topic, trigger.event, trigger.created_at, ) if hasattr(trigger, 'updated_at'): data += (trigger.updated_at,) else: data += (None,) else: data = (tuple('' for _ in range(len(columns))),) return columns, data class List(base.MistralLister): """List all event triggers.""" def _get_format_function(self): return format_list def _get_resources(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine return mistral_client.event_triggers.list() class Get(command.ShowOne): """Show specific event trigger.""" def get_parser(self, prog_name): parser = super(Get, self).get_parser(prog_name) parser.add_argument('event_trigger', help='Event trigger ID') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine return format(mistral_client.event_triggers.get( parsed_args.event_trigger )) class Create(command.ShowOne): """Create new trigger.""" def get_parser(self, prog_name): parser = super(Create, self).get_parser(prog_name) parser.add_argument('name', help='Event trigger name') parser.add_argument('workflow_id', help='Workflow ID') parser.add_argument('exchange', type=str, help='Event trigger exchange') parser.add_argument('topic', type=str, help='Event trigger topic') parser.add_argument('event', type=str, help='Event trigger event name') parser.add_argument('workflow_input', nargs='?', help='Workflow input') parser.add_argument('--params', help='Workflow params') return parser @staticmethod def _get_json_string_or_dict(string): if string: return utils.load_json(string) else: return {} def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine wf_input = self._get_json_string_or_dict(parsed_args.workflow_input) wf_params = self._get_json_string_or_dict(parsed_args.params) trigger = mistral_client.event_triggers.create( parsed_args.name, parsed_args.workflow_id, parsed_args.exchange, parsed_args.topic, parsed_args.event, wf_input, wf_params, ) return format(trigger) class Delete(command.Command): """Delete trigger.""" def get_parser(self, prog_name): parser = super(Delete, self).get_parser(prog_name) parser.add_argument( 'event_trigger_id', nargs='+', help='ID of event trigger(s).' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine utils.do_action_on_many( lambda s: mistral_client.event_triggers.delete(s), parsed_args.event_trigger_id, "Request to delete event trigger %s has been accepted.", "Unable to delete the specified event trigger(s)." ) python-mistralclient-3.3.0/mistralclient/commands/v2/actions.py0000666000175100017510000001513713241060623024754 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import argparse from osc_lib.command import command from mistralclient.commands.v2 import base from mistralclient import utils def format_list(action=None): return format(action, lister=True) def format(action=None, lister=False): columns = ( 'ID', 'Name', 'Is system', 'Input', 'Description', 'Tags', 'Created at', 'Updated at' ) if action: tags = getattr(action, 'tags', None) or [] input = action.input if not lister else base.cut(action.input) desc = (action.description if not lister else base.cut(action.description)) data = ( action.id, action.name, action.is_system, input, desc, base.wrap(', '.join(tags)) or '', action.created_at, ) if hasattr(action, 'updated_at'): data += (action.updated_at,) else: data += (None,) else: data = (tuple('' for _ in range(len(columns))),) return columns, data class List(base.MistralLister): """List all actions.""" def _get_format_function(self): return format_list def get_parser(self, prog_name): parser = super(List, self).get_parser(prog_name) parser.add_argument( '--filter', dest='filters', action='append', help='Filters. Can be repeated.' ) return parser def _get_resources(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine return mistral_client.actions.list( **base.get_filters(parsed_args) ) class Get(command.ShowOne): """Show specific action.""" def get_parser(self, prog_name): parser = super(Get, self).get_parser(prog_name) parser.add_argument('action', help='Action (name or ID)') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine action = mistral_client.actions.get(parsed_args.action) return format(action) class Create(base.MistralLister): """Create new action.""" def get_parser(self, prog_name): parser = super(Create, self).get_parser(prog_name) parser.add_argument( 'definition', type=argparse.FileType('r'), help='Action definition file' ) parser.add_argument( '--public', action='store_true', help='With this flag action will be marked as "public".' ) return parser def _validate_parsed_args(self, parsed_args): if not parsed_args.definition: raise RuntimeError("Provide action definition file.") def _get_format_function(self): return format_list def _get_resources(self, parsed_args): scope = 'public' if parsed_args.public else 'private' mistral_client = self.app.client_manager.workflow_engine return mistral_client.actions.create( parsed_args.definition.read(), scope=scope ) class Delete(command.Command): """Delete action.""" def get_parser(self, prog_name): parser = super(Delete, self).get_parser(prog_name) parser.add_argument( 'action', nargs='+', help='Name or ID of action(s).' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine utils.do_action_on_many( lambda s: mistral_client.actions.delete(s), parsed_args.action, "Request to delete action %s has been accepted.", "Unable to delete the specified action(s)." ) class Update(base.MistralLister): """Update action.""" def get_parser(self, prog_name): parser = super(Update, self).get_parser(prog_name) parser.add_argument( 'definition', type=argparse.FileType('r'), help='Action definition file' ) parser.add_argument('--id', help='Action ID.') parser.add_argument( '--public', action='store_true', help='With this flag action will be marked as "public".' ) return parser def _get_format_function(self): return format_list def _get_resources(self, parsed_args): scope = 'public' if parsed_args.public else 'private' mistral_client = self.app.client_manager.workflow_engine return mistral_client.actions.update( parsed_args.definition.read(), scope=scope, id=parsed_args.id ) class GetDefinition(command.Command): """Show action definition.""" def get_parser(self, prog_name): parser = super(GetDefinition, self).get_parser(prog_name) parser.add_argument('name', help='Action name') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine definition = mistral_client.actions.get(parsed_args.name).definition self.app.stdout.write(definition or "\n") class Validate(command.ShowOne): """Validate action.""" def _format(self, result=None): columns = ('Valid', 'Error') if result: data = (result.get('valid'), result.get('error')) else: data = (tuple('' for _ in range(len(columns))),) return columns, data def get_parser(self, prog_name): parser = super(Validate, self).get_parser(prog_name) parser.add_argument( 'definition', type=argparse.FileType('r'), help='action definition file' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine result = mistral_client.actions.validate( parsed_args.definition.read() ) return self._format(result) python-mistralclient-3.3.0/mistralclient/__init__.py0000666000175100017510000000125613241060623022720 0ustar zuulzuul00000000000000# Copyright 2014 Rackspace Hosting # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import pbr.version __version__ = pbr.version.VersionInfo( 'python-mistralclient').version_string() python-mistralclient-3.3.0/mistralclient/osc/0000775000175100017510000000000013241061116021363 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/mistralclient/osc/__init__.py0000666000175100017510000000000013241060623023466 0ustar zuulzuul00000000000000python-mistralclient-3.3.0/mistralclient/osc/plugin.py0000666000175100017510000000347013241060623023243 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. """OpenStackClient plugin for Workflow service.""" import logging from osc_lib import utils LOG = logging.getLogger(__name__) DEFAULT_WORKFLOW_API_VERSION = '2' API_VERSION_OPTION = 'os_workflow_api_version' API_NAME = 'workflow_engine' API_VERSIONS = { '2': 'mistralclient.api.v2.client.Client', } def make_client(instance): """Returns a workflow_engine service client.""" version = instance._api_version[API_NAME] workflow_client = utils.get_client_class( API_NAME, version, API_VERSIONS) LOG.debug('Instantiating workflow engine client: %s', workflow_client) mistral_url = instance.get_endpoint_for_service_type( 'workflowv2', interface='publicURL' ) client = workflow_client(mistral_url=mistral_url, session=instance.session) return client def build_option_parser(parser): """Hook to add global options.""" parser.add_argument( '--os-workflow-api-version', metavar='', default=utils.env( 'OS_WORKFLOW_API_VERSION', default=DEFAULT_WORKFLOW_API_VERSION), help='Workflow API version, default=' + DEFAULT_WORKFLOW_API_VERSION + ' (Env: OS_WORKFLOW_API_VERSION)') return parser python-mistralclient-3.3.0/mistralclient/exceptions.py0000666000175100017510000000250313241060623023336 0ustar zuulzuul00000000000000# Copyright 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. class MistralClientException(Exception): """Base Exception for Mistral client To correctly use this class, inherit from it and define a 'message' and 'code' properties. """ message = "An unknown exception occurred" code = "UNKNOWN_EXCEPTION" def __str__(self): return self.message def __init__(self, message=message): self.message = message super(MistralClientException, self).__init__( '%s: %s' % (self.code, self.message)) class IllegalArgumentException(MistralClientException): message = "IllegalArgumentException occurred" code = "ILLEGAL_ARGUMENT_EXCEPTION" def __init__(self, message=None): if message: self.message = message python-mistralclient-3.3.0/mistralclient/api/0000775000175100017510000000000013241061116021350 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/mistralclient/api/__init__.py0000666000175100017510000000000013241060623023453 0ustar zuulzuul00000000000000python-mistralclient-3.3.0/mistralclient/api/v2/0000775000175100017510000000000013241061116021677 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/mistralclient/api/v2/workbooks.py0000666000175100017510000000536713241060623024310 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, 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 mistralclient.api import base from mistralclient import utils class Workbook(base.Resource): resource_name = 'Workbook' class WorkbookManager(base.ResourceManager): resource_class = Workbook def create(self, definition): self._ensure_not_empty(definition=definition) # If the specified definition is actually a file, read in the # definition file definition = utils.get_contents_if_file(definition) resp = self.http_client.post( '/workbooks', definition, headers={'content-type': 'text/plain'} ) if resp.status_code != 201: self._raise_api_exception(resp) return self.resource_class(self, base.extract_json(resp, None)) def update(self, definition): self._ensure_not_empty(definition=definition) # If the specified definition is actually a file, read in the # definition file definition = utils.get_contents_if_file(definition) resp = self.http_client.put( '/workbooks', definition, headers={'content-type': 'text/plain'} ) if resp.status_code != 200: self._raise_api_exception(resp) return self.resource_class(self, base.extract_json(resp, None)) def list(self): return self._list('/workbooks', response_key='workbooks') def get(self, name): self._ensure_not_empty(name=name) return self._get('/workbooks/%s' % name) def delete(self, name): self._ensure_not_empty(name=name) self._delete('/workbooks/%s' % name) def validate(self, definition): self._ensure_not_empty(definition=definition) # If the specified definition is actually a file, read in the # definition file definition = utils.get_contents_if_file(definition) resp = self.http_client.post( '/workbooks/validate', definition, headers={'content-type': 'text/plain'} ) if resp.status_code != 200: self._raise_api_exception(resp) return base.extract_json(resp, None) python-mistralclient-3.3.0/mistralclient/api/v2/__init__.py0000666000175100017510000000000013241060623024002 0ustar zuulzuul00000000000000python-mistralclient-3.3.0/mistralclient/api/v2/action_executions.py0000666000175100017510000000503613241060623026004 0ustar zuulzuul00000000000000# Copyright 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 json import six from mistralclient.api import base urlparse = six.moves.urllib.parse class ActionExecution(base.Resource): resource_name = 'ActionExecution' class ActionExecutionManager(base.ResourceManager): resource_class = ActionExecution def create(self, name, input=None, **params): self._ensure_not_empty(name=name) data = {'name': name} if input: data['input'] = json.dumps(input) if params: data['params'] = json.dumps(params) resp = self.http_client.post( '/action_executions', json.dumps(data) ) if resp.status_code != 201: self._raise_api_exception(resp) return self.resource_class(self, base.get_json(resp)) def update(self, id, state=None, output=None): self._ensure_not_empty(id=id) if not (state or output): raise base.APIException( 400, "Please provide either state or output for action execution." ) data = {} if state: data['state'] = state if output: data['output'] = output return self._update('/action_executions/%s' % id, data) def list(self, task_execution_id=None, limit=None): url = '/action_executions' if task_execution_id: url = '/tasks/%s/action_executions' % task_execution_id url += "%s" qparams = {} if limit and limit > 0: qparams['limit'] = limit query_string = ("?%s" % urlparse.urlencode(list(qparams.items())) if qparams else "") return self._list(url % query_string, response_key='action_executions') def get(self, id): self._ensure_not_empty(id=id) return self._get('/action_executions/%s' % id) def delete(self, id): self._ensure_not_empty(id=id) self._delete('/action_executions/%s' % id) python-mistralclient-3.3.0/mistralclient/api/v2/members.py0000666000175100017510000000443313241060623023713 0ustar zuulzuul00000000000000# Copyright 2016 - Catalyst IT Limited # # 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 mistralclient.api import base class Member(base.Resource): resource_name = 'Member' class MemberManager(base.ResourceManager): resource_class = Member def create(self, resource_id, resource_type, member_id): self._ensure_not_empty( resource_id=resource_id, resource_type=resource_type, member_id=member_id ) data = { 'member_id': member_id, } url = '/%ss/%s/members' % (resource_type, resource_id) return self._create(url, data) def update(self, resource_id, resource_type, member_id='', status='accepted'): if not member_id: member_id = self.http_client.project_id url = '/%ss/%s/members/%s' % (resource_type, resource_id, member_id) return self._update(url, {'status': status}) def list(self, resource_id, resource_type): url = '/%ss/%s/members' % (resource_type, resource_id) return self._list(url, response_key='members') def get(self, resource_id, resource_type, member_id=None): self._ensure_not_empty( resource_id=resource_id, resource_type=resource_type, ) if not member_id: member_id = self.http_client.project_id url = '/%ss/%s/members/%s' % (resource_type, resource_id, member_id) return self._get(url) def delete(self, resource_id, resource_type, member_id): self._ensure_not_empty( resource_id=resource_id, resource_type=resource_type, member_id=member_id ) url = '/%ss/%s/members/%s' % (resource_type, resource_id, member_id) self._delete(url) python-mistralclient-3.3.0/mistralclient/api/v2/executions.py0000666000175100017510000000650013241060623024444 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, 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 from oslo_utils import uuidutils import six from mistralclient.api import base urlparse = six.moves.urllib.parse class Execution(base.Resource): resource_name = 'Execution' class ExecutionManager(base.ResourceManager): resource_class = Execution def create(self, workflow_identifier='', namespace='', workflow_input=None, description='', source_execution_id=None, **params): ident = workflow_identifier or source_execution_id self._ensure_not_empty(workflow_identifier=ident) data = { 'description': description, } if uuidutils.is_uuid_like(source_execution_id): data.update({'source_execution_id': source_execution_id}) if workflow_identifier: if uuidutils.is_uuid_like(workflow_identifier): data.update({'workflow_id': workflow_identifier}) else: data.update({'workflow_name': workflow_identifier}) if namespace: data.update({'workflow_namespace': namespace}) if workflow_input: if isinstance(workflow_input, six.string_types): data.update({'input': workflow_input}) else: data.update({'input': json.dumps(workflow_input)}) if params: data.update({'params': json.dumps(params)}) return self._create('/executions', data) def update(self, id, state, description=None, env=None): data = {} if state: data['state'] = state if description: data['description'] = description if env: data['params'] = {'env': env} return self._update('/executions/%s' % id, data) def list(self, task=None, marker='', limit=None, sort_keys='', sort_dirs='', **filters): qparams = {} if task: qparams['task_execution_id'] = task if marker: qparams['marker'] = marker if limit and limit > 0: qparams['limit'] = limit if sort_keys: qparams['sort_keys'] = sort_keys if sort_dirs: qparams['sort_dirs'] = sort_dirs for name, val in filters.items(): qparams[name] = val query_string = ("?%s" % urlparse.urlencode(list(qparams.items())) if qparams else "") return self._list( '/executions%s' % query_string, response_key='executions', ) def get(self, id): self._ensure_not_empty(id=id) return self._get('/executions/%s' % id) def delete(self, id): self._ensure_not_empty(id=id) self._delete('/executions/%s' % id) python-mistralclient-3.3.0/mistralclient/api/v2/cron_triggers.py0000666000175100017510000000405513241060623025130 0ustar zuulzuul00000000000000# Copyright 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 json from oslo_utils import uuidutils from mistralclient.api import base class CronTrigger(base.Resource): resource_name = 'CronTrigger' class CronTriggerManager(base.ResourceManager): resource_class = CronTrigger def create(self, name, workflow_identifier, workflow_input=None, workflow_params=None, pattern=None, first_time=None, count=None): self._ensure_not_empty( name=name, workflow_identifier=workflow_identifier ) data = { 'name': name, 'pattern': pattern, 'first_execution_time': first_time, 'remaining_executions': count } if uuidutils.is_uuid_like(workflow_identifier): data.update({'workflow_id': workflow_identifier}) else: data.update({'workflow_name': workflow_identifier}) if workflow_input: data.update({'workflow_input': json.dumps(workflow_input)}) if workflow_params: data.update({'workflow_params': json.dumps(workflow_params)}) return self._create('/cron_triggers', data) def list(self): return self._list('/cron_triggers', response_key='cron_triggers') def get(self, name): self._ensure_not_empty(name=name) return self._get('/cron_triggers/%s' % name) def delete(self, name): self._ensure_not_empty(name=name) self._delete('/cron_triggers/%s' % name) python-mistralclient-3.3.0/mistralclient/api/v2/tasks.py0000666000175100017510000000420213241060623023400 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, 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 six from mistralclient.api import base urlparse = six.moves.urllib.parse class Task(base.Resource): resource_name = 'Task' class TaskManager(base.ResourceManager): resource_class = Task def list(self, workflow_execution_id=None, marker='', limit=None, sort_keys='', sort_dirs='', fields=[], **filters): url = '/tasks' if workflow_execution_id: url = '/executions/%s/tasks' % workflow_execution_id url += '%s' qparams = {} if marker: qparams['marker'] = marker if limit and limit > 0: qparams['limit'] = limit if sort_keys: qparams['sort_keys'] = sort_keys if sort_dirs: qparams['sort_dirs'] = sort_dirs if fields: qparams['fields'] = ",".join(fields) for name, val in filters.items(): qparams[name] = val query_string = ("?%s" % urlparse.urlencode(list(qparams.items())) if qparams else "") return self._list(url % query_string, response_key='tasks') def get(self, id): self._ensure_not_empty(id=id) return self._get('/tasks/%s' % id) def rerun(self, task_ex_id, reset=True, env=None): url = '/tasks/%s' % task_ex_id body = { 'id': task_ex_id, 'state': 'RUNNING', 'reset': reset } if env: body['env'] = json.dumps(env) return self._update(url, body) python-mistralclient-3.3.0/mistralclient/api/v2/workflows.py0000666000175100017510000001015513241060623024314 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, 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 six from mistralclient.api import base from mistralclient import utils urlparse = six.moves.urllib.parse class Workflow(base.Resource): resource_name = 'Workflow' class WorkflowManager(base.ResourceManager): resource_class = Workflow def create(self, definition, namespace='', scope='private'): self._ensure_not_empty(definition=definition) # If the specified definition is actually a file, read in the # definition file definition = utils.get_contents_if_file(definition) resp = self.http_client.post( '/workflows?scope=%s&namespace=%s' % (scope, namespace), definition, headers={'content-type': 'text/plain'} ) if resp.status_code != 201: self._raise_api_exception(resp) return [self.resource_class(self, resource_data) for resource_data in base.extract_json(resp, 'workflows')] def update(self, definition, namespace='', scope='private', id=None): self._ensure_not_empty(definition=definition) url_pre = ('/workflows/%s' % id) if id else '/workflows' # If the specified definition is actually a file, read in the # definition file definition = utils.get_contents_if_file(definition) resp = self.http_client.put( '%s?namespace=%s&scope=%s' % (url_pre, namespace, scope), definition, headers={'content-type': 'text/plain'} ) if resp.status_code != 200: self._raise_api_exception(resp) if id: return self.resource_class(self, base.extract_json(resp, None)) return [self.resource_class(self, resource_data) for resource_data in base.extract_json(resp, 'workflows')] def list(self, namespace='', marker='', limit=None, sort_keys='', sort_dirs='', **filters): qparams = {} if namespace: qparams['namespace'] = namespace if marker: qparams['marker'] = marker if limit and limit > 0: qparams['limit'] = limit if sort_keys: qparams['sort_keys'] = sort_keys if sort_dirs: qparams['sort_dirs'] = sort_dirs for name, val in filters.items(): qparams[name] = val query_string = ("?%s" % urlparse.urlencode(list(qparams.items())) if qparams else "") return self._list( '/workflows%s' % query_string, response_key='workflows', ) def get(self, identifier, namespace=''): self._ensure_not_empty(identifier=identifier) return self._get( '/workflows/%s?namespace=%s' % (identifier, namespace) ) def delete(self, identifier, namespace=None): self._ensure_not_empty(identifier=identifier) path = '/workflows/%s' % identifier if namespace: path = path + '?namespace=%s' % namespace self._delete(path) def validate(self, definition): self._ensure_not_empty(definition=definition) # If the specified definition is actually a file, read in the # definition file definition = utils.get_contents_if_file(definition) resp = self.http_client.post( '/workflows/validate', definition, headers={'content-type': 'text/plain'} ) if resp.status_code != 200: self._raise_api_exception(resp) return base.extract_json(resp, None) python-mistralclient-3.3.0/mistralclient/api/v2/client.py0000666000175100017510000000706413241060623023542 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, 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 copy import six from oslo_utils import importutils from mistralclient.api import httpclient from mistralclient.api.v2 import action_executions from mistralclient.api.v2 import actions from mistralclient.api.v2 import cron_triggers from mistralclient.api.v2 import environments from mistralclient.api.v2 import event_triggers from mistralclient.api.v2 import executions from mistralclient.api.v2 import members from mistralclient.api.v2 import services from mistralclient.api.v2 import tasks from mistralclient.api.v2 import workbooks from mistralclient.api.v2 import workflows from mistralclient import auth osprofiler_profiler = importutils.try_import("osprofiler.profiler") _DEFAULT_MISTRAL_URL = "http://localhost:8989/v2" class Client(object): def __init__(self, auth_type='keystone', **kwargs): # We get the session at this point, as some instances of session # objects might have mutexes that can't be deep-copied. session = kwargs.pop('session', None) req = copy.deepcopy(kwargs) mistral_url = req.get('mistral_url') profile = req.get('profile') if mistral_url and not isinstance(mistral_url, six.string_types): raise RuntimeError('Mistral url should be a string.') # If auth url was provided then we perform an authentication, otherwise # just ignore this step if req.get('auth_url') or req.get('target_auth_url'): auth_handler = auth.get_auth_handler(auth_type) auth_response = auth_handler.authenticate(req, session=session) else: auth_response = {} if session is None: # If the session was None and we're using keystone auth, it will be # created by the auth_handler. session = auth_response.pop('session', None) req.update(auth_response) mistral_url = auth_response.get('mistral_url') or mistral_url if not mistral_url: mistral_url = _DEFAULT_MISTRAL_URL if profile: osprofiler_profiler.init(profile) http_client = httpclient.HTTPClient(mistral_url, session=session, **req) # Create all resource managers. self.workbooks = workbooks.WorkbookManager(http_client) self.executions = executions.ExecutionManager(http_client) self.tasks = tasks.TaskManager(http_client) self.actions = actions.ActionManager(http_client) self.workflows = workflows.WorkflowManager(http_client) self.cron_triggers = cron_triggers.CronTriggerManager(http_client) self.event_triggers = event_triggers.EventTriggerManager(http_client) self.environments = environments.EnvironmentManager(http_client) self.action_executions = action_executions.ActionExecutionManager( http_client) self.services = services.ServiceManager(http_client) self.members = members.MemberManager(http_client) python-mistralclient-3.3.0/mistralclient/api/v2/services.py0000666000175100017510000000155613241060623024107 0ustar zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from mistralclient.api import base class Service(base.Resource): resource_name = 'Service' class ServiceManager(base.ResourceManager): resource_class = Service def list(self): return self._list('/services', response_key='services') python-mistralclient-3.3.0/mistralclient/api/v2/environments.py0000666000175100017510000000552213241060623025010 0ustar zuulzuul00000000000000# Copyright 2015 - StackStorm, 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 six from mistralclient.api import base from mistralclient import utils class Environment(base.Resource): resource_name = 'Environment' def _set_attributes(self): """Override loading of the "variables" attribute from text to dict.""" for k, v in self._data.items(): if k == 'variables' and isinstance(v, six.string_types): v = json.loads(v) try: setattr(self, k, v) except AttributeError: # In this case we already defined the attribute on the class pass class EnvironmentManager(base.ResourceManager): resource_class = Environment def create(self, **kwargs): # Check to see if the file name or URI is being passed in. If so, # read it's contents first. if 'file' in kwargs: file = kwargs['file'] kwargs = utils.load_content(utils.get_contents_if_file(file)) self._ensure_not_empty(name=kwargs.get('name', None), variables=kwargs.get('variables', None)) # Convert dict to text for the variables attribute. if isinstance(kwargs['variables'], dict): kwargs['variables'] = json.dumps(kwargs['variables']) return self._create('/environments', kwargs) def update(self, **kwargs): # Check to see if the file name or URI is being passed in. If so, # read it's contents first. if 'file' in kwargs: file = kwargs['file'] kwargs = utils.load_content(utils.get_contents_if_file(file)) name = kwargs.get('name', None) self._ensure_not_empty(name=name) # Convert dict to text for the variables attribute. if kwargs.get('variables') and isinstance(kwargs['variables'], dict): kwargs['variables'] = json.dumps(kwargs['variables']) return self._update('/environments', kwargs) def list(self): return self._list('/environments', response_key='environments') def get(self, name): self._ensure_not_empty(name=name) return self._get('/environments/%s' % name) def delete(self, name): self._ensure_not_empty(name=name) self._delete('/environments/%s' % name) python-mistralclient-3.3.0/mistralclient/api/v2/event_triggers.py0000666000175100017510000000342113241060623025304 0ustar zuulzuul00000000000000# Copyright 2017, OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import json from mistralclient.api import base class EventTrigger(base.Resource): resource_name = 'EventTrigger' class EventTriggerManager(base.ResourceManager): resource_class = EventTrigger def create(self, name, workflow_id, exchange, topic, event, workflow_input=None, workflow_params=None): self._ensure_not_empty( name=name, workflow_id=workflow_id ) data = { 'workflow_id': workflow_id, 'name': name, 'exchange': exchange, 'topic': topic, 'event': event } if workflow_input: data.update({'workflow_input': json.dumps(workflow_input)}) if workflow_params: data.update({'workflow_params': json.dumps(workflow_params)}) return self._create('/event_triggers', data) def list(self): return self._list('/event_triggers', response_key='event_triggers') def get(self, id): self._ensure_not_empty(id=id) return self._get('/event_triggers/%s' % id) def delete(self, id): self._ensure_not_empty(id=id) self._delete('/event_triggers/%s' % id) python-mistralclient-3.3.0/mistralclient/api/v2/actions.py0000666000175100017510000000720013241060623023714 0ustar zuulzuul00000000000000# Copyright 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 six from mistralclient.api import base from mistralclient import utils urlparse = six.moves.urllib.parse class Action(base.Resource): resource_name = 'Action' class ActionManager(base.ResourceManager): resource_class = Action def create(self, definition, scope='private'): self._ensure_not_empty(definition=definition) # If the specified definition is actually a file, read in the # definition file definition = utils.get_contents_if_file(definition) resp = self.http_client.post( '/actions?scope=%s' % scope, definition, headers={'content-type': 'text/plain'} ) if resp.status_code != 201: self._raise_api_exception(resp) return [self.resource_class(self, resource_data) for resource_data in base.extract_json(resp, 'actions')] def update(self, definition, scope='private', id=None): self._ensure_not_empty(definition=definition) url_pre = ('/actions/%s' % id) if id else '/actions' # If the specified definition is actually a file, read in the # definition file definition = utils.get_contents_if_file(definition) resp = self.http_client.put( '%s?scope=%s' % (url_pre, scope), definition, headers={'content-type': 'text/plain'} ) if resp.status_code != 200: self._raise_api_exception(resp) return [self.resource_class(self, resource_data) for resource_data in base.extract_json(resp, 'actions')] def list(self, marker='', limit=None, sort_keys='', sort_dirs='', **filters): qparams = {} if marker: qparams['marker'] = marker if limit and limit > 0: qparams['limit'] = limit if sort_keys: qparams['sort_keys'] = sort_keys if sort_dirs: qparams['sort_dirs'] = sort_dirs for name, val in filters.items(): qparams[name] = val query_string = ("?%s" % urlparse.urlencode(list(qparams.items())) if qparams else "") return self._list( '/actions%s' % query_string, response_key='actions', ) def get(self, identifier): self._ensure_not_empty(identifier=identifier) return self._get('/actions/%s' % identifier) def delete(self, identifier): self._ensure_not_empty(identifier=identifier) self._delete('/actions/%s' % identifier) def validate(self, definition): self._ensure_not_empty(definition=definition) # If the specified definition is actually a file, read in the # definition file definition = utils.get_contents_if_file(definition) resp = self.http_client.post( '/actions/validate', definition, headers={'content-type': 'text/plain'} ) if resp.status_code != 200: self._raise_api_exception(resp) return base.extract_json(resp, None) python-mistralclient-3.3.0/mistralclient/api/base.py0000666000175100017510000001277413241060623022653 0ustar zuulzuul00000000000000# Copyright 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 copy import json from keystoneauth1 import exceptions class Resource(object): resource_name = 'Something' defaults = {} def __init__(self, manager, data): self.manager = manager self._data = data self._set_defaults() self._set_attributes() def _set_defaults(self): for k, v in self.defaults.items(): if k not in self._data: self._data[k] = v def _set_attributes(self): for k, v in self._data.items(): try: setattr(self, k, v) except AttributeError: # In this case we already defined the attribute on the class pass def to_dict(self): return copy.deepcopy(self._data) def __str__(self): vals = ", ".join(["%s='%s'" % (n, v) for n, v in self._data.items()]) return "%s [%s]" % (self.resource_name, vals) def _check_items(obj, searches): try: return all(getattr(obj, attr) == value for (attr, value) in searches) except AttributeError: return False def extract_json(response, response_key): if response_key is not None: return get_json(response)[response_key] else: return get_json(response) class ResourceManager(object): resource_class = None def __init__(self, http_client): self.http_client = http_client def find(self, **kwargs): return [i for i in self.list() if _check_items(i, kwargs.items())] def _ensure_not_empty(self, **kwargs): for name, value in kwargs.items(): if value is None or (isinstance(value, str) and len(value) == 0): raise APIException( 400, '%s is missing field "%s"' % (self.resource_class.__name__, name) ) def _copy_if_defined(self, data, **kwargs): for name, value in kwargs.items(): if value is not None: data[name] = value def _create(self, url, data, response_key=None, dump_json=True): if dump_json: data = json.dumps(data) try: resp = self.http_client.post(url, data) except exceptions.HttpError as ex: self._raise_api_exception(ex.response) if resp.status_code != 201: self._raise_api_exception(resp) return self.resource_class(self, extract_json(resp, response_key)) def _update(self, url, data, response_key=None, dump_json=True): if dump_json: data = json.dumps(data) try: resp = self.http_client.put(url, data) except exceptions.HttpError as ex: self._raise_api_exception(ex.response) if resp.status_code != 200: self._raise_api_exception(resp) return self.resource_class(self, extract_json(resp, response_key)) def _list(self, url, response_key=None): try: resp = self.http_client.get(url) except exceptions.HttpError as ex: self._raise_api_exception(ex.response) if resp.status_code != 200: self._raise_api_exception(resp) return [self.resource_class(self, resource_data) for resource_data in extract_json(resp, response_key)] def _get(self, url, response_key=None): try: resp = self.http_client.get(url) except exceptions.HttpError as ex: self._raise_api_exception(ex.response) if resp.status_code == 200: return self.resource_class(self, extract_json(resp, response_key)) else: self._raise_api_exception(resp) def _delete(self, url): try: resp = self.http_client.delete(url) except exceptions.HttpError as ex: self._raise_api_exception(ex.response) if resp.status_code != 204: self._raise_api_exception(resp) def _plurify_resource_name(self): return self.resource_class.resource_name + 's' def _raise_api_exception(self, resp): try: error_data = (resp.headers.get("Server-Error-Message", None) or get_json(resp).get("faultstring")) except ValueError: error_data = resp.content raise APIException(error_code=resp.status_code, error_message=error_data) def get_json(response): """Gets JSON representation of response. This method provided backward compatibility with old versions of requests library. """ json_field_or_function = getattr(response, 'json', None) if callable(json_field_or_function): return response.json() else: return json.loads(response.content) class APIException(Exception): def __init__(self, error_code=None, error_message=None): super(APIException, self).__init__(error_message) self.error_code = error_code self.error_message = error_message python-mistralclient-3.3.0/mistralclient/api/client.py0000666000175100017510000000165113241060623023207 0ustar zuulzuul00000000000000# Copyright 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 mistralclient.api.v2 import client as client_v2 def client(auth_type='keystone', **kwargs): return client_v2.Client(auth_type=auth_type, **kwargs) def determine_client_version(mistral_version): if mistral_version.find("v2") != -1: return 2 raise RuntimeError("Cannot determine mistral API version") python-mistralclient-3.3.0/mistralclient/api/httpclient.py0000666000175100017510000001517613241060623024116 0ustar zuulzuul00000000000000# Copyright 2013 - Mirantis, Inc. # Copyright 2016 - StackStorm, 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 base64 import copy import logging import os from oslo_utils import importutils import requests AUTH_TOKEN = 'auth_token' SESSION = 'session' CACERT = 'cacert' CERT_FILE = 'cert' CERT_KEY = 'key' INSECURE = 'insecure' PROJECT_ID = 'project_id' USER_ID = 'user_id' REGION_NAME = 'region_name' TARGET_AUTH_TOKEN = 'target_auth_token' TARGET_SESSION = 'target_session' TARGET_AUTH_URI = 'target_auth_url' TARGET_PROJECT_ID = 'target_project_id' TARGET_USER_ID = 'target_user_id' TARGET_INSECURE = 'target_insecure' TARGET_SERVICE_CATALOG = 'target_service_catalog' TARGET_REGION_NAME = 'target_region_name' TARGET_USER_DOMAIN_NAME = 'target_user_domain_name' TARGET_PROJECT_DOMAIN_NAME = 'target_project_domain_name' osprofiler_web = importutils.try_import("osprofiler.web") LOG = logging.getLogger(__name__) def log_request(func): def decorator(self, *args, **kwargs): resp = func(self, *args, **kwargs) LOG.debug("HTTP %s %s %d", resp.request.method, resp.url, resp.status_code) return resp return decorator class HTTPClient(object): def __init__(self, base_url, **kwargs): self.base_url = base_url self.session = kwargs.get('session') if not self.session: self.session = requests.Session() self.auth_token = kwargs.get(AUTH_TOKEN) self.project_id = kwargs.get(PROJECT_ID) self.user_id = kwargs.get(USER_ID) self.cacert = kwargs.get(CACERT) self.insecure = kwargs.get(INSECURE, False) self.region_name = kwargs.get(REGION_NAME) self.ssl_options = {} self.target_session = kwargs.get(TARGET_SESSION) self.target_auth_token = kwargs.get(TARGET_AUTH_TOKEN) self.target_auth_uri = kwargs.get(TARGET_AUTH_URI) self.target_user_id = kwargs.get(TARGET_USER_ID) self.target_project_id = kwargs.get(TARGET_PROJECT_ID) self.target_service_catalog = kwargs.get(TARGET_SERVICE_CATALOG) self.target_region_name = kwargs.get(TARGET_REGION_NAME) self.target_insecure = kwargs.get(TARGET_INSECURE) self.target_user_domain_name = kwargs.get(TARGET_USER_DOMAIN_NAME) self.target_project_domain_name = kwargs.get( TARGET_PROJECT_DOMAIN_NAME ) if self.base_url.startswith('https'): if self.cacert and not os.path.exists(self.cacert): raise ValueError('Unable to locate cacert file ' 'at %s.' % self.cacert) if self.cacert and self.insecure: LOG.warning('Client is set to not verify even though ' 'cacert is provided.') if self.insecure: self.ssl_options['verify'] = False else: if self.cacert: self.ssl_options['verify'] = self.cacert else: self.ssl_options['verify'] = True self.ssl_options['cert'] = ( kwargs.get(CERT_FILE), kwargs.get(CERT_KEY) ) @log_request def get(self, url, headers=None): options = self._get_request_options('get', headers) return self.session.get(self.base_url + url, **options) @log_request def post(self, url, body, headers=None): options = self._get_request_options('post', headers) return self.session.post(self.base_url + url, data=body, **options) @log_request def put(self, url, body, headers=None): options = self._get_request_options('put', headers) return self.session.put(self.base_url + url, data=body, **options) @log_request def delete(self, url, headers=None): options = self._get_request_options('delete', headers) return self.session.delete(self.base_url + url, **options) def _get_request_options(self, method, headers): headers = self._update_headers(headers) if method in ['post', 'put']: content_type = headers.get('content-type', 'application/json') headers['content-type'] = content_type options = copy.deepcopy(self.ssl_options) options['headers'] = headers return options def _update_headers(self, headers): if not headers: headers = {} if isinstance(self.session, requests.Session): if self.auth_token: headers['X-Auth-Token'] = self.auth_token if self.project_id: headers['X-Project-Id'] = self.project_id if self.user_id: headers['X-User-Id'] = self.user_id if self.region_name: headers['X-Region-Name'] = self.region_name if self.target_auth_token: headers['X-Target-Auth-Token'] = self.target_auth_token if self.target_auth_uri: headers['X-Target-Auth-Uri'] = self.target_auth_uri if self.target_project_id: headers['X-Target-Project-Id'] = self.target_project_id if self.target_user_id: headers['X-Target-User-Id'] = self.target_user_id if self.target_insecure: # Note(akovi): due to changes in requests, this parameter # must be a string. Basically, it is a truthy value on # the server side. headers['X-Target-Insecure'] = str(self.target_insecure) if self.target_region_name: headers['X-Target-Region-Name'] = self.target_region_name if self.target_user_domain_name: headers['X-Target-User-Domain-Name'] = self.target_user_domain_name if self.target_project_domain_name: h_name = 'X-Target-Project-Domain-Name' headers[h_name] = self.target_project_domain_name if self.target_service_catalog: headers['X-Target-Service-Catalog'] = base64.b64encode( self.target_service_catalog.encode('utf-8') ) if osprofiler_web: # Add headers for osprofiler. headers.update(osprofiler_web.get_trace_id_headers()) return headers python-mistralclient-3.3.0/mistralclient/utils.py0000666000175100017510000000507613241060623022325 0ustar zuulzuul00000000000000# Copyright 2015 - Huawei Technologies Co. Ltd # Copyright 2015 - StackStorm, 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 import yaml from six.moves.urllib import parse from six.moves.urllib import request from mistralclient import exceptions def do_action_on_many(action, resources, success_msg, error_msg): """Helper to run an action on many resources.""" failure_flag = False for resource in resources: try: action(resource) print(success_msg % resource) except Exception as e: failure_flag = True print(e) if failure_flag: raise exceptions.MistralClientException(error_msg) def load_content(content): if content is None or content == '': return dict() try: data = yaml.safe_load(content) except Exception: data = json.loads(content) return data def load_file(path): with open(path, 'r') as f: return load_content(f.read()) def get_contents_if_file(contents_or_file_name): """Get the contents of a file. If the value passed in is a file name or file URI, return the contents. If not, or there is an error reading the file contents, return the value passed in as the contents. For example, a workflow definition will be returned if either the workflow definition file name, or file URI are passed in, or the actual workflow definition itself is passed in. """ try: if parse.urlparse(contents_or_file_name).scheme: definition_url = contents_or_file_name else: path = os.path.abspath(contents_or_file_name) definition_url = parse.urljoin( 'file:', request.pathname2url(path) ) return request.urlopen(definition_url).read().decode('utf8') except Exception: return contents_or_file_name def load_json(input_string): try: with open(input_string) as fh: return json.load(fh) except IOError: return json.loads(input_string) python-mistralclient-3.3.0/mistralclient/tests/0000775000175100017510000000000013241061116021741 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/mistralclient/tests/__init__.py0000666000175100017510000000000013241060623024044 0ustar zuulzuul00000000000000python-mistralclient-3.3.0/mistralclient/tests/functional/0000775000175100017510000000000013241061116024103 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/mistralclient/tests/functional/cli/0000775000175100017510000000000013241061116024652 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/mistralclient/tests/functional/cli/__init__.py0000666000175100017510000000000013241060623026755 0ustar zuulzuul00000000000000python-mistralclient-3.3.0/mistralclient/tests/functional/cli/v2/0000775000175100017510000000000013241061116025201 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/mistralclient/tests/functional/cli/v2/cli_multi_tenancy_tests.py0000666000175100017510000003513013241060623032505 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. from tempest.lib import exceptions from mistralclient.tests.functional.cli.v2 import base_v2 class StandardItemsAvailabilityCLITests(base_v2.MistralClientTestBase): def test_std_workflows_availability(self): wfs = self.mistral_admin("workflow-list") self.assertTableStruct( wfs, ["Name", "Tags", "Input", "Created at", "Updated at"] ) self.assertIn("std.create_instance", [workflow["Name"] for workflow in wfs]) wfs = self.mistral_alt_user("workflow-list") self.assertTableStruct( wfs, ["Name", "Tags", "Input", "Created at", "Updated at"] ) self.assertIn("std.create_instance", [workflow["Name"] for workflow in wfs]) def test_std_actions_availability(self): acts = self.mistral_admin("action-list") self.assertTableStruct( acts, ["Name", "Is system", "Input", "Description", "Tags", "Created at", "Updated at"] ) self.assertIn("glance.images_list", [action["Name"] for action in acts]) acts = self.mistral_alt_user("action-list") self.assertTableStruct( acts, ["Name", "Is system", "Input", "Description", "Tags", "Created at", "Updated at"] ) self.assertIn("glance.images_list", [action["Name"] for action in acts]) class WorkbookIsolationCLITests(base_v2.MistralClientTestBase): def test_workbook_name_uniqueness(self): self.workbook_create(self.wb_def) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, "workbook-create", params="{0}".format(self.wb_def) ) self.workbook_create(self.wb_def, admin=False) self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "workbook-create", params="{0}".format(self.wb_def) ) def test_wb_isolation(self): wb = self.workbook_create(self.wb_def) wb_name = self.get_field_value(wb, "Name") wbs = self.mistral_admin("workbook-list") self.assertIn(wb_name, [w["Name"] for w in wbs]) alt_wbs = self.mistral_alt_user("workbook-list") self.assertNotIn(wb_name, [w["Name"] for w in alt_wbs]) def test_get_wb_from_another_tenant(self): wb = self.workbook_create(self.wb_def) name = self.get_field_value(wb, "Name") self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "workbook-get", params=name ) def test_delete_wb_from_another_tenant(self): wb = self.workbook_create(self.wb_def) name = self.get_field_value(wb, "Name") self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "workbook-delete", params=name ) class WorkflowIsolationCLITests(base_v2.MistralClientTestBase): def test_workflow_name_uniqueness(self): self.workflow_create(self.wf_def) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, "workflow-create", params="{0}".format(self.wf_def) ) self.workflow_create(self.wf_def, admin=False) self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "workflow-create", params="{0}".format(self.wf_def) ) def test_wf_isolation(self): wf = self.workflow_create(self.wf_def) wfs = self.mistral_admin("workflow-list") self.assertIn(wf[0]["Name"], [w["Name"] for w in wfs]) alt_wfs = self.mistral_alt_user("workflow-list") self.assertNotIn(wf[0]["Name"], [w["Name"] for w in alt_wfs]) def test_get_wf_from_another_tenant(self): wf = self.workflow_create(self.wf_def) self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "workflow-get", params=wf[0]["ID"] ) def test_create_public_workflow(self): wf = self.workflow_create(self.wf_def, scope='public') same_wf = self.mistral_alt_user( "workflow-get", params=wf[0]["Name"] ) self.assertEqual( wf[0]["Name"], self.get_field_value(same_wf, "Name") ) def test_delete_wf_from_another_tenant(self): wf = self.workflow_create(self.wf_def) self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "workflow-delete", params=wf[0]["ID"] ) class WorkflowSharingCLITests(base_v2.MistralClientTestBase): def setUp(self): super(WorkflowSharingCLITests, self).setUp() self.wf = self.workflow_create(self.wf_def, admin=True) def _update_shared_workflow(self, new_status='accepted'): member = self.workflow_member_create(self.wf[0]["ID"]) status = self.get_field_value(member, 'Status') self.assertEqual('pending', status) cmd_param = '%s workflow --status %s --member-id %s' % ( self.wf[0]["ID"], new_status, self.get_project_id("demo")) member = self.mistral_alt_user("member-update", params=cmd_param) status = self.get_field_value(member, 'Status') self.assertEqual(new_status, status) def test_list_accepted_shared_workflow(self): wfs = self.mistral_alt_user("workflow-list") self.assertNotIn(self.wf[0]["ID"], [w["ID"] for w in wfs]) self._update_shared_workflow(new_status='accepted') alt_wfs = self.mistral_alt_user("workflow-list") self.assertIn(self.wf[0]["ID"], [w["ID"] for w in alt_wfs]) self.assertIn( self.get_project_id("admin"), [w["Project ID"] for w in alt_wfs] ) def test_list_rejected_shared_workflow(self): self._update_shared_workflow(new_status='rejected') alt_wfs = self.mistral_alt_user("workflow-list") self.assertNotIn(self.wf[0]["ID"], [w["ID"] for w in alt_wfs]) def test_create_execution_using_shared_workflow(self): self._update_shared_workflow(new_status='accepted') execution = self.execution_create(self.wf[0]["ID"], admin=False) wf_name = self.get_field_value(execution, 'Workflow name') self.assertEqual(self.wf[0]["Name"], wf_name) def test_create_contrigger_using_shared_workflow(self): self._update_shared_workflow(new_status='accepted') trigger = self.cron_trigger_create( "test_trigger", self.wf[0]["ID"], "{}", "5 * * * *", admin=False ) wf_name = self.get_field_value(trigger, 'Workflow') self.assertEqual(self.wf[0]["Name"], wf_name) # Admin project can not delete the shared workflow, because it is used # in a cron-trigger of another project. self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-delete', params=self.wf[0]["ID"] ) class ActionIsolationCLITests(base_v2.MistralClientTestBase): def test_actions_name_uniqueness(self): self.action_create(self.act_def) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, "action-create", params="{0}".format(self.act_def) ) self.action_create(self.act_def, admin=False) self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "action-create", params="{0}".format(self.act_def) ) def test_action_isolation(self): act = self.action_create(self.act_def) acts = self.mistral_admin("action-list") self.assertIn(act[0]["Name"], [a["Name"] for a in acts]) alt_acts = self.mistral_alt_user("action-list") self.assertNotIn(act[0]["Name"], [a["Name"] for a in alt_acts]) def test_get_action_from_another_tenant(self): act = self.action_create(self.act_def) self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "action-get", params=act[0]["Name"] ) def test_delete_action_from_another_tenant(self): act = self.action_create(self.act_def) self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "action-delete", params=act[0]["Name"] ) def test_create_public_action(self): act = self.action_create(self.act_def, scope='public') same_act = self.mistral_alt_user( "action-get", params=act[0]["Name"] ) self.assertEqual( act[0]["Name"], self.get_field_value(same_act, "Name") ) class CronTriggerIsolationCLITests(base_v2.MistralClientTestBase): def test_cron_trigger_name_uniqueness(self): wf = self.workflow_create(self.wf_def) self.cron_trigger_create( "admin_trigger", wf[0]["ID"], "{}", "5 * * * *" ) self.assertRaises( exceptions.CommandFailed, self.cron_trigger_create, "admin_trigger", wf[0]["ID"], "{}" "5 * * * *", ) wf = self.workflow_create(self.wf_def, admin=False) self.cron_trigger_create( "user_trigger", wf[0]["ID"], "{}", "5 * * * *", None, None, admin=False ) self.assertRaises( exceptions.CommandFailed, self.cron_trigger_create, "user_trigger", wf[0]["ID"], "{}", "5 * * * *", None, None, admin=False ) def test_cron_trigger_isolation(self): wf = self.workflow_create(self.wf_def) self.cron_trigger_create( "trigger", wf[0]["Name"], "{}", "5 * * * *") alt_trs = self.mistral_alt_user("cron-trigger-list") self.assertNotIn("trigger", [t["Name"] for t in alt_trs]) class ExecutionIsolationCLITests(base_v2.MistralClientTestBase): def test_execution_isolation(self): wf = self.workflow_create(self.wf_def) ex = self.execution_create(wf[0]["Name"]) exec_id = self.get_field_value(ex, "ID") execs = self.mistral_admin("execution-list") self.assertIn(exec_id, [e["ID"] for e in execs]) alt_execs = self.mistral_alt_user("execution-list") self.assertNotIn(exec_id, [e["ID"] for e in alt_execs]) def test_get_execution_from_another_tenant(self): wf = self.workflow_create(self.wf_def) ex = self.execution_create(wf[0]["Name"]) exec_id = self.get_field_value(ex, "ID") self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "execution-get", params=exec_id ) class EnvironmentIsolationCLITests(base_v2.MistralClientTestBase): def setUp(self): super(EnvironmentIsolationCLITests, self).setUp() self.env_file = "env.yaml" self.create_file("{0}".format(self.env_file), "name: env\n" "description: Test env\n" "variables:\n" " var: value") def test_environment_name_uniqueness(self): self.environment_create(self.env_file) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, "environment-create", params=self.env_file ) self.environment_create(self.env_file, admin=False) self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "environment-create", params=self.env_file ) def test_environment_isolation(self): env = self.environment_create(self.env_file) env_name = self.get_field_value(env, "Name") envs = self.mistral_admin("environment-list") self.assertIn(env_name, [en["Name"] for en in envs]) alt_envs = self.mistral_alt_user("environment-list") self.assertNotIn(env_name, [en["Name"] for en in alt_envs]) def test_get_env_from_another_tenant(self): env = self.environment_create(self.env_file) env_name = self.get_field_value(env, "Name") self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "environment-get", params=env_name ) def test_delete_env_from_another_tenant(self): env = self.environment_create(self.env_file) env_name = self.get_field_value(env, "Name") self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "environment-delete", params=env_name ) class ActionExecutionIsolationCLITests(base_v2.MistralClientTestBase): def test_action_execution_isolation(self): wf = self.workflow_create(self.wf_def) wf_exec = self.execution_create(wf[0]["Name"]) direct_ex_id = self.get_field_value(wf_exec, 'ID') self.wait_execution_success(direct_ex_id) act_execs = self.mistral_admin("action-execution-list") self.assertIn(wf[0]["Name"], [act["Workflow name"] for act in act_execs]) alt_act_execs = self.mistral_alt_user("action-execution-list") self.assertNotIn(wf[0]["Name"], [act["Workflow name"] for act in alt_act_execs]) def test_get_action_execution_from_another_tenant(self): wf = self.workflow_create(self.wf_def) ex = self.execution_create(wf[0]["Name"]) exec_id = self.get_field_value(ex, "ID") self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "action-execution-get", params=exec_id ) python-mistralclient-3.3.0/mistralclient/tests/functional/cli/v2/__init__.py0000666000175100017510000000000013241060623027304 0ustar zuulzuul00000000000000python-mistralclient-3.3.0/mistralclient/tests/functional/cli/v2/base_v2.py0000666000175100017510000002110513241060623027077 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 time from tempest.lib import exceptions from mistralclient.tests.functional.cli import base MISTRAL_URL = "http://localhost:8989/v2" class MistralClientTestBase(base.MistralCLIAuth, base.MistralCLIAltAuth): _mistral_url = MISTRAL_URL @classmethod def setUpClass(cls): super(MistralClientTestBase, cls).setUpClass() cls.wb_def = os.path.relpath( 'functionaltests/resources/v2/wb_v2.yaml', os.getcwd() ) cls.wb_with_tags_def = os.path.relpath( 'functionaltests/resources/v2/wb_with_tags_v2.yaml', os.getcwd() ) cls.wf_def = os.path.relpath( 'functionaltests/resources/v2/wf_v2.yaml', os.getcwd() ) cls.wf_single_def = os.path.relpath( 'functionaltests/resources/v2/wf_single_v2.yaml', os.getcwd() ) cls.wf_with_delay_def = os.path.relpath( 'functionaltests/resources/v2/wf_delay_v2.yaml', os.getcwd() ) cls.wf_wrapping_wf = os.path.relpath( 'functionaltests/resources/v2/wf_wrapping_wf_v2.yaml', os.getcwd() ) cls.top_level_wf = os.path.relpath( 'functionaltests/resources/v2/for_namespaces/top_level_wf.yaml', os.getcwd() ) cls.middle_wf = os.path.relpath( 'functionaltests/resources/v2/for_namespaces/middle_wf.yaml', os.getcwd() ) cls.lowest_level_wf = os.path.relpath( 'functionaltests/resources/v2/for_namespaces/lowest_level_wf.yaml', os.getcwd() ) cls.async_wf_def = os.path.relpath( 'functionaltests/resources/v2/async.yaml', os.getcwd() ) cls.act_def = os.path.relpath( 'functionaltests/resources/v2/action_v2.yaml', os.getcwd() ) cls.act_tag_def = os.path.relpath( 'functionaltests/resources/v2/action_v2_tags.yaml', os.getcwd() ) def setUp(self): super(MistralClientTestBase, self).setUp() def get_field_value(self, obj, field): return [ o['Value'] for o in obj if o['Field'] == "{0}".format(field) ][0] def get_item_info(self, get_from, get_by, value): return [i for i in get_from if i[get_by] == value][0] def mistral_admin(self, cmd, params=""): self.clients = self._get_admin_clients() return self.parser.listing( self.mistral('{0}'.format(cmd), params='{0}'.format(params)) ) def mistral_alt_user(self, cmd, params=""): self.clients = self._get_alt_clients() return self.parser.listing( self.mistral_alt('{0}'.format(cmd), params='{0}'.format(params)) ) def mistral_cli(self, admin, cmd, params=''): if admin: return self.mistral_admin(cmd, params) else: return self.mistral_alt_user(cmd, params) def workbook_create(self, wb_def, admin=True): wb = self.mistral_cli( admin, 'workbook-create', params='{0}'.format(wb_def) ) wb_name = self.get_field_value(wb, "Name") self.addCleanup( self.mistral_cli, admin, 'workbook-delete', params=wb_name ) self.addCleanup( self.mistral_cli, admin, 'workflow-delete', params='wb.wf1' ) return wb def workflow_create(self, wf_def, namespace='', admin=True, scope='private'): params = '{0}'.format(wf_def) if scope == 'public': params += ' --public' if namespace: params += " --namespace " + namespace wf = self.mistral_cli( admin, 'workflow-create', params=params ) for workflow in wf: self.addCleanup( self.mistral_cli, admin, 'workflow-delete', params=workflow['ID'] ) return wf def workflow_member_create(self, wf_id): cmd_param = ( '%s workflow %s' % (wf_id, self.get_project_id("demo")) ) member = self.mistral_admin("member-create", params=cmd_param) self.addCleanup( self.mistral_admin, 'member-delete', params=cmd_param ) return member def action_create(self, act_def, admin=True, scope='private'): params = '{0}'.format(act_def) if scope == 'public': params += ' --public' acts = self.mistral_cli( admin, 'action-create', params=params ) for action in acts: self.addCleanup( self.mistral_cli, admin, 'action-delete', params=action['Name'] ) return acts def cron_trigger_create(self, name, wf_name, wf_input, pattern=None, count=None, first_time=None, admin=True): optional_params = "" if pattern: optional_params += ' --pattern "{}"'.format(pattern) if count: optional_params += ' --count {}'.format(count) if first_time: optional_params += ' --first-time "{}"'.format(first_time) trigger = self.mistral_cli( admin, 'cron-trigger-create', params='{} {} {} {}'.format(name, wf_name, wf_input, optional_params)) self.addCleanup(self.mistral_cli, admin, 'cron-trigger-delete', params=name) return trigger def event_trigger_create(self, name, wf_id, exchange, topic, event, wf_input, admin=True): trigger = self.mistral_cli( admin, 'event-trigger-create', params=' '.join((name, wf_id, exchange, topic, event, wf_input))) ev_tr_id = self.get_field_value(trigger, 'ID') self.addCleanup(self.mistral_cli, admin, 'event-trigger-delete', params=ev_tr_id) return trigger def execution_create(self, params, admin=True): ex = self.mistral_cli(admin, 'execution-create', params=params) exec_id = self.get_field_value(ex, 'ID') self.addCleanup( self.mistral_cli, admin, 'execution-delete', params=exec_id ) return ex def environment_create(self, params, admin=True): env = self.mistral_cli(admin, 'environment-create', params=params) env_name = self.get_field_value(env, 'Name') self.addCleanup( self.mistral_cli, admin, 'environment-delete', params=env_name ) return env def create_file(self, file_name, file_body=""): f = open(file_name, 'w') f.write(file_body) f.close() self.addCleanup(os.remove, file_name) def wait_execution_success(self, exec_id, timeout=180): start_time = time.time() ex = self.mistral_admin('execution-get', params=exec_id) exec_state = self.get_field_value(ex, 'State') expected_states = ['SUCCESS', 'RUNNING'] while exec_state != 'SUCCESS': if time.time() - start_time > timeout: msg = ("Execution exceeds timeout {0} to change state " "to SUCCESS. Execution: {1}".format(timeout, ex)) raise exceptions.TimeoutException(msg) ex = self.mistral_admin('execution-get', params=exec_id) exec_state = self.get_field_value(ex, 'State') if exec_state not in expected_states: msg = ("Execution state %s is not in expected " "states: %s" % (exec_state, expected_states)) raise exceptions.TempestException(msg) time.sleep(2) return True python-mistralclient-3.3.0/mistralclient/tests/functional/cli/v2/cli_tests_v2.py0000666000175100017510000021367413241060641030174 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. from tempest.lib import exceptions from mistralclient.tests.functional.cli import base from mistralclient.tests.functional.cli.v2 import base_v2 MISTRAL_URL = "http://localhost:8989/v2" class SimpleMistralCLITests(base.MistralCLIAuth): """Basic tests, check '-list', '-help' commands.""" _mistral_url = MISTRAL_URL @classmethod def setUpClass(cls): super(SimpleMistralCLITests, cls).setUpClass() def test_workbooks_list(self): workbooks = self.parser.listing(self.mistral('workbook-list')) self.assertTableStruct( workbooks, ['Name', 'Tags', 'Created at', 'Updated at'] ) def test_workflow_list(self): workflows = self.parser.listing(self.mistral('workflow-list')) self.assertTableStruct( workflows, ['ID', 'Name', 'Tags', 'Input', 'Created at', 'Updated at'] ) def test_executions_list(self): executions = self.parser.listing(self.mistral('execution-list')) self.assertTableStruct( executions, ['ID', 'Workflow name', 'Workflow ID', 'State', 'Created at', 'Updated at'] ) def test_tasks_list(self): tasks = self.parser.listing(self.mistral('task-list')) self.assertTableStruct( tasks, ['ID', 'Name', 'Workflow name', 'Execution ID', 'State'] ) def test_cron_trigger_list(self): triggers = self.parser.listing(self.mistral('cron-trigger-list')) self.assertTableStruct( triggers, ['Name', 'Workflow', 'Pattern', 'Next execution time', 'Remaining executions', 'Created at', 'Updated at'] ) def test_event_trigger_list(self): triggers = self.parser.listing(self.mistral('event-trigger-list')) self.assertTableStruct( triggers, ['ID', 'Name', 'Workflow ID', 'Exchange', 'Topic', 'Event', 'Created at', 'Updated at'] ) def test_actions_list(self): actions = self.parser.listing(self.mistral('action-list')) self.assertTableStruct( actions, ['Name', 'Is system', 'Input', 'Description', 'Tags', 'Created at', 'Updated at'] ) def test_environments_list(self): envs = self.parser.listing(self.mistral('environment-list')) self.assertTableStruct( envs, ['Name', 'Description', 'Scope', 'Created at', 'Updated at'] ) def test_action_execution_list(self): act_execs = self.parser.listing(self.mistral('action-execution-list')) self.assertTableStruct( act_execs, ['ID', 'Name', 'Workflow name', 'State', 'Accepted'] ) def test_action_execution_list_with_limit(self): act_execs = self.parser.listing( self.mistral( 'action-execution-list', params='--limit 1' ) ) self.assertEqual(1, len(act_execs)) class WorkbookCLITests(base_v2.MistralClientTestBase): """Test suite checks commands to work with workbooks.""" @classmethod def setUpClass(cls): super(WorkbookCLITests, cls).setUpClass() def test_workbook_create_delete(self): wb = self.mistral_admin('workbook-create', params=self.wb_def) wb_name = self.get_field_value(wb, "Name") self.assertTableStruct(wb, ['Field', 'Value']) wbs = self.mistral_admin('workbook-list') self.assertIn(wb_name, [w['Name'] for w in wbs]) wbs = self.mistral_admin('workbook-list') self.assertIn(wb_name, [w['Name'] for w in wbs]) self.mistral_admin('workbook-delete', params=wb_name) wbs = self.mistral_admin('workbook-list') self.assertNotIn(wb_name, [w['Name'] for w in wbs]) def test_workbook_create_with_tags(self): wb = self.workbook_create(self.wb_with_tags_def) self.assertIn('tag', self.get_field_value(wb, 'Tags')) def test_workbook_update(self): wb = self.workbook_create(self.wb_def) wb_name = self.get_field_value(wb, "Name") init_update_at = self.get_field_value(wb, "Updated at") tags = self.get_field_value(wb, 'Tags') self.assertNotIn('tag', tags) wb = self.mistral_admin('workbook-update', params=self.wb_def) update_at = self.get_field_value(wb, "Updated at") name = self.get_field_value(wb, 'Name') tags = self.get_field_value(wb, 'Tags') self.assertEqual(wb_name, name) self.assertNotIn('tag', tags) self.assertEqual(init_update_at, update_at) wb = self.mistral_admin( 'workbook-update', params=self.wb_with_tags_def ) self.assertTableStruct(wb, ['Field', 'Value']) update_at = self.get_field_value(wb, "Updated at") name = self.get_field_value(wb, 'Name') tags = self.get_field_value(wb, 'Tags') self.assertEqual(wb_name, name) self.assertIn('tag', tags) self.assertNotEqual(init_update_at, update_at) def test_workbook_get(self): created = self.workbook_create(self.wb_with_tags_def) wb_name = self.get_field_value(created, "Name") fetched = self.mistral_admin('workbook-get', params=wb_name) created_wb_name = self.get_field_value(created, 'Name') fetched_wb_name = self.get_field_value(fetched, 'Name') self.assertEqual(created_wb_name, fetched_wb_name) created_wb_tag = self.get_field_value(created, 'Tags') fetched_wb_tag = self.get_field_value(fetched, 'Tags') self.assertEqual(created_wb_tag, fetched_wb_tag) def test_workbook_get_definition(self): wb = self.workbook_create(self.wb_def) wb_name = self.get_field_value(wb, "Name") definition = self.mistral_admin( 'workbook-get-definition', params=wb_name ) self.assertNotIn('404 Not Found', definition) def test_workbook_validate_with_valid_def(self): wb = self.mistral_admin('workbook-validate', params=self.wb_def) wb_valid = self.get_field_value(wb, 'Valid') wb_error = self.get_field_value(wb, 'Error') self.assertEqual('True', wb_valid) self.assertEqual('None', wb_error) def test_workbook_validate_with_invalid_def(self): self.create_file('wb.yaml', 'name: wb\n') wb = self.mistral_admin('workbook-validate', params='wb.yaml') wb_valid = self.get_field_value(wb, 'Valid') wb_error = self.get_field_value(wb, 'Error') self.assertEqual('False', wb_valid) self.assertNotEqual('None', wb_error) class WorkflowCLITests(base_v2.MistralClientTestBase): """Test suite checks commands to work with workflows.""" @classmethod def setUpClass(cls): super(WorkflowCLITests, cls).setUpClass() def test_workflow_create_delete(self): init_wfs = self.mistral_admin('workflow-create', params=self.wf_def) wf_names = [wf['Name'] for wf in init_wfs] self.assertTableStruct(init_wfs, ['Name', 'Created at', 'Updated at']) wfs = self.mistral_admin('workflow-list') self.assertIn(wf_names[0], [workflow['Name'] for workflow in wfs]) for wf_name in wf_names: self.mistral_admin('workflow-delete', params=wf_name) wfs = self.mistral_admin('workflow-list') for wf in wf_names: self.assertNotIn(wf, [workflow['Name'] for workflow in wfs]) def test_workflow_within_namespace_create_delete(self): params = self.wf_def + ' --namespace abcdef' init_wfs = self.mistral_admin('workflow-create', params=params) wf_names = [wf['Name'] for wf in init_wfs] self.assertTableStruct(init_wfs, ['Name', 'Created at', 'Updated at']) wfs = self.mistral_admin('workflow-list') self.assertIn(wf_names[0], [workflow['Name'] for workflow in wfs]) for wf_name in wf_names: self.mistral_admin( 'workflow-delete', params=wf_name+' --namespace abcdef' ) wfs = self.mistral_admin('workflow-list') for wf in wf_names: self.assertNotIn(wf, [workflow['Name'] for workflow in wfs]) init_wfs = self.mistral_admin('workflow-create', params=params) wf_ids = [wf['ID'] for wf in init_wfs] for wf_id in wf_ids: self.mistral_admin('workflow-delete', params=wf_id) for wf in wf_names: self.assertNotIn(wf, [workflow['Name'] for workflow in wfs]) def test_create_wf_with_tags(self): init_wfs = self.workflow_create(self.wf_def) wf_name = init_wfs[1]['Name'] self.assertTableStruct( init_wfs, ['Name', 'Created at', 'Updated at', 'Tags'] ) created_wf_info = self.get_item_info( get_from=init_wfs, get_by='Name', value=wf_name ) self.assertEqual('tag', created_wf_info['Tags']) def test_workflow_update(self): wf = self.workflow_create(self.wf_def) wf_name = wf[0]['Name'] wf_id = wf[0]['ID'] created_wf_info = self.get_item_info( get_from=wf, get_by='Name', value=wf_name ) # Update a workflow with definition unchanged. upd_wf = self.mistral_admin( 'workflow-update', params='{0}'.format(self.wf_def) ) self.assertTableStruct(upd_wf, ['Name', 'Created at', 'Updated at']) updated_wf_info = self.get_item_info( get_from=upd_wf, get_by='Name', value=wf_name ) self.assertEqual(wf_name, upd_wf[0]['Name']) self.assertEqual( created_wf_info['Created at'].split(".")[0], updated_wf_info['Created at'] ) self.assertEqual( created_wf_info['Updated at'], updated_wf_info['Updated at'] ) # Update a workflow with definition changed. upd_wf = self.mistral_admin( 'workflow-update', params='{0}'.format(self.wf_with_delay_def) ) self.assertTableStruct(upd_wf, ['Name', 'Created at', 'Updated at']) updated_wf_info = self.get_item_info( get_from=upd_wf, get_by='Name', value=wf_name ) self.assertEqual(wf_name, upd_wf[0]['Name']) self.assertEqual( created_wf_info['Created at'].split(".")[0], updated_wf_info['Created at'] ) self.assertNotEqual( created_wf_info['Updated at'], updated_wf_info['Updated at'] ) # Update a workflow with uuid. upd_wf = self.mistral_admin( 'workflow-update', params='{0} --id {1}'.format(self.wf_with_delay_def, wf_id) ) self.assertTableStruct(upd_wf, ['Name', 'Created at', 'Updated at']) updated_wf_info = self.get_item_info( get_from=upd_wf, get_by='ID', value=wf_id ) self.assertEqual(wf_name, upd_wf[0]['Name']) self.assertEqual( created_wf_info['Created at'].split(".")[0], updated_wf_info['Created at'] ) self.assertNotEqual( created_wf_info['Updated at'], updated_wf_info['Updated at'] ) def test_workflow_update_within_namespace(self): namespace = 'abc' wf = self.workflow_create(self.wf_def, namespace=namespace) wf_name = wf[0]['Name'] wf_id = wf[0]['ID'] wf_namespace = wf[0]['Namespace'] created_wf_info = self.get_item_info( get_from=wf, get_by='Name', value=wf_name ) # Update a workflow with definition unchanged. upd_wf = self.mistral_admin( 'workflow-update', params='{0} --namespace {1}'.format(self.wf_def, namespace) ) self.assertTableStruct(upd_wf, ['Name', 'Created at', 'Updated at']) updated_wf_info = self.get_item_info( get_from=upd_wf, get_by='Name', value=wf_name ) self.assertEqual(wf_name, upd_wf[0]['Name']) self.assertEqual(namespace, wf_namespace) self.assertEqual(wf_namespace, upd_wf[0]['Namespace']) self.assertEqual( created_wf_info['Created at'].split(".")[0], updated_wf_info['Created at'] ) self.assertEqual( created_wf_info['Updated at'], updated_wf_info['Updated at'] ) # Update a workflow with definition changed. upd_wf = self.mistral_admin( 'workflow-update', params='{0} --namespace {1}'.format( self.wf_with_delay_def, namespace ) ) self.assertTableStruct(upd_wf, ['Name', 'Created at', 'Updated at']) updated_wf_info = self.get_item_info( get_from=upd_wf, get_by='Name', value=wf_name ) self.assertEqual(wf_name, upd_wf[0]['Name']) self.assertEqual( created_wf_info['Created at'].split(".")[0], updated_wf_info['Created at'] ) self.assertNotEqual( created_wf_info['Updated at'], updated_wf_info['Updated at'] ) # Update a workflow with uuid. upd_wf = self.mistral_admin( 'workflow-update', params='{0} --id {1}'.format(self.wf_with_delay_def, wf_id) ) self.assertTableStruct(upd_wf, ['Name', 'Created at', 'Updated at']) updated_wf_info = self.get_item_info( get_from=upd_wf, get_by='ID', value=wf_id ) self.assertEqual(wf_name, upd_wf[0]['Name']) self.assertEqual( created_wf_info['Created at'].split(".")[0], updated_wf_info['Created at'] ) self.assertNotEqual( created_wf_info['Updated at'], updated_wf_info['Updated at'] ) def test_workflow_update_truncate_input(self): input_value = "very_long_input_parameter_name_that_should_be_truncated" wf_def = """ version: "2.0" workflow1: input: - {0} tasks: task1: action: std.noop """.format(input_value) self.create_file('wf.yaml', wf_def) self.workflow_create('wf.yaml') updated_wf = self.mistral_admin('workflow-update', params='wf.yaml') updated_wf_info = self.get_item_info( get_from=updated_wf, get_by='Name', value='workflow1' ) self.assertEqual(updated_wf_info['Input'][:-3], input_value[:25]) def test_workflow_get(self): created = self.workflow_create(self.wf_def) wf_name = created[0]['Name'] fetched = self.mistral_admin('workflow-get', params=wf_name) fetched_wf_name = self.get_field_value(fetched, 'Name') self.assertEqual(wf_name, fetched_wf_name) def test_workflow_get_with_id(self): created = self.workflow_create(self.wf_def) wf_name = created[0]['Name'] wf_id = created[0]['ID'] fetched = self.mistral_admin('workflow-get', params=wf_id) fetched_wf_name = self.get_field_value(fetched, 'Name') self.assertEqual(wf_name, fetched_wf_name) def test_workflow_get_definition(self): wf = self.workflow_create(self.wf_def) wf_name = wf[0]['Name'] definition = self.mistral_admin( 'workflow-get-definition', params=wf_name ) self.assertNotIn('404 Not Found', definition) def test_workflow_validate_with_valid_def(self): wf = self.mistral_admin('workflow-validate', params=self.wf_def) wf_valid = self.get_field_value(wf, 'Valid') wf_error = self.get_field_value(wf, 'Error') self.assertEqual('True', wf_valid) self.assertEqual('None', wf_error) def test_workflow_validate_with_invalid_def(self): self.create_file('wf.yaml', 'name: wf\n') wf = self.mistral_admin('workflow-validate', params='wf.yaml') wf_valid = self.get_field_value(wf, 'Valid') wf_error = self.get_field_value(wf, 'Error') self.assertEqual('False', wf_valid) self.assertNotEqual('None', wf_error) def test_workflow_list_with_filter(self): workflows = self.parser.listing(self.mistral('workflow-list')) self.assertTableStruct( workflows, ['ID', 'Name', 'Tags', 'Input', 'Created at', 'Updated at'] ) # We know that we have more than one workflow by default. self.assertGreater(len(workflows), 1) # Now let's provide a filter to the list command. workflows = self.parser.listing( self.mistral( 'workflow-list', params='--filter name=std.create_instance' ) ) self.assertTableStruct( workflows, ['ID', 'Name', 'Tags', 'Input', 'Created at', 'Updated at'] ) self.assertEqual(1, len(workflows)) self.assertIn('std.create_instance', workflows[0]['Name']) class ExecutionCLITests(base_v2.MistralClientTestBase): """Test suite checks commands to work with executions.""" @classmethod def setUpClass(cls): super(ExecutionCLITests, cls).setUpClass() def setUp(self): super(ExecutionCLITests, self).setUp() wfs = self.workflow_create(self.wf_def) self.async_wf = self.workflow_create(self.async_wf_def)[0] self.direct_wf = wfs[0] self.reverse_wf = wfs[1] self.create_file('input', '{\n "farewell": "Bye"\n}\n') self.create_file('task_name', '{\n "task_name": "goodbye"\n}\n') def test_execution_by_id_of_workflow_within_namespace(self): namespace = 'abc' wfs = self.workflow_create(self.lowest_level_wf, namespace=namespace) wf_def_name = wfs[0]['Name'] wf_id = wfs[0]['ID'] execution = self.execution_create(wf_id) self.assertTableStruct(execution, ['Field', 'Value']) wf_name = self.get_field_value(execution, 'Workflow name') wf_namespace = self.get_field_value(execution, 'Workflow namespace') wf_id = self.get_field_value(execution, 'Workflow ID') self.assertEqual(wf_def_name, wf_name) self.assertEqual(namespace, wf_namespace) self.assertIsNotNone(wf_id) def test_execution_within_namespace_create_delete(self): namespace = 'abc' self.workflow_create(self.lowest_level_wf) self.workflow_create(self.lowest_level_wf, namespace=namespace) self.workflow_create(self.middle_wf, namespace=namespace) self.workflow_create(self.top_level_wf) wfs = self.workflow_create(self.top_level_wf, namespace=namespace) top_wf_name = wfs[0]['Name'] execution = self.mistral_admin( 'execution-create', params='{0} --namespace {1}'.format(top_wf_name, namespace) ) exec_id = self.get_field_value(execution, 'ID') self.assertTableStruct(execution, ['Field', 'Value']) wf_name = self.get_field_value(execution, 'Workflow name') wf_namespace = self.get_field_value(execution, 'Workflow namespace') wf_id = self.get_field_value(execution, 'Workflow ID') created_at = self.get_field_value(execution, 'Created at') self.assertEqual(top_wf_name, wf_name) self.assertEqual(namespace, wf_namespace) self.assertIsNotNone(wf_id) self.assertIsNotNone(created_at) execs = self.mistral_admin('execution-list') self.assertIn(exec_id, [ex['ID'] for ex in execs]) self.assertIn(wf_name, [ex['Workflow name'] for ex in execs]) self.assertIn(namespace, [ex['Workflow namespace'] for ex in execs]) self.mistral_admin('execution-delete', params=exec_id) def test_execution_create_delete(self): execution = self.mistral_admin( 'execution-create', params='{0} -d "execution test"'.format(self.direct_wf['Name']) ) exec_id = self.get_field_value(execution, 'ID') self.assertTableStruct(execution, ['Field', 'Value']) wf_name = self.get_field_value(execution, 'Workflow name') wf_id = self.get_field_value(execution, 'Workflow ID') created_at = self.get_field_value(execution, 'Created at') description = self.get_field_value(execution, 'Description') self.assertEqual(self.direct_wf['Name'], wf_name) self.assertIsNotNone(wf_id) self.assertIsNotNone(created_at) self.assertEqual("execution test", description) execs = self.mistral_admin('execution-list') self.assertIn(exec_id, [ex['ID'] for ex in execs]) self.assertIn(wf_name, [ex['Workflow name'] for ex in execs]) self.mistral_admin('execution-delete', params=exec_id) def test_execution_create_with_input_and_start_task(self): execution = self.execution_create( "%s input task_name" % self.reverse_wf['Name'] ) exec_id = self.get_field_value(execution, 'ID') result = self.wait_execution_success(exec_id) self.assertTrue(result) def test_execution_update(self): execution = self.execution_create(self.async_wf['Name']) exec_id = self.get_field_value(execution, 'ID') status = self.get_field_value(execution, 'State') self.assertEqual('RUNNING', status) # Update execution state. execution = self.mistral_admin( 'execution-update', params='{0} -s PAUSED'.format(exec_id)) updated_exec_id = self.get_field_value(execution, 'ID') status = self.get_field_value(execution, 'State') self.assertEqual(exec_id, updated_exec_id) self.assertEqual('PAUSED', status) # Update execution description. execution = self.mistral_admin( 'execution-update', params='{0} -d "execution update test"'.format(exec_id) ) description = self.get_field_value(execution, 'Description') self.assertEqual("execution update test", description) def test_execution_get(self): execution = self.execution_create(self.direct_wf['Name']) exec_id = self.get_field_value(execution, 'ID') execution = self.mistral_admin( 'execution-get', params='{0}'.format(exec_id) ) gotten_id = self.get_field_value(execution, 'ID') wf_name = self.get_field_value(execution, 'Workflow name') wf_id = self.get_field_value(execution, 'Workflow ID') self.assertIsNotNone(wf_id) self.assertEqual(exec_id, gotten_id) self.assertEqual(self.direct_wf['Name'], wf_name) def test_execution_get_input(self): execution = self.execution_create(self.direct_wf['Name']) exec_id = self.get_field_value(execution, 'ID') ex_input = self.mistral_admin('execution-get-input', params=exec_id) self.assertEqual([], ex_input) def test_execution_get_output(self): execution = self.execution_create(self.direct_wf['Name']) exec_id = self.get_field_value(execution, 'ID') ex_output = self.mistral_admin('execution-get-output', params=exec_id) self.assertEqual([], ex_output) def test_executions_list_with_task(self): wrapping_wf = self.workflow_create(self.wf_wrapping_wf) decoy = self.execution_create(wrapping_wf[-1]['Name']) wrapping_wf_ex = self.execution_create(wrapping_wf[-1]['Name']) wrapping_wf_ex_id = self.get_field_value(wrapping_wf_ex, 'ID') self.assertIsNot(wrapping_wf_ex_id, self.get_field_value(decoy, 'ID')) tasks = self.mistral_admin('task-list', params=wrapping_wf_ex_id) wrapping_task_id = tasks[-1]['ID'] wf_execs = self.mistral_cli( True, 'execution-list', params="--task {}".format(wrapping_task_id) ) self.assertEqual(1, len(wf_execs)) wf_exec = wf_execs[0] self.assertEqual(wrapping_task_id, wf_exec['Task Execution ID']) def test_executions_list_with_pagination(self): wf_ex1 = self.execution_create( params='{0} -d "a"'.format(self.direct_wf['Name']) ) wf_ex2 = self.execution_create( params='{0} -d "b"'.format(self.direct_wf['Name']) ) wf_execs = self.mistral_cli(True, 'execution-list') self.assertEqual(2, len(wf_execs)) wf_execs = self.mistral_cli( True, 'execution-list', params="--limit 1" ) self.assertEqual(1, len(wf_execs)) wf_ex1_id = self.get_field_value(wf_ex1, 'ID') wf_ex2_id = self.get_field_value(wf_ex2, 'ID') wf_execs = self.mistral_cli( True, 'execution-list', params="--marker %s" % wf_ex1_id ) self.assertNotIn(wf_ex1_id, [ex['ID'] for ex in wf_execs]) self.assertIn(wf_ex2_id, [ex['ID'] for ex in wf_execs]) wf_execs = self.mistral_cli( True, 'execution-list', params="--sort_keys Description" ) self.assertIn(wf_ex1_id, [ex['ID'] for ex in wf_execs]) self.assertIn(wf_ex2_id, [ex['ID'] for ex in wf_execs]) wf_ex1_index = -1 wf_ex2_index = -1 for idx, ex in enumerate(wf_execs): if ex['ID'] == wf_ex1_id: wf_ex1_index = idx elif ex['ID'] == wf_ex2_id: wf_ex2_index = idx self.assertLess(wf_ex1_index, wf_ex2_index) wf_execs = self.mistral_cli( True, 'execution-list', params="--sort_keys Description --sort_dirs=desc" ) self.assertIn(wf_ex1_id, [ex['ID'] for ex in wf_execs]) self.assertIn(wf_ex2_id, [ex['ID'] for ex in wf_execs]) wf_ex1_index = -1 wf_ex2_index = -1 for idx, ex in enumerate(wf_execs): if ex['ID'] == wf_ex1_id: wf_ex1_index = idx elif ex['ID'] == wf_ex2_id: wf_ex2_index = idx self.assertGreater(wf_ex1_index, wf_ex2_index) def test_execution_list_with_filter(self): wf_ex1 = self.execution_create( params='{0} -d "a"'.format(self.direct_wf['Name']) ) wf_ex1_id = self.get_field_value(wf_ex1, 'ID') self.execution_create( params='{0} -d "b"'.format(self.direct_wf['Name']) ) # Request a list without filters. wf_execs = self.mistral_cli(True, 'execution-list') self.assertTableStruct( wf_execs, ['ID', 'Workflow name', 'Workflow ID', 'State', 'Created at', 'Updated at'] ) self.assertEqual(2, len(wf_execs)) # Now let's provide a filter. wf_execs = self.mistral_cli( True, 'execution-list', params='--filter description=a' ) self.assertTableStruct( wf_execs, ['ID', 'Workflow name', 'Workflow ID', 'State', 'Created at', 'Updated at'] ) self.assertEqual(1, len(wf_execs)) self.assertEqual(wf_ex1_id, wf_execs[0]['ID']) self.assertEqual('a', wf_execs[0]['Description']) class CronTriggerCLITests(base_v2.MistralClientTestBase): """Test suite checks commands to work with cron-triggers.""" @classmethod def setUpClass(cls): super(CronTriggerCLITests, cls).setUpClass() def setUp(self): super(CronTriggerCLITests, self).setUp() wf = self.workflow_create(self.wf_def) self.wf_name = wf[0]['Name'] def test_cron_trigger_create_delete(self): trigger = self.mistral_admin( 'cron-trigger-create', params=('trigger %s {} --pattern "5 * * * *" --count 5' ' --first-time "4242-12-25 13:37"' % self.wf_name) ) self.assertTableStruct(trigger, ['Field', 'Value']) tr_name = self.get_field_value(trigger, 'Name') wf_name = self.get_field_value(trigger, 'Workflow') created_at = self.get_field_value(trigger, 'Created at') remain = self.get_field_value(trigger, 'Remaining executions') next_time = self.get_field_value(trigger, 'Next execution time') self.assertEqual('trigger', tr_name) self.assertEqual(self.wf_name, wf_name) self.assertIsNotNone(created_at) self.assertEqual("4242-12-25 13:37:00", next_time) self.assertEqual("5", remain) triggers = self.mistral_admin('cron-trigger-list') self.assertIn(tr_name, [tr['Name'] for tr in triggers]) self.assertIn(wf_name, [tr['Workflow'] for tr in triggers]) self.mistral('cron-trigger-delete', params=tr_name) triggers = self.mistral_admin('cron-trigger-list') self.assertNotIn(tr_name, [tr['Name'] for tr in triggers]) def test_two_cron_triggers_for_one_wf(self): self.cron_trigger_create('trigger1', self.wf_name, '{}', "5 * * * *") self.cron_trigger_create('trigger2', self.wf_name, '{}', "15 * * * *") triggers = self.mistral_admin('cron-trigger-list') self.assertIn("trigger1", [tr['Name'] for tr in triggers]) self.assertIn("trigger2", [tr['Name'] for tr in triggers]) def test_cron_trigger_get(self): trigger = self.cron_trigger_create( 'trigger', self.wf_name, '{}', "5 * * * *" ) self.assertTableStruct(trigger, ['Field', 'Value']) fetched_tr = self.mistral_admin( 'cron-trigger-get', params='trigger' ) self.assertTableStruct(trigger, ['Field', 'Value']) tr_name = self.get_field_value(fetched_tr, 'Name') wf_name = self.get_field_value(fetched_tr, 'Workflow') created_at = self.get_field_value(fetched_tr, 'Created at') self.assertEqual('trigger', tr_name) self.assertEqual(self.wf_name, wf_name) self.assertIsNotNone(created_at) class EventTriggerCLITests(base_v2.MistralClientTestBase): """Test suite checks commands to work with event-triggers.""" @classmethod def setUpClass(cls): super(EventTriggerCLITests, cls).setUpClass() def setUp(self): super(EventTriggerCLITests, self).setUp() wf = self.workflow_create(self.wf_def) self.wf_id = wf[0]['ID'] def test_event_trigger_create_delete(self): trigger = self.mistral_admin( 'event-trigger-create', params=('trigger %s dummy_exchange dummy_topic event.dummy {}' % self.wf_id)) self.assertTableStruct(trigger, ['Field', 'Value']) tr_id = self.get_field_value(trigger, 'ID') tr_name = self.get_field_value(trigger, 'Name') wf_id = self.get_field_value(trigger, 'Workflow ID') created_at = self.get_field_value(trigger, 'Created at') self.assertEqual('trigger', tr_name) self.assertEqual(self.wf_id, wf_id) self.assertIsNotNone(created_at) triggers = self.mistral_admin('event-trigger-list') self.assertIn(tr_name, [tr['Name'] for tr in triggers]) self.assertIn(wf_id, [tr['Workflow ID'] for tr in triggers]) self.mistral('event-trigger-delete', params=tr_id) triggers = self.mistral_admin('event-trigger-list') self.assertNotIn(tr_name, [tr['Name'] for tr in triggers]) def test_two_event_triggers_for_one_wf(self): self.event_trigger_create('trigger1', self.wf_id, 'dummy_exchange', 'dummy_topic', 'event.dummy', '{}') self.event_trigger_create('trigger2', self.wf_id, 'dummy_exchange', 'dummy_topic', 'dummy.event', '{}') triggers = self.mistral_admin('event-trigger-list') self.assertIn('trigger1', [tr['Name'] for tr in triggers]) self.assertIn('trigger2', [tr['Name'] for tr in triggers]) def test_event_trigger_get(self): trigger = self.event_trigger_create('trigger', self.wf_id, 'dummy_exchange', 'dummy_topic', 'event.dummy.other', '{}') self.assertTableStruct(trigger, ['Field', 'Value']) ev_tr_id = self.get_field_value(trigger, 'ID') fetched_tr = self.mistral_admin('event-trigger-get', params=ev_tr_id) self.assertTableStruct(trigger, ['Field', 'Value']) tr_name = self.get_field_value(fetched_tr, 'Name') wf_id = self.get_field_value(fetched_tr, 'Workflow ID') created_at = self.get_field_value(fetched_tr, 'Created at') self.assertEqual('trigger', tr_name) self.assertEqual(self.wf_id, wf_id) self.assertIsNotNone(created_at) class TaskCLITests(base_v2.MistralClientTestBase): """Test suite checks commands to work with tasks.""" def setUp(self): super(TaskCLITests, self).setUp() wfs = self.workflow_create(self.wf_def) self.direct_wf = wfs[0] self.reverse_wf = wfs[1] self.create_file('input', '{\n "farewell": "Bye"\n}\n') self.create_file('task_name', '{\n "task_name": "goodbye"\n}\n') def test_task_get(self): wf_ex = self.execution_create(self.direct_wf['Name']) wf_ex_id = self.get_field_value(wf_ex, 'ID') tasks = self.mistral_admin('task-list', params=wf_ex_id) created_task_id = tasks[-1]['ID'] fetched_task = self.mistral_admin('task-get', params=created_task_id) fetched_task_id = self.get_field_value(fetched_task, 'ID') fetched_task_wf_namespace = self.get_field_value( fetched_task, 'Workflow namespace' ) task_execution_id = self.get_field_value(fetched_task, 'Execution ID') self.assertEqual(created_task_id, fetched_task_id) self.assertEqual('', fetched_task_wf_namespace) self.assertEqual(wf_ex_id, task_execution_id) def test_task_get_list_within_namespace(self): namespace = 'aaa' self.workflow_create(self.wf_def, namespace=namespace) wf_ex = self.execution_create( self.direct_wf['Name'] + ' --namespace ' + namespace ) wf_ex_id = self.get_field_value(wf_ex, 'ID') tasks = self.mistral_admin('task-list', params=wf_ex_id) created_task_id = tasks[-1]['ID'] created_wf_namespace = tasks[-1]['Workflow namespace'] fetched_task = self.mistral_admin('task-get', params=created_task_id) fetched_task_id = self.get_field_value(fetched_task, 'ID') fetched_task_wf_namespace = self.get_field_value( fetched_task, 'Workflow namespace' ) task_execution_id = self.get_field_value(fetched_task, 'Execution ID') self.assertEqual(created_task_id, fetched_task_id) self.assertEqual(namespace, created_wf_namespace) self.assertEqual(created_wf_namespace, fetched_task_wf_namespace) self.assertEqual(wf_ex_id, task_execution_id) def test_task_list_with_filter(self): wf_exec = self.execution_create( "%s input task_name" % self.reverse_wf['Name'] ) exec_id = self.get_field_value(wf_exec, 'ID') self.assertTrue(self.wait_execution_success(exec_id)) # Request task executions without filters. tasks = self.parser.listing(self.mistral('task-list')) self.assertTableStruct( tasks, ['ID', 'Name', 'Workflow name', 'Execution ID', 'State'] ) self.assertEqual(2, len(tasks)) # Now let's provide a filter. tasks = self.parser.listing( self.mistral( 'task-list', params='--filter name=goodbye' ) ) self.assertTableStruct( tasks, ['ID', 'Name', 'Workflow name', 'Execution ID', 'State'] ) self.assertEqual(1, len(tasks)) self.assertEqual('goodbye', tasks[0]['Name']) def test_task_list_with_limit(self): wf_exec = self.execution_create( "%s input task_name" % self.reverse_wf['Name'] ) exec_id = self.get_field_value(wf_exec, 'ID') self.assertTrue(self.wait_execution_success(exec_id)) tasks = self.parser.listing(self.mistral('task-list')) tasks = self.parser.listing( self.mistral( 'task-list', params='--limit 1' ) ) self.assertEqual(1, len(tasks)) class ActionCLITests(base_v2.MistralClientTestBase): """Test suite checks commands to work with actions.""" @classmethod def setUpClass(cls): super(ActionCLITests, cls).setUpClass() def test_action_create_delete(self): init_acts = self.mistral_admin('action-create', params=self.act_def) self.assertTableStruct( init_acts, [ 'Name', 'Is system', 'Input', 'Description', 'Tags', 'Created at', 'Updated at' ] ) self.assertIn('greeting', [action['Name'] for action in init_acts]) self.assertIn('farewell', [action['Name'] for action in init_acts]) action_1 = self.get_item_info( get_from=init_acts, get_by='Name', value='greeting' ) action_2 = self.get_item_info( get_from=init_acts, get_by='Name', value='farewell' ) self.assertEqual('', action_1['Tags']) self.assertEqual('', action_2['Tags']) self.assertEqual('False', action_1['Is system']) self.assertEqual('False', action_2['Is system']) self.assertEqual('name', action_1['Input']) self.assertEqual('None', action_2['Input']) acts = self.mistral_admin('action-list') self.assertIn(action_1['Name'], [action['Name'] for action in acts]) self.assertIn(action_2['Name'], [action['Name'] for action in acts]) self.mistral_admin( 'action-delete', params='{0}'.format(action_1['Name']) ) self.mistral_admin( 'action-delete', params='{0}'.format(action_2['Name']) ) acts = self.mistral_admin('action-list') self.assertNotIn(action_1['Name'], [action['Name'] for action in acts]) self.assertNotIn(action_2['Name'], [action['Name'] for action in acts]) def test_action_update(self): actions = self.action_create(self.act_def) created_action = self.get_item_info( get_from=actions, get_by='Name', value='greeting' ) actions = self.mistral_admin('action-update', params=self.act_def) updated_action = self.get_item_info( get_from=actions, get_by='Name', value='greeting' ) self.assertEqual( created_action['Created at'].split(".")[0], updated_action['Created at'] ) self.assertEqual(created_action['Name'], updated_action['Name']) self.assertEqual( created_action['Updated at'], updated_action['Updated at'] ) actions = self.mistral_admin('action-update', params=self.act_tag_def) updated_action = self.get_item_info( get_from=actions, get_by='Name', value='greeting' ) self.assertEqual('tag, tag1', updated_action['Tags']) self.assertEqual( created_action['Created at'].split(".")[0], updated_action['Created at'] ) self.assertEqual(created_action['Name'], updated_action['Name']) self.assertNotEqual( created_action['Updated at'], updated_action['Updated at'] ) def test_action_update_with_id(self): acts = self.action_create(self.act_def) created_action = self.get_item_info( get_from=acts, get_by='Name', value='greeting' ) action_id = created_action['ID'] params = '{0} --id {1}'.format(self.act_tag_def, action_id) acts = self.mistral_admin('action-update', params=params) updated_action = self.get_item_info( get_from=acts, get_by='ID', value=action_id ) self.assertEqual( created_action['Created at'].split(".")[0], updated_action['Created at'] ) self.assertEqual(created_action['Name'], updated_action['Name']) self.assertNotEqual( created_action['Updated at'], updated_action['Updated at'] ) def test_action_update_truncate_input(self): input_value = "very_long_input_parameter_name_that_should_be_truncated" act_def = """ version: "2.0" action1: input: - {0} base: std.noop """.format(input_value) self.create_file('action.yaml', act_def) self.action_create('action.yaml') updated_act = self.mistral_admin('action-update', params='action.yaml') updated_act_info = self.get_item_info( get_from=updated_act, get_by='Name', value='action1' ) self.assertEqual(updated_act_info['Input'][:-3], input_value[:25]) def test_action_get_definition(self): self.action_create(self.act_def) definition = self.mistral_admin( 'action-get-definition', params='greeting' ) self.assertNotIn('404 Not Found', definition) def test_action_get_with_id(self): created = self.action_create(self.act_def) action_name = created[0]['Name'] action_id = created[0]['ID'] fetched = self.mistral_admin('action-get', params=action_id) fetched_action_name = self.get_field_value(fetched, 'Name') self.assertEqual(action_name, fetched_action_name) def test_action_list_with_filter(self): actions = self.parser.listing(self.mistral('action-list')) self.assertTableStruct( actions, ['Name', 'Is system', 'Input', 'Description', 'Tags', 'Created at', 'Updated at'] ) # NOTE(rakhmerov): This length isn't really a number of actions. # The problem is that one entity in a table may be on more than # one lines depending on their data. For example, for the # workflows that we use in our tests it works fine and parsing # algorithm is able to parse entities correctly even if they are # on multiple lines, but for actions it doesn't. So the only thing # we can do is only check if unfiltered table is bigger than # filtered. # We need to think how to improve it. unfiltered_len = len(actions) self.assertGreater(unfiltered_len, 0) # Now let's provide a filter to the list command. actions = self.parser.listing( self.mistral( 'action-list', params='--filter name=in:std.echo,std.noop' ) ) self.assertTableStruct( actions, ['Name', 'Is system', 'Input', 'Description', 'Tags', 'Created at', 'Updated at'] ) self.assertGreater(unfiltered_len, len(actions)) action_names = [a['Name'] for a in actions] self.assertIn('std.echo', action_names) self.assertIn('std.noop', action_names) self.assertNotIn('std.ssh', action_names) class EnvironmentCLITests(base_v2.MistralClientTestBase): """Test suite checks commands to work with environments.""" def setUp(self): super(EnvironmentCLITests, self).setUp() self.create_file( 'env.yaml', 'name: env\n' 'description: Test env\n' 'variables:\n' ' var: "value"' ) def test_environment_create(self): env = self.mistral_admin('environment-create', params='env.yaml') env_name = self.get_field_value(env, 'Name') env_desc = self.get_field_value(env, 'Description') self.assertTableStruct(env, ['Field', 'Value']) envs = self.mistral_admin('environment-list') self.assertIn(env_name, [en['Name'] for en in envs]) self.assertIn(env_desc, [en['Description'] for en in envs]) self.mistral_admin('environment-delete', params=env_name) envs = self.mistral_admin('environment-list') self.assertNotIn(env_name, [en['Name'] for en in envs]) def test_environment_create_without_description(self): self.create_file( 'env_without_des.yaml', 'name: env\n' 'variables:\n' ' var: "value"' ) env = self.mistral_admin( 'environment-create', params='env_without_des.yaml' ) env_name = self.get_field_value(env, 'Name') env_desc = self.get_field_value(env, 'Description') self.assertTableStruct(env, ['Field', 'Value']) envs = self.mistral_admin('environment-list') self.assertIn(env_name, [en['Name'] for en in envs]) self.assertIn(env_desc, 'None') self.mistral_admin('environment-delete', params='env') envs = self.mistral_admin('environment-list') self.assertNotIn(env_name, [en['Name'] for en in envs]) def test_environment_update(self): env = self.environment_create('env.yaml') env_name = self.get_field_value(env, 'Name') env_desc = self.get_field_value(env, 'Description') env_created_at = self.get_field_value(env, 'Created at') env_updated_at = self.get_field_value(env, 'Updated at') self.assertIsNotNone(env_created_at) self.assertEqual('None', env_updated_at) self.create_file( 'env_upd.yaml', 'name: env\n' 'description: Updated env\n' 'variables:\n' ' var: "value"' ) env = self.mistral_admin('environment-update', params='env_upd.yaml') self.assertTableStruct(env, ['Field', 'Value']) updated_env_name = self.get_field_value(env, 'Name') updated_env_desc = self.get_field_value(env, 'Description') updated_env_created_at = self.get_field_value(env, 'Created at') updated_env_updated_at = self.get_field_value(env, 'Updated at') self.assertEqual(env_name, updated_env_name) self.assertNotEqual(env_desc, updated_env_desc) self.assertEqual('Updated env', updated_env_desc) self.assertEqual(env_created_at.split('.')[0], updated_env_created_at) self.assertIsNotNone(updated_env_updated_at) def test_environment_get(self): env = self.environment_create('env.yaml') env_name = self.get_field_value(env, 'Name') env_desc = self.get_field_value(env, 'Description') env = self.mistral_admin('environment-get', params=env_name) fetched_env_name = self.get_field_value(env, 'Name') fetched_env_desc = self.get_field_value(env, 'Description') self.assertTableStruct(env, ['Field', 'Value']) self.assertEqual(env_name, fetched_env_name) self.assertEqual(env_desc, fetched_env_desc) class ActionExecutionCLITests(base_v2.MistralClientTestBase): """Test suite checks commands to work with action executions.""" def setUp(self): super(ActionExecutionCLITests, self).setUp() wfs = self.workflow_create(self.wf_def) self.direct_wf = wfs[0] direct_wf_exec = self.execution_create(self.direct_wf['Name']) self.direct_ex_id = self.get_field_value(direct_wf_exec, 'ID') def test_act_execution_get(self): self.wait_execution_success(self.direct_ex_id) task = self.mistral_admin('task-list', params=self.direct_ex_id)[0] act_ex_from_list = self.mistral_admin( 'action-execution-list', params=task['ID'] )[0] act_ex = self.mistral_admin( 'action-execution-get', params=act_ex_from_list['ID'] ) wf_name = self.get_field_value(act_ex, 'Workflow name') state = self.get_field_value(act_ex, 'State') self.assertEqual( act_ex_from_list['ID'], self.get_field_value(act_ex, 'ID') ) self.assertEqual(self.direct_wf['Name'], wf_name) self.assertEqual('SUCCESS', state) def test_act_execution_list_with_limit(self): self.wait_execution_success(self.direct_ex_id) act_execs = self.mistral_admin('action-execution-list') # The workflow execution started in setUp() # generates 2 action executions. self.assertGreater(len(act_execs), 1) act_execs = self.mistral_admin( 'action-execution-list', params="--limit 1" ) self.assertEqual(len(act_execs), 1) act_ex = act_execs[0] self.assertEqual(self.direct_wf['Name'], act_ex['Workflow name']) self.assertEqual('SUCCESS', act_ex['State']) def test_act_execution_get_list_within_namespace(self): namespace = 'bbb' self.workflow_create(self.wf_def, namespace=namespace) wf_ex = self.execution_create( self.direct_wf['Name'] + ' --namespace ' + namespace ) exec_id = self.get_field_value(wf_ex, 'ID') self.wait_execution_success(exec_id) task = self.mistral_admin('task-list', params=exec_id)[0] act_ex_from_list = self.mistral_admin( 'action-execution-list', params=task['ID'] )[0] act_ex = self.mistral_admin( 'action-execution-get', params=act_ex_from_list['ID'] ) wf_name = self.get_field_value(act_ex, 'Workflow name') wf_namespace = self.get_field_value(act_ex, 'Workflow namespace') status = self.get_field_value(act_ex, 'State') self.assertEqual( act_ex_from_list['ID'], self.get_field_value(act_ex, 'ID') ) self.assertEqual(self.direct_wf['Name'], wf_name) self.assertEqual('SUCCESS', status) self.assertEqual(namespace, wf_namespace) self.assertEqual(namespace, act_ex_from_list['Workflow namespace']) def test_act_execution_create_delete(self): action_ex = self.mistral_admin( 'run-action', params="std.echo '{0}' --save-result".format( '{"output": "Hello!"}') ) action_ex_id = self.get_field_value(action_ex, 'ID') self.assertTableStruct(action_ex, ['Field', 'Value']) name = self.get_field_value(action_ex, 'Name') wf_name = self.get_field_value(action_ex, 'Workflow name') task_name = self.get_field_value(action_ex, 'Task name') self.assertEqual('std.echo', name) self.assertEqual('None', wf_name) self.assertEqual('None', task_name) action_exs = self.mistral_admin('action-execution-list') self.assertIn(action_ex_id, [ex['ID'] for ex in action_exs]) self.mistral_admin('action-execution-delete', params=action_ex_id) action_exs = self.mistral_admin('action-execution-list') self.assertNotIn(action_ex_id, [ex['ID'] for ex in action_exs]) class NegativeCLITests(base_v2.MistralClientTestBase): """This class contains negative tests.""" def test_wb_list_extra_param(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workbook-list', params='param' ) def test_wb_get_unexist_wb(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workbook-get', params='wb' ) def test_wb_get_without_param(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workbook-get' ) def test_wb_create_same_name(self): self.workbook_create(self.wb_def) self.assertRaises( exceptions.CommandFailed, self.workbook_create, self.wb_def ) def test_wb_create_with_wrong_path_to_definition(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workbook_create', 'wb' ) def test_wb_delete_unexist_wb(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workbook-delete', params='wb' ) def test_wb_update_wrong_path_to_def(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workbook-update', params='wb' ) def test_wb_update_nonexistant_wb(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workbook-update', params=self.wb_with_tags_def ) def test_wb_create_empty_def(self): self.create_file('empty') self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workbook-create', params='empty' ) def test_wb_update_empty_def(self): self.create_file('empty') self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workbook-update', params='empty' ) def test_wb_get_definition_unexist_wb(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workbook-get-definition', params='wb' ) def test_wb_create_invalid_def(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workbook-create', params=self.wf_def ) def test_wb_update_invalid_def(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workbook-update', params=self.wf_def ) def test_wb_update_without_def(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workbook-update' ) def test_wf_list_extra_param(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-list', params='param' ) def test_wf_get_unexist_wf(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-get', params='wf' ) def test_wf_get_without_param(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-get' ) def test_wf_create_without_definition(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-create', params='' ) def test_wf_create_with_wrong_path_to_definition(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-create', params='wf' ) def test_wf_delete_unexist_wf(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-delete', params='wf' ) def test_wf_update_unexist_wf(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-update', params='wf' ) def test_wf_get_definition_unexist_wf(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-get-definition', params='wf' ) def test_wf_get_definition_missed_param(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-get-definition' ) def test_wf_create_invalid_def(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-create', params=self.wb_def ) def test_wf_update_invalid_def(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-update', params=self.wb_def ) def test_wf_create_empty_def(self): self.create_file('empty') self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-create', params='empty' ) def test_wf_update_empty_def(self): self.create_file('empty') self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-update', params='empty' ) def test_ex_list_extra_param(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'execution-list', params='param' ) def test_ex_create_unexist_wf(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'execution-create', params='wf' ) def test_ex_create_unexist_task(self): wf = self.workflow_create(self.wf_def) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'execution-create', params='%s param {}' % wf[0]['Name'] ) def test_ex_create_with_invalid_input(self): wf = self.workflow_create(self.wf_def) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'execution-create', params="%s input" % wf[0]['Name'] ) def test_ex_get_nonexist_execution(self): wf = self.workflow_create(self.wf_def) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'execution-get', params='%s id' % wf[0]['Name'] ) def test_ex_create_without_wf_name(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'execution-create' ) def test_ex_create_reverse_wf_without_start_task(self): wf = self.workflow_create(self.wf_def) self.create_file('input', '{\n "farewell": "Bye"\n}\n') self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'execution-create ', params=wf[1]['Name'] ) def test_ex_create_missed_input(self): self.create_file('empty') wf = self.workflow_create(self.wf_def) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'execution-create empty', params=wf[1]['Name'] ) def test_ex_update_both_state_and_description(self): wf = self.workflow_create(self.wf_def) execution = self.execution_create(params=wf[0]['Name']) exec_id = self.get_field_value(execution, 'ID') self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'execution-update', params='%s -s ERROR -d update' % exec_id ) def test_ex_delete_nonexistent_execution(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'execution-delete', params='1a2b3c' ) def test_tr_create_without_pattern(self): wf = self.workflow_create(self.wf_def) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'cron-trigger-create', params='tr %s {}' % wf[0]['Name'] ) def test_tr_create_invalid_pattern(self): wf = self.workflow_create(self.wf_def) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'cron-trigger-create', params='tr %s {} --pattern "q"' % wf[0]['Name'] ) def test_tr_create_invalid_pattern_value_out_of_range(self): wf = self.workflow_create(self.wf_def) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'cron-trigger-create', params='tr %s {} --pattern "80 * * * *"' % wf[0]['Name'] ) def test_tr_create_nonexistent_wf(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'cron-trigger-create', params='tr wb.wf1 {} --pattern "* * * * *"' ) def test_tr_delete_nonexistant_tr(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'cron-trigger-delete', params='tr' ) def test_tr_get_nonexistant_tr(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'cron-trigger-get', params='tr' ) def test_tr_create_invalid_count(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'cron-trigger-create', params='tr wb.wf1 {} --pattern "* * * * *" --count q' ) def test_tr_create_negative_count(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'cron-trigger-create', params='tr wb.wf1 {} --pattern "* * * * *" --count -1') def test_tr_create_invalid_first_date(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'cron-trigger-create', params='tr wb.wf1 {} --pattern "* * * * *" --first-date "q"' ) def test_tr_create_count_only(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'cron-trigger-create', params='tr wb.wf1 {} --count 42' ) def test_tr_create_date_and_count_without_pattern(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'cron-trigger-create', params='tr wb.wf1 {} --count 42 --first-time "4242-12-25 13:37"' ) def test_event_tr_create_missing_argument(self): wf = self.workflow_create(self.wf_def) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'event-trigger-create', params='tr %s exchange topic' % wf[0]['ID'] ) def test_event_tr_create_nonexistent_wf(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'event-trigger-create', params='456 4307362e-4a4a-4021-aa58-0fab23c9c751 ' 'exchange topic event {} ' ) def test_event_tr_delete_nonexistent_tr(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'event-trigger-delete', params='789' ) def test_event_tr_get_nonexistent_tr(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'event-trigger-get', params='789' ) def test_action_get_nonexistent(self): self.assertRaises( exceptions.CommandFailed, self.mistral, 'action-get', params='nonexist' ) def test_action_double_creation(self): self.action_create(self.act_def) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'action-create', params='{0}'.format(self.act_def) ) def test_action_create_without_def(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'action-create', params='' ) def test_action_create_invalid_def(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'action-create', params='{0}'.format(self.wb_def) ) def test_action_delete_nonexistent_act(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'action-delete', params='nonexist' ) def test_action_delete_standard_action(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'action-delete', params='heat.events_get' ) def test_action_get_definition_nonexistent_action(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'action-get-definition', params='nonexist' ) def test_task_get_nonexistent_task(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'task-get', params='nonexist' ) def test_env_get_without_param(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'environment-get' ) def test_env_get_nonexistent(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'environment-get', params='nonexist' ) def test_env_create_same_name(self): self.create_file( 'env.yaml', 'name: env\n' 'description: Test env\n' 'variables:\n' ' var: "value"' ) self.environment_create('env.yaml') self.assertRaises( exceptions.CommandFailed, self.environment_create, 'env.yaml' ) def test_env_create_empty(self): self.create_file('env.yaml') self.assertRaises( exceptions.CommandFailed, self.environment_create, 'env.yaml' ) def test_env_create_with_wrong_path_to_definition(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'execution_create', 'env' ) def test_env_delete_unexist_env(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'environment-delete', params='env' ) def test_env_update_wrong_path_to_def(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'environment-update', params='env' ) def test_env_update_empty(self): self.create_file('env.yaml') self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'environment-update', params='env' ) def test_env_update_nonexistant_env(self): self.create_file( 'env.yaml', 'name: env' 'variables:\n var: "value"' ) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'environment-update', params='env.yaml' ) def test_env_create_without_name(self): self.create_file('env.yaml', 'variables:\n var: "value"') self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'environment-create', params='env.yaml' ) def test_env_create_without_variables(self): self.create_file('env.yaml', 'name: env') self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'environment-create', params='env.yaml' ) def test_action_execution_get_without_params(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'action-execution-get' ) def test_action_execution_get_unexistent_obj(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'action-execution-get', params='123456' ) def test_action_execution_update(self): wfs = self.workflow_create(self.wf_def) direct_wf_exec = self.execution_create(wfs[0]['Name']) direct_ex_id = self.get_field_value(direct_wf_exec, 'ID') self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'action-execution-update', params='%s ERROR' % direct_ex_id ) def test_target_action_execution(self): command = ( '--debug ' '--os-target-tenant-name={tenantname} ' '--os-target-username={username} ' '--os-target-password="{password}" ' '--os-target-auth-url="{auth_url}" ' '--target_insecure ' 'run-action heat.stacks_list' ).format( tenantname=self.clients.tenant_name, username=self.clients.username, password=self.clients.password, auth_url=self.clients.uri ) self.mistral_alt_user(cmd=command) python-mistralclient-3.3.0/mistralclient/tests/functional/cli/base.py0000666000175100017510000001272513241060623026151 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 CLI_DIR = os.environ.get( 'OS_MISTRALCLIENT_EXEC_DIR', os.path.join(os.path.abspath('.'), '.tox/functional/bin') ) _CREDS_FILE = 'functional_creds.conf' def credentials(group='admin'): """Retrieves credentials to run functional tests. Credentials are either read from the environment or from a config file ('functional_creds.conf'). Environment variables override those from the config file. The 'functional_creds.conf' file is the clean and new way to use (by default tox 2.0 does not pass environment variables). """ if group == 'admin': username = os.environ.get('OS_USERNAME') password = os.environ.get('OS_PASSWORD') tenant_name = os.environ.get('OS_TENANT_NAME') user_domain = os.environ.get('OS_USER_DOMAIN_NAME') project_domain = os.environ.get('OS_PROJECT_DOMAIN_NAME') else: username = os.environ.get('OS_ALT_USERNAME') password = os.environ.get('OS_ALT_PASSWORD') tenant_name = os.environ.get('OS_ALT_TENANT_NAME') user_domain = os.environ.get('OS_ALT_USER_DOMAIN_NAME') project_domain = os.environ.get('OS_ALT_PROJECT_DOMAIN_NAME') auth_url = os.environ.get('OS_AUTH_URL') config = configparser.RawConfigParser() if config.read(_CREDS_FILE): username = username or config.get(group, 'user') password = password or config.get(group, 'pass') tenant_name = tenant_name or config.get(group, 'tenant') auth_url = auth_url or config.get('auth', 'uri') user_domain = user_domain or config.get(group, 'user_domain') project_domain = project_domain or config.get(group, 'project_domain') # TODO(ddeja): Default value of OS_AUTH_URL is to provide url to v3 API. # Since tempest openstack client doesn't properly handle it, we switch # it back to v2. Once tempest openstack starts to use v3, this can be # deleted. # https://github.com/openstack/tempest/blob/master/tempest/lib/cli/base.py#L363 return { 'username': username, 'password': password, 'tenant_name': tenant_name, 'auth_url': auth_url.replace('v3', 'v2.0') } class MistralCLIAuth(base.ClientTestBase): _mistral_url = None def _get_admin_clients(self): creds = credentials() clients = base.CLIClient( username=creds['username'], password=creds['password'], tenant_name=creds['tenant_name'], project_name=creds['tenant_name'], uri=creds['auth_url'], cli_dir=CLI_DIR ) return clients def _get_clients(self): return self._get_admin_clients() def mistral(self, action, flags='', params='', fail_ok=False): """Executes Mistral command.""" mistral_url_op = "--os-mistral-url %s" % self._mistral_url if 'WITHOUT_AUTH' in os.environ: return base.execute( 'mistral %s' % mistral_url_op, action, flags, params, fail_ok, merge_stderr=False, cli_dir='' ) else: return self.clients.cmd_with_auth( 'mistral %s' % mistral_url_op, action, flags, params, fail_ok ) def get_project_id(self, project='admin'): project_name = credentials(project)['tenant_name'] admin_clients = self._get_clients() # TODO(mfedosin): when bug #1719687 is closed we should provide # domain names in related parameters, not just as abstract flags flags = "--os-user-domain-name default " \ "--os-project-domain-name default " \ "--os-identity-api-version 3" projects = self.parser.listing( admin_clients.openstack( 'project show', params=project_name, flags=flags ) ) return [o['Value'] for o in projects if o['Field'] == 'id'][0] class MistralCLIAltAuth(base.ClientTestBase): _mistral_url = None def _get_alt_clients(self): creds = credentials('demo') clients = base.CLIClient( username=creds['username'], password=creds['password'], project_name=creds['tenant_name'], tenant_name=creds['tenant_name'], uri=creds['auth_url'], cli_dir=CLI_DIR ) return clients def _get_clients(self): return self._get_alt_clients() def mistral_alt(self, action, flags='', params='', mode='alt_user'): """Executes Mistral command for alt_user from alt_tenant.""" mistral_url_op = "--os-mistral-url %s" % self._mistral_url return self.clients.cmd_with_auth( 'mistral %s' % mistral_url_op, action, flags, params) python-mistralclient-3.3.0/mistralclient/tests/functional/__init__.py0000666000175100017510000000000013241060623026206 0ustar zuulzuul00000000000000python-mistralclient-3.3.0/mistralclient/tests/unit/0000775000175100017510000000000013241061116022720 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/mistralclient/tests/unit/test_utils.py0000666000175100017510000000426613241060623025505 0ustar zuulzuul00000000000000# Copyright 2015 - StackStorm, 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 yaml from mistralclient import utils from oslotest import base ENV_DICT = {'k1': 'abc', 'k2': 123, 'k3': True} ENV_STR = json.dumps(ENV_DICT) ENV_YAML = yaml.safe_dump(ENV_DICT, default_flow_style=False) class UtilityTest(base.BaseTestCase): def test_load_empty(self): self.assertDictEqual(dict(), utils.load_content(None)) self.assertDictEqual(dict(), utils.load_content('')) self.assertDictEqual(dict(), utils.load_content('{}')) self.assertListEqual(list(), utils.load_content('[]')) def test_load_json_content(self): self.assertDictEqual(ENV_DICT, utils.load_content(ENV_STR)) def test_load_json_file(self): with tempfile.NamedTemporaryFile() as f: f.write(ENV_STR.encode('utf-8')) f.flush() file_path = os.path.abspath(f.name) self.assertDictEqual(ENV_DICT, utils.load_file(file_path)) def test_load_yaml_content(self): self.assertDictEqual(ENV_DICT, utils.load_content(ENV_YAML)) def test_load_yaml_file(self): with tempfile.NamedTemporaryFile() as f: f.write(ENV_YAML.encode('utf-8')) f.flush() file_path = os.path.abspath(f.name) self.assertDictEqual(ENV_DICT, utils.load_file(file_path)) def test_load_json(self): with tempfile.NamedTemporaryFile() as f: f.write(ENV_STR.encode('utf-8')) f.flush() self.assertDictEqual(ENV_DICT, utils.load_json(f.name)) self.assertDictEqual(ENV_DICT, utils.load_json(ENV_STR)) python-mistralclient-3.3.0/mistralclient/tests/unit/test_httpclient.py0000666000175100017510000002213113241060623026512 0ustar zuulzuul00000000000000# Copyright 2016 - StackStorm, 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 base64 import copy import mock from six.moves.urllib import parse as urlparse from oslo_utils import uuidutils from osprofiler import _utils as osprofiler_utils import osprofiler.profiler from mistralclient.api import httpclient from mistralclient.tests.unit import base API_BASE_URL = 'http://localhost:8989/v2' API_URL = '/executions' EXPECTED_URL = API_BASE_URL + API_URL AUTH_TOKEN = uuidutils.generate_uuid() PROJECT_ID = uuidutils.generate_uuid() USER_ID = uuidutils.generate_uuid() REGION_NAME = 'fake_region' PROFILER_HMAC_KEY = 'SECRET_HMAC_KEY' PROFILER_TRACE_ID = uuidutils.generate_uuid() EXPECTED_AUTH_HEADERS = { 'X-Auth-Token': AUTH_TOKEN, 'X-Project-Id': PROJECT_ID, 'X-User-Id': USER_ID, 'X-Region-Name': REGION_NAME } EXPECTED_REQ_OPTIONS = { 'headers': EXPECTED_AUTH_HEADERS } EXPECTED_BODY = { 'k1': 'abc', 'k2': 123, 'k3': True } class HTTPClientTest(base.BaseClientTest): def setUp(self): super(HTTPClientTest, self).setUp() osprofiler.profiler.init(None) self.client = httpclient.HTTPClient( API_BASE_URL, auth_token=AUTH_TOKEN, project_id=PROJECT_ID, user_id=USER_ID, region_name=REGION_NAME ) def assertExpectedAuthHeaders(self): headers = self.requests_mock.last_request.headers self.assertEqual(AUTH_TOKEN, headers['X-Auth-Token']) self.assertEqual(PROJECT_ID, headers['X-Project-Id']) self.assertEqual(USER_ID, headers['X-User-Id']) return headers def assertExpectedBody(self): text = self.requests_mock.last_request.text form = urlparse.parse_qs(text, strict_parsing=True) self.assertEqual(len(EXPECTED_BODY), len(form)) for k, v in EXPECTED_BODY.items(): self.assertEqual([str(v)], form[k]) return form def test_get_request_options(self): m = self.requests_mock.get(EXPECTED_URL, text='text') self.client.get(API_URL) self.assertTrue(m.called_once) self.assertExpectedAuthHeaders() @mock.patch.object( osprofiler.profiler._Profiler, 'get_base_id', mock.MagicMock(return_value=PROFILER_TRACE_ID) ) @mock.patch.object( osprofiler.profiler._Profiler, 'get_id', mock.MagicMock(return_value=PROFILER_TRACE_ID) ) def test_get_request_options_with_profile_enabled(self): m = self.requests_mock.get(EXPECTED_URL, text='text') osprofiler.profiler.init(PROFILER_HMAC_KEY) data = {'base_id': PROFILER_TRACE_ID, 'parent_id': PROFILER_TRACE_ID} signed_data = osprofiler_utils.signed_pack(data, PROFILER_HMAC_KEY) headers = { 'X-Trace-Info': signed_data[0], 'X-Trace-HMAC': signed_data[1] } self.client.get(API_URL) self.assertTrue(m.called_once) headers = self.assertExpectedAuthHeaders() self.assertEqual(signed_data[0], headers['X-Trace-Info']) self.assertEqual(signed_data[1], headers['X-Trace-HMAC']) def test_get_request_options_with_headers_for_get(self): m = self.requests_mock.get(EXPECTED_URL, text='text') target_auth_url = uuidutils.generate_uuid() target_auth_token = uuidutils.generate_uuid() target_user_id = 'target_user' target_project_id = 'target_project' target_service_catalog = 'this should be there' target_insecure = 'target insecure' target_region = 'target region name' target_user_domain_name = 'target user domain name' target_project_domain_name = 'target project domain name' target_client = httpclient.HTTPClient( API_BASE_URL, auth_token=AUTH_TOKEN, project_id=PROJECT_ID, user_id=USER_ID, region_name=REGION_NAME, target_auth_url=target_auth_url, target_auth_token=target_auth_token, target_project_id=target_project_id, target_user_id=target_user_id, target_service_catalog=target_service_catalog, target_region_name=target_region, target_user_domain_name=target_user_domain_name, target_project_domain_name=target_project_domain_name, target_insecure=target_insecure ) target_client.get(API_URL) self.assertTrue(m.called_once) headers = self.assertExpectedAuthHeaders() self.assertEqual(target_auth_url, headers['X-Target-Auth-Uri']) self.assertEqual(target_auth_token, headers['X-Target-Auth-Token']) self.assertEqual(target_user_id, headers['X-Target-User-Id']) self.assertEqual(target_project_id, headers['X-Target-Project-Id']) self.assertEqual(str(target_insecure), headers['X-Target-Insecure']) self.assertEqual(target_region, headers['X-Target-Region-Name']) self.assertEqual(target_user_domain_name, headers['X-Target-User-Domain-Name']) self.assertEqual(target_project_domain_name, headers['X-Target-Project-Domain-Name']) catalog = base64.b64encode(target_service_catalog.encode('utf-8')) self.assertEqual(catalog, headers['X-Target-Service-Catalog']) def test_get_request_options_with_headers_for_post(self): m = self.requests_mock.post(EXPECTED_URL, text='text') headers = {'foo': 'bar'} self.client.post(API_URL, EXPECTED_BODY, headers=headers) self.assertTrue(m.called_once) headers = self.assertExpectedAuthHeaders() self.assertEqual('application/json', headers['Content-Type']) self.assertEqual('bar', headers['foo']) self.assertExpectedBody() def test_get_request_options_with_headers_for_put(self): m = self.requests_mock.put(EXPECTED_URL, text='text') headers = {'foo': 'bar'} self.client.put(API_URL, EXPECTED_BODY, headers=headers) self.assertTrue(m.called_once) headers = self.assertExpectedAuthHeaders() self.assertEqual('application/json', headers['Content-Type']) self.assertEqual('bar', headers['foo']) self.assertExpectedBody() def test_get_request_options_with_headers_for_delete(self): m = self.requests_mock.delete(EXPECTED_URL, text='text') headers = {'foo': 'bar'} self.client.delete(API_URL, headers=headers) self.assertTrue(m.called_once) headers = self.assertExpectedAuthHeaders() self.assertEqual('bar', headers['foo']) @mock.patch.object( httpclient.HTTPClient, '_get_request_options', mock.MagicMock(return_value=copy.deepcopy(EXPECTED_REQ_OPTIONS)) ) def test_http_get(self): m = self.requests_mock.get(EXPECTED_URL, text='text') self.client.get(API_URL) httpclient.HTTPClient._get_request_options.assert_called_with( 'get', None ) self.assertTrue(m.called_once) self.assertExpectedAuthHeaders() @mock.patch.object( httpclient.HTTPClient, '_get_request_options', mock.MagicMock(return_value=copy.deepcopy(EXPECTED_REQ_OPTIONS)) ) def test_http_post(self): m = self.requests_mock.post(EXPECTED_URL, status_code=201, text='text') self.client.post(API_URL, EXPECTED_BODY) httpclient.HTTPClient._get_request_options.assert_called_with( 'post', None ) self.assertTrue(m.called_once) self.assertExpectedAuthHeaders() self.assertExpectedBody() @mock.patch.object( httpclient.HTTPClient, '_get_request_options', mock.MagicMock(return_value=copy.deepcopy(EXPECTED_REQ_OPTIONS)) ) def test_http_put(self): m = self.requests_mock.put(EXPECTED_URL, json={}) self.client.put(API_URL, EXPECTED_BODY) httpclient.HTTPClient._get_request_options.assert_called_with( 'put', None ) self.assertTrue(m.called_once) self.assertExpectedAuthHeaders() self.assertExpectedBody() @mock.patch.object( httpclient.HTTPClient, '_get_request_options', mock.MagicMock(return_value=copy.deepcopy(EXPECTED_REQ_OPTIONS)) ) def test_http_delete(self): m = self.requests_mock.delete(EXPECTED_URL, text='text') self.client.delete(API_URL) httpclient.HTTPClient._get_request_options.assert_called_with( 'delete', None ) self.assertTrue(m.called_once) self.assertExpectedAuthHeaders() python-mistralclient-3.3.0/mistralclient/tests/unit/__init__.py0000666000175100017510000000000013241060623025023 0ustar zuulzuul00000000000000python-mistralclient-3.3.0/mistralclient/tests/unit/test_client.py0000666000175100017510000002575013241060623025624 0ustar zuulzuul00000000000000# Copyright 2015 - Huawei Technologies Co., Ltd. # Copyright 2016 - StackStorm, 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 import tempfile import mock from oslo_utils import uuidutils from oslotest import base import osprofiler.profiler from mistralclient.api import client AUTH_HTTP_URL_v3 = 'http://localhost:35357/v3' AUTH_HTTP_URL_v2_0 = 'http://localhost:35357/v2.0' AUTH_HTTPS_URL = AUTH_HTTP_URL_v3.replace('http', 'https') MISTRAL_HTTP_URL = 'http://localhost:8989/v2' MISTRAL_HTTPS_URL = MISTRAL_HTTP_URL.replace('http', 'https') PROFILER_HMAC_KEY = 'SECRET_HMAC_KEY' class BaseClientTests(base.BaseTestCase): @staticmethod def setup_keystone_mock(session_mock): keystone_client_instance = session_mock.return_value keystone_client_instance.auth_token = uuidutils.generate_uuid() keystone_client_instance.project_id = uuidutils.generate_uuid() keystone_client_instance.user_id = uuidutils.generate_uuid() keystone_client_instance.auth_ref = str(json.dumps({})) return keystone_client_instance @mock.patch('keystoneauth1.session.Session') def test_mistral_url_from_catalog_v2(self, session_mock): session = mock.Mock() session_mock.side_effect = [session] get_endpoint = mock.Mock(return_value='http://mistral_host:8989/v2') session.get_endpoint = get_endpoint mistralclient = client.client( username='mistral', project_name='mistral', api_key='password', auth_url=AUTH_HTTP_URL_v2_0, service_type='workflowv2' ) self.assertEqual( 'http://mistral_host:8989/v2', mistralclient.actions.http_client.base_url ) @mock.patch('keystoneauth1.session.Session') def test_mistral_url_from_catalog(self, session_mock): session = mock.Mock() session_mock.side_effect = [session] get_endpoint = mock.Mock(return_value='http://mistral_host:8989/v2') session.get_endpoint = get_endpoint mistralclient = client.client( username='mistral', project_name='mistral', api_key='password', user_domain_name='Default', project_domain_name='Default', auth_url=AUTH_HTTP_URL_v3, service_type='workflowv2' ) self.assertEqual( 'http://mistral_host:8989/v2', mistralclient.actions.http_client.base_url ) @mock.patch('keystoneauth1.session.Session') @mock.patch('mistralclient.api.httpclient.HTTPClient') def test_mistral_url_default(self, http_client_mock, session_mock): session = mock.Mock() session_mock.side_effect = [session] get_endpoint = mock.Mock(side_effect=Exception) session.get_endpoint = get_endpoint client.client( username='mistral', project_name='mistral', api_key='password', user_domain_name='Default', project_domain_name='Default', auth_url=AUTH_HTTP_URL_v3 ) self.assertTrue(http_client_mock.called) mistral_url_for_http = http_client_mock.call_args[0][0] self.assertEqual(MISTRAL_HTTP_URL, mistral_url_for_http) @mock.patch('mistralclient.auth.keystone.KeystoneAuthHandler' '._is_service_catalog_v2', return_value=True) @mock.patch('keystoneauth1.identity.generic.Password') @mock.patch('keystoneauth1.session.Session') @mock.patch('mistralclient.api.httpclient.HTTPClient') def test_target_parameters_processed( self, http_client_mock, session_mock, password_mock, catalog_type_mock ): session = mock.MagicMock() target_session = mock.MagicMock() session_mock.side_effect = [session, target_session] auth = mock.MagicMock() target_auth = mock.MagicMock() target_auth._plugin.auth_url = AUTH_HTTP_URL_v3 password_mock.side_effect = [auth, target_auth] get_endpoint = mock.Mock(return_value='http://mistral_host:8989/v2') session.get_endpoint = get_endpoint target_session.get_project_id = mock.Mock(return_value='projectid') target_session.get_user_id = mock.Mock(return_value='userid') target_session.get_auth_headers = mock.Mock(return_value={ 'X-Auth-Token': 'authtoken' }) mock_access = mock.MagicMock() mock_catalog = mock.MagicMock() mock_catalog.catalog = {} mock_access.service_catalog = mock_catalog auth.get_access = mock.Mock(return_value=mock_access) client.client( auth_url=AUTH_HTTP_URL_v3, username='user', api_key='password', user_domain_name='Default', project_domain_name='Default', target_username='tmistral', target_project_name='tmistralp', target_auth_url=AUTH_HTTP_URL_v3, target_api_key='tpassword', target_user_domain_name='Default', target_project_domain_name='Default', target_region_name='tregion' ) self.assertTrue(http_client_mock.called) mistral_url_for_http = http_client_mock.call_args[0][0] kwargs = http_client_mock.call_args[1] self.assertEqual('http://mistral_host:8989/v2', mistral_url_for_http) expected_values = { 'target_project_id': 'projectid', 'target_auth_token': 'authtoken', 'target_user_id': 'userid', 'target_auth_url': AUTH_HTTP_URL_v3, 'target_project_name': 'tmistralp', 'target_username': 'tmistral', 'target_region_name': 'tregion', 'target_service_catalog': "{}" } for key in expected_values: self.assertEqual(expected_values[key], kwargs[key]) @mock.patch('keystoneauth1.session.Session') @mock.patch('mistralclient.api.httpclient.HTTPClient') def test_mistral_url_https_insecure(self, http_client_mock, session_mock): keystone_client_instance = self.setup_keystone_mock( # noqa session_mock ) expected_args = ( MISTRAL_HTTPS_URL, ) client.client( mistral_url=MISTRAL_HTTPS_URL, username='mistral', project_name='mistral', api_key='password', user_domain_name='Default', project_domain_name='Default', auth_url=AUTH_HTTP_URL_v3, cacert=None, insecure=True ) self.assertTrue(http_client_mock.called) self.assertEqual(http_client_mock.call_args[0], expected_args) self.assertEqual(http_client_mock.call_args[1]['insecure'], True) @mock.patch('keystoneauth1.session.Session') @mock.patch('mistralclient.api.httpclient.HTTPClient') def test_mistral_url_https_secure(self, http_client_mock, session_mock): fd, cert_path = tempfile.mkstemp(suffix='.pem') keystone_client_instance = self.setup_keystone_mock( # noqa session_mock ) expected_args = ( MISTRAL_HTTPS_URL, ) try: client.client( mistral_url=MISTRAL_HTTPS_URL, username='mistral', project_name='mistral', api_key='password', user_domain_name='Default', project_domain_name='Default', auth_url=AUTH_HTTP_URL_v3, cacert=cert_path, insecure=False ) finally: os.close(fd) os.unlink(cert_path) self.assertTrue(http_client_mock.called) self.assertEqual(http_client_mock.call_args[0], expected_args) self.assertEqual(http_client_mock.call_args[1]['cacert'], cert_path) @mock.patch('keystoneauth1.session.Session') def test_mistral_url_https_bad_cacert(self, session_mock): keystone_client_instance = self.setup_keystone_mock( # noqa session_mock ) self.assertRaises( ValueError, client.client, mistral_url=MISTRAL_HTTPS_URL, username='mistral', project_name='mistral', api_key='password', user_domain_name='Default', project_domain_name='Default', auth_url=AUTH_HTTP_URL_v3, cacert='/path/to/foobar', insecure=False ) @mock.patch('logging.Logger.warning') @mock.patch('keystoneauth1.session.Session') def test_mistral_url_https_bad_insecure(self, session_mock, log_warning_mock): fd, path = tempfile.mkstemp(suffix='.pem') keystone_client_instance = self.setup_keystone_mock( session_mock ) try: client.client( mistral_url=MISTRAL_HTTPS_URL, user_id=keystone_client_instance.user_id, project_id=keystone_client_instance.project_id, api_key='password', user_domain_name='Default', project_domain_name='Default', auth_url=AUTH_HTTP_URL_v3, cacert=path, insecure=True ) finally: os.close(fd) os.unlink(path) self.assertTrue(log_warning_mock.called) @mock.patch('keystoneauth1.session.Session') @mock.patch('mistralclient.api.httpclient.HTTPClient') def test_mistral_profile_enabled(self, http_client_mock, session_mock): keystone_client_instance = self.setup_keystone_mock( # noqa session_mock ) client.client( username='mistral', project_name='mistral', api_key='password', user_domain_name='Default', project_domain_name='Default', auth_url=AUTH_HTTP_URL_v3, profile=PROFILER_HMAC_KEY ) self.assertTrue(http_client_mock.called) profiler = osprofiler.profiler.get() self.assertEqual(profiler.hmac_key, PROFILER_HMAC_KEY) @mock.patch('mistralclient.auth.get_auth_handler') def test_mistral_no_auth(self, get_auth_handler_mock): # Test that we don't authenticate if auth url wasn't provided client.client( username='mistral', project_name='mistral', api_key='password', service_type='workflowv2' ) self.assertEqual(0, get_auth_handler_mock.call_count) python-mistralclient-3.3.0/mistralclient/tests/unit/base_shell_test.py0000666000175100017510000000274513241060623026446 0ustar zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import sys from oslotest import base import six from mistralclient import shell class BaseShellTests(base.BaseTestCase): def shell(self, argstr): orig = (sys.stdout, sys.stderr) clean_env = {} _old_env, os.environ = os.environ, clean_env.copy() try: sys.stdout = six.moves.cStringIO() sys.stderr = six.moves.cStringIO() _shell = shell.MistralShell() _shell.run(argstr.split()) except SystemExit: exc_type, exc_value, exc_traceback = sys.exc_info() self.assertEqual(0, exc_value.code) finally: stdout = sys.stdout.getvalue() stderr = sys.stderr.getvalue() sys.stdout.close() sys.stderr.close() sys.stdout, sys.stderr = orig os.environ = _old_env return stdout, stderr python-mistralclient-3.3.0/mistralclient/tests/unit/resources/0000775000175100017510000000000013241061116024732 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/mistralclient/tests/unit/resources/ctx.json0000666000175100017510000000012413241060623026424 0ustar zuulzuul00000000000000{ "context": { "server": { "name": "name" } } } python-mistralclient-3.3.0/mistralclient/tests/unit/resources/env_v2.yaml0000666000175100017510000000016213241060623027020 0ustar zuulzuul00000000000000--- "name": "env1" "description": "Test Environment #1" "scope": "private" "variables": "server": "localhost"python-mistralclient-3.3.0/mistralclient/tests/unit/resources/wf_v2.yaml0000666000175100017510000000015313241060623026644 0ustar zuulzuul00000000000000 --- version: 2.0 my_wf: type: direct tasks: task1: action: std.echo output="hello, world" python-mistralclient-3.3.0/mistralclient/tests/unit/resources/action_v2.yaml0000666000175100017510000000016313241060623027506 0ustar zuulzuul00000000000000 --- version: 2.0 my_action: base: std.echo base-input: output: 'Bye!' output: info: <% $.output %> python-mistralclient-3.3.0/mistralclient/tests/unit/resources/env_v2.json0000666000175100017510000000022013241060623027022 0ustar zuulzuul00000000000000{ "name": "env1", "description": "Test Environment #1", "scope": "private", "variables": { "server": "localhost" } }python-mistralclient-3.3.0/mistralclient/tests/unit/resources/wb_v2.yaml0000666000175100017510000000046313241060623026644 0ustar zuulzuul00000000000000 --- version: 2.0 name: wb workflows: wf1: type: direct input: - param1 - param2 tasks: task1: action: std.http url="localhost:8989" on-success: - test_subsequent test_subsequent: action: std.http url="http://some_url" server_id=1 python-mistralclient-3.3.0/mistralclient/tests/unit/v2/0000775000175100017510000000000013241061116023247 5ustar zuulzuul00000000000000python-mistralclient-3.3.0/mistralclient/tests/unit/v2/test_executions.py0000666000175100017510000001720413241060623027056 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, 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 from mistralclient.api import base as api_base from mistralclient.api.v2 import executions from mistralclient.tests.unit.v2 import base # TODO(everyone): Later we need additional tests verifying all the errors etc. EXEC = { 'id': "123", 'workflow_id': '123e4567-e89b-12d3-a456-426655440000', 'workflow_name': 'my_wf', 'workflow_namespace': '', 'description': '', 'state': 'RUNNING', 'input': { "person": { "first_name": "John", "last_name": "Doe" } } } SUB_WF_EXEC = { 'id': "456", 'workflow_id': '123e4567-e89b-12d3-a456-426655440000', 'workflow_name': 'my_sub_wf', 'workflow_namespace': '', 'task_execution_id': "abc", 'description': '', 'state': 'RUNNING', 'input': { "person": { "first_name": "John", "last_name": "Doe" } } } SOURCE_EXEC = EXEC SOURCE_EXEC['source_execution_id'] = EXEC['workflow_id'] URL_TEMPLATE = '/executions' URL_TEMPLATE_ID = '/executions/%s' class TestExecutionsV2(base.BaseClientV2Test): def test_create(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE, json=EXEC, status_code=201) body = { 'workflow_name': EXEC['workflow_name'], 'description': '', 'input': json.dumps(EXEC['input']) } ex = self.executions.create( EXEC['workflow_name'], EXEC['workflow_namespace'], EXEC['input'] ) self.assertIsNotNone(ex) self.assertDictEqual( executions.Execution(self.executions, EXEC).to_dict(), ex.to_dict() ) self.assertDictEqual(body, self.requests_mock.last_request.json()) def test_create_with_workflow_id(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE, json=EXEC, status_code=201) body = { 'workflow_id': EXEC['workflow_id'], 'description': '', 'input': json.dumps(EXEC['input']) } ex = self.executions.create( EXEC['workflow_id'], workflow_input=EXEC['input'] ) self.assertIsNotNone(ex) self.assertDictEqual( executions.Execution(self.executions, EXEC).to_dict(), ex.to_dict() ) self.assertDictEqual(body, self.requests_mock.last_request.json()) def test_create_with_source_execution_id(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE, json=SOURCE_EXEC, status_code=201) body = { 'description': '', 'source_execution_id': SOURCE_EXEC['source_execution_id'] } ex = self.executions.create( source_execution_id=SOURCE_EXEC['source_execution_id'] ) self.assertIsNotNone(ex) self.assertDictEqual( executions.Execution(self.executions, SOURCE_EXEC).to_dict(), ex.to_dict() ) self.assertDictEqual(body, self.requests_mock.last_request.json()) def test_create_failure1(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE, json=EXEC, status_code=201) self.assertRaises(api_base.APIException, self.executions.create, '') def test_update(self): url = self.TEST_URL + URL_TEMPLATE_ID % EXEC['id'] self.requests_mock.put(url, json=EXEC) body = { 'state': EXEC['state'] } ex = self.executions.update(EXEC['id'], EXEC['state']) self.assertIsNotNone(ex) self.assertDictEqual( executions.Execution(self.executions, EXEC).to_dict(), ex.to_dict() ) self.assertDictEqual(body, self.requests_mock.last_request.json()) def test_update_env(self): url = self.TEST_URL + URL_TEMPLATE_ID % EXEC['id'] self.requests_mock.put(url, json=EXEC) body = { 'state': EXEC['state'], 'params': { 'env': {'k1': 'foobar'} } } ex = self.executions.update( EXEC['id'], EXEC['state'], env={'k1': 'foobar'} ) self.assertIsNotNone(ex) self.assertDictEqual( executions.Execution(self.executions, EXEC).to_dict(), ex.to_dict() ) self.assertDictEqual(body, self.requests_mock.last_request.json()) def test_list(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'executions': [EXEC, SUB_WF_EXEC]}) execution_list = self.executions.list() self.assertEqual(2, len(execution_list)) self.assertDictEqual( executions.Execution(self.executions, EXEC).to_dict(), execution_list[0].to_dict() ) self.assertDictEqual( executions.Execution(self.executions, SUB_WF_EXEC).to_dict(), execution_list[1].to_dict() ) def test_list_with_pagination(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'executions': [EXEC], 'next': '/executions?fake'}) execution_list = self.executions.list( limit=1, sort_keys='created_at', sort_dirs='asc' ) self.assertEqual(1, len(execution_list)) last_request = self.requests_mock.last_request # The url param order is unpredictable. self.assertEqual(['1'], last_request.qs['limit']) self.assertEqual(['created_at'], last_request.qs['sort_keys']) self.assertEqual(['asc'], last_request.qs['sort_dirs']) def test_list_with_no_limit(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'executions': [EXEC]}) execution_list = self.executions.list(limit=-1) self.assertEqual(1, len(execution_list)) last_request = self.requests_mock.last_request self.assertNotIn('limit', last_request.qs) def test_get(self): url = self.TEST_URL + URL_TEMPLATE_ID % EXEC['id'] self.requests_mock.get(url, json=EXEC) ex = self.executions.get(EXEC['id']) self.assertDictEqual( executions.Execution(self.executions, EXEC).to_dict(), ex.to_dict() ) def test_get_sub_wf_ex(self): url = self.TEST_URL + URL_TEMPLATE_ID % SUB_WF_EXEC['id'] self.requests_mock.get(url, json=SUB_WF_EXEC) ex = self.executions.get(SUB_WF_EXEC['id']) self.assertDictEqual( executions.Execution(self.executions, SUB_WF_EXEC).to_dict(), ex.to_dict() ) def test_delete(self): url = self.TEST_URL + URL_TEMPLATE_ID % EXEC['id'] self.requests_mock.delete(url, status_code=204) self.executions.delete(EXEC['id']) python-mistralclient-3.3.0/mistralclient/tests/unit/v2/test_cli_workflows.py0000666000175100017510000001402313241060623027550 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, 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 from mistralclient.api.v2 import workflows from mistralclient.commands.v2 import base as cmd_base from mistralclient.commands.v2 import workflows as workflow_cmd from mistralclient.tests.unit import base WORKFLOW_DICT = { 'id': '1-2-3-4', 'name': 'a', 'namespace': '', 'project_id': '12345', 'tags': ['a', 'b'], 'input': 'param', 'created_at': '1', 'updated_at': '1' } WF_DEF = """ version: '2.0' flow: tasks: task1: action: nova.servers_get server="1" """ WF_WITH_DEF_DICT = WORKFLOW_DICT.copy() WF_WITH_DEF_DICT.update({'definition': WF_DEF}) WORKFLOW = workflows.Workflow(mock, WORKFLOW_DICT) WORKFLOW_WITH_DEF = workflows.Workflow(mock, WF_WITH_DEF_DICT) class TestCLIWorkflowsV2(base.BaseCommandTest): @mock.patch('argparse.open', create=True) def test_create(self, mock_open): self.client.workflows.create.return_value = [WORKFLOW] result = self.call(workflow_cmd.Create, app_args=['1.txt']) self.assertEqual( [('1-2-3-4', 'a', '', '12345', 'a, b', 'param', '1', '1')], result[1] ) @mock.patch('argparse.open', create=True) def test_create_public(self, mock_open): self.client.workflows.create.return_value = [WORKFLOW] result = self.call( workflow_cmd.Create, app_args=['1.txt', '--public'] ) self.assertEqual( [('1-2-3-4', 'a', '', '12345', 'a, b', 'param', '1', '1')], result[1] ) self.assertEqual( 'public', self.client.workflows.create.call_args[1]['scope'] ) @mock.patch('argparse.open', create=True) def test_create_long_input(self, mock_open): wf_long_input_dict = WORKFLOW_DICT.copy() long_input = ', '.join( ['var%s' % i for i in six.moves.xrange(10)] ) wf_long_input_dict['input'] = long_input workflow_long_input = workflows.Workflow(mock, wf_long_input_dict) self.client.workflows.create.return_value = [workflow_long_input] result = self.call(workflow_cmd.Create, app_args=['1.txt']) self.assertEqual( [('1-2-3-4', 'a', '', '12345', 'a, b', cmd_base.cut(long_input), '1', '1')], result[1] ) @mock.patch('argparse.open', create=True) def test_update(self, mock_open): self.client.workflows.update.return_value = [WORKFLOW] result = self.call(workflow_cmd.Update, app_args=['1.txt']) self.assertEqual( [('1-2-3-4', 'a', '', '12345', 'a, b', 'param', '1', '1')], result[1] ) @mock.patch('argparse.open', create=True) def test_update_public(self, mock_open): self.client.workflows.update.return_value = [WORKFLOW] result = self.call( workflow_cmd.Update, app_args=['1.txt', '--public'] ) self.assertEqual( [('1-2-3-4', 'a', '', '12345', 'a, b', 'param', '1', '1')], result[1] ) self.assertEqual( 'public', self.client.workflows.update.call_args[1]['scope'] ) @mock.patch('argparse.open', create=True) def test_update_with_id(self, mock_open): self.client.workflows.update.return_value = WORKFLOW result = self.call( workflow_cmd.Update, app_args=['1.txt', '--id', '1-2-3-4'] ) self.assertEqual( [('1-2-3-4', 'a', '', '12345', 'a, b', 'param', '1', '1')], result[1] ) def test_list(self): self.client.workflows.list.return_value = [WORKFLOW] result = self.call(workflow_cmd.List) self.assertEqual( [('1-2-3-4', 'a', '', '12345', 'a, b', 'param', '1', '1')], result[1] ) def test_get(self): self.client.workflows.get.return_value = WORKFLOW result = self.call(workflow_cmd.Get, app_args=['name']) self.assertEqual( ('1-2-3-4', 'a', '', '12345', 'a, b', 'param', '1', '1'), result[1] ) def test_delete(self): self.call(workflow_cmd.Delete, app_args=['name']) self.client.workflows.delete.assert_called_once_with('name', None) def test_delete_with_multi_names(self): self.call(workflow_cmd.Delete, app_args=['name1', 'name2']) self.assertEqual(2, self.client.workflows.delete.call_count) self.assertEqual( [mock.call('name1', None), mock.call('name2', None)], self.client.workflows.delete.call_args_list ) def test_get_definition(self): self.client.workflows.get.return_value = WORKFLOW_WITH_DEF self.call(workflow_cmd.GetDefinition, app_args=['name']) self.app.stdout.write.assert_called_with(WF_DEF) @mock.patch('argparse.open', create=True) def test_validate(self, mock_open): self.client.workflows.validate.return_value = {'valid': True} result = self.call(workflow_cmd.Validate, app_args=['wf.yaml']) self.assertEqual((True, None), result[1]) @mock.patch('argparse.open', create=True) def test_validate_failed(self, mock_open): self.client.workflows.validate.return_value = { 'valid': False, 'error': 'Invalid DSL...' } result = self.call(workflow_cmd.Validate, app_args=['wf.yaml']) self.assertEqual((False, 'Invalid DSL...'), result[1]) python-mistralclient-3.3.0/mistralclient/tests/unit/v2/test_cli_tasks.py0000666000175100017510000000774613241060623026656 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, Inc. # 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 json import mock from mistralclient.api.v2 import tasks from mistralclient.commands.v2 import tasks as task_cmd from mistralclient.commands.v2.tasks import TaskFormatter from mistralclient.tests.unit import base TASK_DICT = { 'id': '123', 'name': 'some', 'workflow_name': 'thing', 'workflow_namespace': '', 'workflow_execution_id': '321', 'state': 'RUNNING', 'state_info': None, 'created_at': '1', 'updated_at': '1', } TASK_RESULT = {"test": "is", "passed": "successfully"} TASK_PUBLISHED = {"bar1": "val1", "var2": 2} TASK_WITH_RESULT_DICT = TASK_DICT.copy() TASK_WITH_RESULT_DICT.update({'result': json.dumps(TASK_RESULT)}) TASK_WITH_PUBLISHED_DICT = TASK_DICT.copy() TASK_WITH_PUBLISHED_DICT.update({'published': json.dumps(TASK_PUBLISHED)}) TASK = tasks.Task(mock, TASK_DICT) TASK_WITH_RESULT = tasks.Task(mock, TASK_WITH_RESULT_DICT) TASK_WITH_PUBLISHED = tasks.Task(mock, TASK_WITH_PUBLISHED_DICT) EXPECTED_TASK_RESULT = ( '123', 'some', 'thing', '', '321', 'RUNNING', None, '1', '1' ) class TestCLITasksV2(base.BaseCommandTest): def test_list(self): self.client.tasks.list.return_value = [TASK] result = self.call(task_cmd.List) self.assertEqual([EXPECTED_TASK_RESULT], result[1]) self.assertEqual( self.client.tasks.list.call_args[1]["fields"], TaskFormatter.COLUMN_FIELD_NAMES ) def test_list_with_workflow_execution(self): self.client.tasks.list.return_value = [TASK] result = self.call(task_cmd.List, app_args=['workflow_execution']) self.assertEqual([EXPECTED_TASK_RESULT], result[1]) def test_get(self): self.client.tasks.get.return_value = TASK result = self.call(task_cmd.Get, app_args=['id']) self.assertEqual(EXPECTED_TASK_RESULT, result[1]) def test_get_result(self): self.client.tasks.get.return_value = TASK_WITH_RESULT self.call(task_cmd.GetResult, app_args=['id']) self.assertDictEqual( TASK_RESULT, json.loads(self.app.stdout.write.call_args[0][0]) ) def test_get_published(self): self.client.tasks.get.return_value = TASK_WITH_PUBLISHED self.call(task_cmd.GetPublished, app_args=['id']) self.assertDictEqual( TASK_PUBLISHED, json.loads(self.app.stdout.write.call_args[0][0]) ) def test_rerun(self): self.client.tasks.rerun.return_value = TASK result = self.call(task_cmd.Rerun, app_args=['id']) self.assertEqual(EXPECTED_TASK_RESULT, result[1]) def test_rerun_no_reset(self): self.client.tasks.rerun.return_value = TASK result = self.call(task_cmd.Rerun, app_args=['id', '--resume']) self.assertEqual(EXPECTED_TASK_RESULT, result[1]) def test_rerun_update_env(self): self.client.tasks.rerun.return_value = TASK result = self.call( task_cmd.Rerun, app_args=['id', '--env', '{"k1": "foobar"}'] ) self.assertEqual(EXPECTED_TASK_RESULT, result[1]) def test_rerun_no_reset_update_env(self): self.client.tasks.rerun.return_value = TASK result = self.call( task_cmd.Rerun, app_args=['id', '--resume', '--env', '{"k1": "foobar"}'] ) self.assertEqual(EXPECTED_TASK_RESULT, result[1]) python-mistralclient-3.3.0/mistralclient/tests/unit/v2/test_members.py0000666000175100017510000000575213241060623026327 0ustar zuulzuul00000000000000# Copyright 2016 Catalyst IT Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import copy from mistralclient.tests.unit.v2 import base MEMBER = { 'id': '123', 'resource_id': '456', 'resource_type': 'workflow', 'project_id': 'dc4ffdee54d74028b19b1b90e77aa84f', 'member_id': '04f61e967fa14cd49950ffe2319317ad', 'status': 'pending', } WORKFLOW_MEMBERS_URL = '/workflows/%s/members' % (MEMBER['resource_id']) WORKFLOW_MEMBER_URL = '/workflows/%s/members/%s' % ( MEMBER['resource_id'], MEMBER['member_id'] ) class TestWorkflowMembers(base.BaseClientV2Test): def test_create(self): self.requests_mock.post(self.TEST_URL + WORKFLOW_MEMBERS_URL, json=MEMBER, status_code=201) mb = self.members.create( MEMBER['resource_id'], MEMBER['resource_type'], MEMBER['member_id'] ) self.assertIsNotNone(mb) self.assertDictEqual({'member_id': MEMBER['member_id']}, self.requests_mock.last_request.json()) def test_update(self): updated_member = copy.copy(MEMBER) updated_member['status'] = 'accepted' self.requests_mock.put(self.TEST_URL + WORKFLOW_MEMBER_URL, json=updated_member) mb = self.members.update( MEMBER['resource_id'], MEMBER['resource_type'], MEMBER['member_id'] ) self.assertIsNotNone(mb) self.assertDictEqual({"status": "accepted"}, self.requests_mock.last_request.json()) def test_list(self): self.requests_mock.get(self.TEST_URL + WORKFLOW_MEMBERS_URL, json={'members': [MEMBER]}) mbs = self.members.list(MEMBER['resource_id'], MEMBER['resource_type']) self.assertEqual(1, len(mbs)) def test_get(self): self.requests_mock.get(self.TEST_URL + WORKFLOW_MEMBER_URL, json=MEMBER) mb = self.members.get( MEMBER['resource_id'], MEMBER['resource_type'], MEMBER['member_id'] ) self.assertIsNotNone(mb) def test_delete(self): self.requests_mock.delete(self.TEST_URL + WORKFLOW_MEMBER_URL, status_code=204) self.members.delete( MEMBER['resource_id'], MEMBER['resource_type'], MEMBER['member_id'] ) python-mistralclient-3.3.0/mistralclient/tests/unit/v2/test_cli_environments.py0000666000175100017510000001026013241060623030241 0ustar zuulzuul00000000000000# Copyright 2015 - StackStorm, 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 copy import datetime import json import os import tempfile import mock import yaml from mistralclient.api.v2 import environments from mistralclient.commands.v2 import environments as environment_cmd from mistralclient.tests.unit import base ENVIRONMENT_DICT = { 'name': 'env1', 'description': 'Test Environment #1', 'scope': 'private', 'variables': { 'server': 'localhost', 'database': 'test', 'timeout': 600, 'verbose': True }, 'created_at': str(datetime.datetime.utcnow()), 'updated_at': str(datetime.datetime.utcnow()) } ENVIRONMENT = environments.Environment(mock, ENVIRONMENT_DICT) EXPECTED_RESULT = (ENVIRONMENT_DICT['name'], ENVIRONMENT_DICT['description'], json.dumps(ENVIRONMENT_DICT['variables'], indent=4), ENVIRONMENT_DICT['scope'], ENVIRONMENT_DICT['created_at'], ENVIRONMENT_DICT['updated_at']) class TestCLIEnvironmentsV2(base.BaseCommandTest): def _test_create(self, content): self.client.environments.create.return_value = ENVIRONMENT with tempfile.NamedTemporaryFile() as f: f.write(content.encode('utf-8')) f.flush() file_path = os.path.abspath(f.name) result = self.call(environment_cmd.Create, app_args=[file_path]) self.assertEqual(EXPECTED_RESULT, result[1]) def test_create_from_json(self): self._test_create(json.dumps(ENVIRONMENT_DICT, indent=4)) def test_create_from_yaml(self): yml = yaml.dump(ENVIRONMENT_DICT, default_flow_style=False) self._test_create(yml) def _test_update(self, content): self.client.environments.update.return_value = ENVIRONMENT with tempfile.NamedTemporaryFile() as f: f.write(content.encode('utf-8')) f.flush() file_path = os.path.abspath(f.name) result = self.call(environment_cmd.Update, app_args=[file_path]) self.assertEqual(EXPECTED_RESULT, result[1]) def test_update_from_json(self): env = copy.deepcopy(ENVIRONMENT_DICT) del env['created_at'] del env['updated_at'] self._test_update(json.dumps(env, indent=4)) def test_update_from_yaml(self): env = copy.deepcopy(ENVIRONMENT_DICT) del env['created_at'] del env['updated_at'] yml = yaml.dump(env, default_flow_style=False) self._test_update(yml) def test_list(self): self.client.environments.list.return_value = [ENVIRONMENT] expected = (ENVIRONMENT_DICT['name'], ENVIRONMENT_DICT['description'], ENVIRONMENT_DICT['scope'], ENVIRONMENT_DICT['created_at'], ENVIRONMENT_DICT['updated_at']) result = self.call(environment_cmd.List) self.assertListEqual([expected], result[1]) def test_get(self): self.client.environments.get.return_value = ENVIRONMENT result = self.call(environment_cmd.Get, app_args=['name']) self.assertEqual(EXPECTED_RESULT, result[1]) def test_delete(self): self.call(environment_cmd.Delete, app_args=['name']) self.client.environments.delete.assert_called_once_with('name') def test_delete_with_multi_names(self): self.call(environment_cmd.Delete, app_args=['name1', 'name2']) self.assertEqual(2, self.client.environments.delete.call_count) self.assertEqual( [mock.call('name1'), mock.call('name2')], self.client.environments.delete.call_args_list ) python-mistralclient-3.3.0/mistralclient/tests/unit/v2/test_cli_actions.py0000666000175100017510000001362113241060623027156 0ustar zuulzuul00000000000000# Copyright 2014 Mirantis, Inc. # 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 six from mistralclient.api.v2 import actions from mistralclient.commands.v2 import actions as action_cmd from mistralclient.commands.v2 import base as cmd_base from mistralclient.tests.unit import base ACTION_DICT = { 'id': '1234-4567-7894-7895', 'name': 'a', 'is_system': True, 'input': "param1", 'description': 'My cool action', 'tags': ['test'], 'created_at': '1', 'updated_at': '1' } ACTION_DEF = """ --- version: '2.0' base: std.echo base-parameters: output: "<% $.str1 %><% $.str2 %>" output: "<% $ %><% $ %>" """ ACTION_WITH_DEF_DICT = ACTION_DICT.copy() ACTION_WITH_DEF_DICT.update({'definition': ACTION_DEF}) ACTION = actions.Action(mock, ACTION_DICT) ACTION_WITH_DEF = actions.Action(mock, ACTION_WITH_DEF_DICT) class TestCLIActionsV2(base.BaseCommandTest): @mock.patch('argparse.open', create=True) def test_create(self, mock_open): self.client.actions.create.return_value = [ACTION] result = self.call(action_cmd.Create, app_args=['1.txt']) self.assertEqual( [('1234-4567-7894-7895', 'a', True, "param1", 'My cool action', 'test', '1', '1')], result[1] ) @mock.patch('argparse.open', create=True) def test_create_public(self, mock_open): self.client.actions.create.return_value = [ACTION] result = self.call( action_cmd.Create, app_args=['1.txt', '--public'] ) self.assertEqual( [('1234-4567-7894-7895', 'a', True, "param1", 'My cool action', 'test', '1', '1')], result[1] ) self.assertEqual( 'public', self.client.actions.create.call_args[1]['scope'] ) @mock.patch('argparse.open', create=True) def test_create_long_input(self, mock_open): action_long_input_dict = ACTION_DICT.copy() long_input = ', '.join( ['var%s' % i for i in six.moves.xrange(10)] ) action_long_input_dict['input'] = long_input workflow_long_input = actions.Action( mock.Mock(), action_long_input_dict ) self.client.actions.create.return_value = [workflow_long_input] result = self.call(action_cmd.Create, app_args=['1.txt']) self.assertEqual( [('1234-4567-7894-7895', 'a', True, cmd_base.cut(long_input), 'My cool action', 'test', '1', '1')], result[1] ) @mock.patch('argparse.open', create=True) def test_update(self, mock_open): self.client.actions.update.return_value = [ACTION] result = self.call(action_cmd.Update, app_args=['my_action.yaml']) self.assertEqual( [('1234-4567-7894-7895', 'a', True, "param1", 'My cool action', 'test', '1', '1')], result[1] ) @mock.patch('argparse.open', create=True) def test_update_public(self, mock_open): self.client.actions.update.return_value = [ACTION] result = self.call( action_cmd.Update, app_args=['my_action.yaml', '--public'] ) self.assertEqual( [('1234-4567-7894-7895', 'a', True, "param1", 'My cool action', 'test', '1', '1')], result[1] ) self.assertEqual( 'public', self.client.actions.update.call_args[1]['scope'] ) def test_list(self): self.client.actions.list.return_value = [ACTION] result = self.call(action_cmd.List) self.assertEqual( [('1234-4567-7894-7895', 'a', True, "param1", 'My cool action', 'test', '1', '1')], result[1] ) def test_get(self): self.client.actions.get.return_value = ACTION result = self.call(action_cmd.Get, app_args=['name']) self.assertEqual( ('1234-4567-7894-7895', 'a', True, "param1", 'My cool action', 'test', '1', '1'), result[1] ) def test_delete(self): self.call(action_cmd.Delete, app_args=['name']) self.client.actions.delete.assert_called_once_with('name') def test_delete_with_multi_names(self): self.call(action_cmd.Delete, app_args=['name1', 'name2']) self.assertEqual(2, self.client.actions.delete.call_count) self.assertEqual( [mock.call('name1'), mock.call('name2')], self.client.actions.delete.call_args_list ) def test_get_definition(self): self.client.actions.get.return_value = ACTION_WITH_DEF self.call(action_cmd.GetDefinition, app_args=['name']) self.app.stdout.write.assert_called_with(ACTION_DEF) @mock.patch('argparse.open', create=True) def test_validate(self, mock_open): self.client.actions.validate.return_value = {'valid': True} result = self.call(action_cmd.Validate, app_args=['action.yaml']) self.assertEqual((True, None), result[1]) @mock.patch('argparse.open', create=True) def test_validate_failed(self, mock_open): self.client.actions.validate.return_value = { 'valid': False, 'error': 'Invalid DSL...' } result = self.call(action_cmd.Validate, app_args=['action.yaml']) self.assertEqual((False, 'Invalid DSL...'), result[1]) python-mistralclient-3.3.0/mistralclient/tests/unit/v2/test_cli_members.py0000666000175100017510000000602313241060623027146 0ustar zuulzuul00000000000000# Copyright 2016 Catalyst IT Limited # # 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 mistralclient.api.v2 import members from mistralclient.commands.v2 import members as member_cmd from mistralclient.tests.unit import base MEMBER_DICT = { 'id': '123', 'resource_id': '456', 'resource_type': 'workflow', 'project_id': '1111', 'member_id': '2222', 'status': 'pending', 'created_at': '1', 'updated_at': '1' } MEMBER = members.Member(mock, MEMBER_DICT) class TestCLIWorkflowMembers(base.BaseCommandTest): def test_create(self): self.client.members.create.return_value = MEMBER result = self.call( member_cmd.Create, app_args=[MEMBER_DICT['resource_id'], MEMBER_DICT['resource_type'], MEMBER_DICT['member_id']] ) self.assertEqual( ('456', 'workflow', '1111', '2222', 'pending', '1', '1'), result[1] ) def test_update(self): self.client.members.update.return_value = MEMBER result = self.call( member_cmd.Update, app_args=[MEMBER_DICT['resource_id'], MEMBER_DICT['resource_type'], '-m', MEMBER_DICT['member_id']] ) self.assertEqual( ('456', 'workflow', '1111', '2222', 'pending', '1', '1'), result[1] ) def test_list(self): self.client.members.list.return_value = [MEMBER] result = self.call( member_cmd.List, app_args=[MEMBER_DICT['resource_id'], MEMBER_DICT['resource_type']] ) self.assertListEqual( [('456', 'workflow', '1111', '2222', 'pending', '1', '1')], result[1] ) def test_get(self): self.client.members.get.return_value = MEMBER result = self.call( member_cmd.Get, app_args=[MEMBER_DICT['resource_id'], MEMBER_DICT['resource_type'], '-m', MEMBER_DICT['member_id']] ) self.assertEqual( ('456', 'workflow', '1111', '2222', 'pending', '1', '1'), result[1] ) def test_delete(self): self.call( member_cmd.Delete, app_args=[MEMBER_DICT['resource_id'], MEMBER_DICT['resource_type'], MEMBER_DICT['member_id']] ) self.client.members.delete.assert_called_once_with( MEMBER_DICT['resource_id'], MEMBER_DICT['resource_type'], MEMBER_DICT['member_id'] ) python-mistralclient-3.3.0/mistralclient/tests/unit/v2/__init__.py0000666000175100017510000000000013241060623025352 0ustar zuulzuul00000000000000python-mistralclient-3.3.0/mistralclient/tests/unit/v2/test_cli_cron_triggers.py0000666000175100017510000001366213241060623030372 0ustar zuulzuul00000000000000# Copyright 2014 Mirantis, Inc. # All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import mock from mistralclient.api.v2 import cron_triggers from mistralclient.commands.v2 import cron_triggers as cron_triggers_cmd from mistralclient.tests.unit import base TRIGGER_DICT = { 'name': 'my_trigger', 'workflow_name': 'flow1', 'workflow_input': {}, 'workflow_params': {}, 'pattern': '* * * * *', 'next_execution_time': '4242-12-20 13:37', 'remaining_executions': 5, 'created_at': '1', 'updated_at': '1' } TRIGGER = cron_triggers.CronTrigger(mock, TRIGGER_DICT) class TestCLITriggersV2(base.BaseCommandTest): @mock.patch('mistralclient.commands.v2.cron_triggers.Create.' '_convert_time_string_to_utc') @mock.patch('argparse.open', create=True) def test_create(self, mock_open, mock_convert): self.client.cron_triggers.create.return_value = TRIGGER mock_open.return_value = mock.MagicMock(spec=open) result = self.call( cron_triggers_cmd.Create, app_args=['my_trigger', 'flow1', '--pattern', '* * * * *', '--params', '{}', '--count', '5', '--first-time', '4242-12-20 13:37', '--utc'] ) mock_convert.assert_not_called() self.assertEqual( ( 'my_trigger', 'flow1', {}, '* * * * *', '4242-12-20 13:37', 5, '1', '1' ), result[1] ) @mock.patch('mistralclient.commands.v2.cron_triggers.Create.' '_convert_time_string_to_utc') @mock.patch('argparse.open', create=True) def test_create_no_utc(self, mock_open, mock_convert): self.client.cron_triggers.create.return_value = TRIGGER mock_open.return_value = mock.MagicMock(spec=open) mock_convert.return_value = '4242-12-20 18:37' result = self.call( cron_triggers_cmd.Create, app_args=['my_trigger', 'flow1', '--pattern', '* * * * *', '--params', '{}', '--count', '5', '--first-time', '4242-12-20 13:37'] ) mock_convert.assert_called_once_with('4242-12-20 13:37') self.client.cron_triggers.create.assert_called_once_with( 'my_trigger', 'flow1', {}, {}, '* * * * *', '4242-12-20 18:37', 5) self.assertEqual( ( 'my_trigger', 'flow1', {}, '* * * * *', '4242-12-20 13:37', 5, '1', '1' ), result[1] ) @mock.patch('mistralclient.commands.v2.cron_triggers.time') def test_convert_time_string_to_utc_from_utc(self, mock_time): cmd = cron_triggers_cmd.Create(self.app, None) mock_time.daylight = 0 mock_time.altzone = 0 mock_time.timezone = 0 mock_localtime = mock.Mock() mock_localtime.tm_isdst = 0 mock_time.localtime.return_value = mock_localtime utc_value = cmd._convert_time_string_to_utc('4242-12-20 13:37') expected_time = '4242-12-20 13:37' self.assertEqual(expected_time, utc_value) @mock.patch('mistralclient.commands.v2.cron_triggers.time') def test_convert_time_string_to_utc_from_dst(self, mock_time): cmd = cron_triggers_cmd.Create(self.app, None) mock_time.daylight = 1 mock_time.altzone = (4 * 60 * 60) mock_time.timezone = (5 * 60 * 60) mock_localtime = mock.Mock() mock_localtime.tm_isdst = 1 mock_time.localtime.return_value = mock_localtime utc_value = cmd._convert_time_string_to_utc('4242-12-20 13:37') expected_time = '4242-12-20 17:37' self.assertEqual(expected_time, utc_value) @mock.patch('mistralclient.commands.v2.cron_triggers.time') def test_convert_time_string_to_utc_no_dst(self, mock_time): cmd = cron_triggers_cmd.Create(self.app, None) mock_time.daylight = 1 mock_time.altzone = (4 * 60 * 60) mock_time.timezone = (5 * 60 * 60) mock_localtime = mock.Mock() mock_localtime.tm_isdst = 0 mock_time.localtime.return_value = mock_localtime utc_value = cmd._convert_time_string_to_utc('4242-12-20 13:37') expected_time = '4242-12-20 18:37' self.assertEqual(expected_time, utc_value) def test_list(self): self.client.cron_triggers.list.return_value = [TRIGGER] result = self.call(cron_triggers_cmd.List) self.assertEqual( [( 'my_trigger', 'flow1', {}, '* * * * *', '4242-12-20 13:37', 5, '1', '1' )], result[1] ) def test_get(self): self.client.cron_triggers.get.return_value = TRIGGER result = self.call(cron_triggers_cmd.Get, app_args=['name']) self.assertEqual( ( 'my_trigger', 'flow1', {}, '* * * * *', '4242-12-20 13:37', 5, '1', '1' ), result[1] ) def test_delete(self): self.call(cron_triggers_cmd.Delete, app_args=['name']) self.client.cron_triggers.delete.assert_called_once_with('name') def test_delete_with_multi_names(self): self.call(cron_triggers_cmd.Delete, app_args=['name1', 'name2']) self.assertEqual(2, self.client.cron_triggers.delete.call_count) self.assertEqual( [mock.call('name1'), mock.call('name2')], self.client.cron_triggers.delete.call_args_list ) python-mistralclient-3.3.0/mistralclient/tests/unit/v2/test_workflows.py0000666000175100017510000001417213241060623026726 0ustar zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import pkg_resources as pkg from six.moves.urllib import parse from six.moves.urllib import request from mistralclient.api.v2 import workflows from mistralclient.tests.unit.v2 import base WF_DEF = """ --- version: 2.0 my_wf: type: direct tasks: task1: action: std.echo output="hello, world" """ WORKFLOW = { 'id': '123', 'name': 'my_wf', 'input': '', 'definition': WF_DEF } URL_TEMPLATE = '/workflows' URL_TEMPLATE_SCOPE = '/workflows?scope=private' URL_TEMPLATE_NAME = '/workflows/%s' class TestWorkflowsV2(base.BaseClientV2Test): def test_create(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE_SCOPE, json={'workflows': [WORKFLOW]}, status_code=201) wfs = self.workflows.create(WF_DEF) self.assertIsNotNone(wfs) self.assertEqual(WF_DEF, wfs[0].definition) last_request = self.requests_mock.last_request self.assertEqual(WF_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_create_with_file(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE_SCOPE, json={'workflows': [WORKFLOW]}, status_code=201) # The contents of wf_v2.yaml must be identical to WF_DEF path = pkg.resource_filename( 'mistralclient', 'tests/unit/resources/wf_v2.yaml' ) wfs = self.workflows.create(path) self.assertIsNotNone(wfs) self.assertEqual(WF_DEF, wfs[0].definition) last_request = self.requests_mock.last_request self.assertEqual(WF_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_update(self): self.requests_mock.put(self.TEST_URL + URL_TEMPLATE_SCOPE, json={'workflows': [WORKFLOW]}) wfs = self.workflows.update(WF_DEF) self.assertIsNotNone(wfs) self.assertEqual(WF_DEF, wfs[0].definition) last_request = self.requests_mock.last_request self.assertEqual(WF_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_update_with_id(self): self.requests_mock.put(self.TEST_URL + URL_TEMPLATE_NAME % '123', json=WORKFLOW) wf = self.workflows.update(WF_DEF, id='123') self.assertIsNotNone(wf) self.assertEqual(WF_DEF, wf.definition) last_request = self.requests_mock.last_request self.assertEqual('namespace=&scope=private', last_request.query) self.assertEqual(WF_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_update_with_file_uri(self): self.requests_mock.put(self.TEST_URL + URL_TEMPLATE_SCOPE, json={'workflows': [WORKFLOW]}) # The contents of wf_v2.yaml must be identical to WF_DEF path = pkg.resource_filename( 'mistralclient', 'tests/unit/resources/wf_v2.yaml' ) # Convert the file path to file URI uri = parse.urljoin('file:', request.pathname2url(path)) wfs = self.workflows.update(uri) self.assertIsNotNone(wfs) self.assertEqual(WF_DEF, wfs[0].definition) last_request = self.requests_mock.last_request self.assertEqual(WF_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_list(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'workflows': [WORKFLOW]}) workflows_list = self.workflows.list() self.assertEqual(1, len(workflows_list)) wf = workflows_list[0] self.assertEqual( workflows.Workflow(self.workflows, WORKFLOW).to_dict(), wf.to_dict() ) def test_list_with_pagination(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'workflows': [WORKFLOW], 'next': '/workflows?fake'}) workflows_list = self.workflows.list( limit=1, sort_keys='created_at', sort_dirs='asc' ) self.assertEqual(1, len(workflows_list)) last_request = self.requests_mock.last_request # The url param order is unpredictable. self.assertEqual(['1'], last_request.qs['limit']) self.assertEqual(['created_at'], last_request.qs['sort_keys']) self.assertEqual(['asc'], last_request.qs['sort_dirs']) def test_list_with_no_limit(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'workflows': [WORKFLOW]}) workflows_list = self.workflows.list(limit=-1) self.assertEqual(1, len(workflows_list)) last_request = self.requests_mock.last_request self.assertNotIn('limit', last_request.qs) def test_get(self): url = self.TEST_URL + URL_TEMPLATE_NAME % 'wf' self.requests_mock.get(url, json=WORKFLOW) wf = self.workflows.get('wf') self.assertIsNotNone(wf) self.assertEqual( workflows.Workflow(self.workflows, WORKFLOW).to_dict(), wf.to_dict() ) def test_delete(self): url = self.TEST_URL + URL_TEMPLATE_NAME % 'wf' self.requests_mock.delete(url, status_code=204) self.workflows.delete('wf') python-mistralclient-3.3.0/mistralclient/tests/unit/v2/test_actions.py0000666000175100017510000002175413241060623026335 0ustar zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import pkg_resources as pkg from six.moves.urllib import parse from six.moves.urllib import request from mistralclient.api import base as api_base from mistralclient.api.v2 import actions from mistralclient.tests.unit.v2 import base ACTION_DEF = """ --- version: 2.0 my_action: base: std.echo base-input: output: 'Bye!' output: info: <% $.output %> """ INVALID_ACTION_DEF = """ --- version: 2.0 my_action: base: std.echo unexpected-property: 'this should fail' base-input: output: 'Bye!' output: info: <% $.output %> """ ACTION = { 'id': '123', 'name': 'my_action', 'input': '', 'definition': ACTION_DEF } URL_TEMPLATE = '/actions' URL_TEMPLATE_SCOPE = '/actions?scope=private' URL_TEMPLATE_NAME = '/actions/%s' URL_TEMPLATE_VALIDATE = '/actions/validate' class TestActionsV2(base.BaseClientV2Test): def test_create(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE, json={'actions': [ACTION]}, status_code=201) actions = self.actions.create(ACTION_DEF) self.assertIsNotNone(actions) self.assertEqual(ACTION_DEF, actions[0].definition) last_request = self.requests_mock.last_request self.assertEqual('text/plain', last_request.headers['content-type']) self.assertEqual(ACTION_DEF, last_request.text) def test_create_with_file(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE, json={'actions': [ACTION]}, status_code=201) # The contents of action_v2.yaml must be identical to ACTION_DEF path = pkg.resource_filename( 'mistralclient', 'tests/unit/resources/action_v2.yaml' ) actions = self.actions.create(path) self.assertIsNotNone(actions) self.assertEqual(ACTION_DEF, actions[0].definition) last_request = self.requests_mock.last_request self.assertEqual('text/plain', last_request.headers['content-type']) self.assertEqual(ACTION_DEF, last_request.text) def test_update_with_id(self): self.requests_mock.put(self.TEST_URL + URL_TEMPLATE_NAME % 123, json={'actions': [ACTION]}) actions = self.actions.update(ACTION_DEF, id=123) self.assertIsNotNone(actions) self.assertEqual(ACTION_DEF, actions[0].definition) last_request = self.requests_mock.last_request self.assertEqual('scope=private', last_request.query) self.assertEqual('text/plain', last_request.headers['content-type']) self.assertEqual(ACTION_DEF, last_request.text) def test_update(self): self.requests_mock.put(self.TEST_URL + URL_TEMPLATE, json={'actions': [ACTION]}) actions = self.actions.update(ACTION_DEF) self.assertIsNotNone(actions) self.assertEqual(ACTION_DEF, actions[0].definition) last_request = self.requests_mock.last_request self.assertEqual('scope=private', last_request.query) self.assertEqual('text/plain', last_request.headers['content-type']) self.assertEqual(ACTION_DEF, last_request.text) def test_update_with_file_uri(self): self.requests_mock.put(self.TEST_URL + URL_TEMPLATE, json={'actions': [ACTION]}) # The contents of action_v2.yaml must be identical to ACTION_DEF path = pkg.resource_filename( 'mistralclient', 'tests/unit/resources/action_v2.yaml' ) # Convert the file path to file URI uri = parse.urljoin('file:', request.pathname2url(path)) actions = self.actions.update(uri) self.assertIsNotNone(actions) self.assertEqual(ACTION_DEF, actions[0].definition) last_request = self.requests_mock.last_request self.assertEqual('scope=private', last_request.query) self.assertEqual('text/plain', last_request.headers['content-type']) self.assertEqual(ACTION_DEF, last_request.text) def test_list(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'actions': [ACTION]}) action_list = self.actions.list() self.assertEqual(1, len(action_list)) action = action_list[0] self.assertEqual( actions.Action(self.actions, ACTION).to_dict(), action.to_dict() ) def test_list_with_pagination(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'actions': [ACTION], 'next': '/actions?fake'}) action_list = self.actions.list( limit=1, sort_keys='created_at', sort_dirs='asc' ) self.assertEqual(1, len(action_list)) last_request = self.requests_mock.last_request # The url param order is unpredictable. self.assertEqual(['1'], last_request.qs['limit']) self.assertEqual(['created_at'], last_request.qs['sort_keys']) self.assertEqual(['asc'], last_request.qs['sort_dirs']) def test_list_with_no_limit(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'actions': [ACTION]}) action_list = self.actions.list(limit=-1) self.assertEqual(1, len(action_list)) last_request = self.requests_mock.last_request self.assertNotIn('limit', last_request.qs) def test_get(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE_NAME % 'action', json=ACTION) action = self.actions.get('action') self.assertIsNotNone(action) self.assertEqual( actions.Action(self.actions, ACTION).to_dict(), action.to_dict() ) def test_delete(self): url = self.TEST_URL + URL_TEMPLATE_NAME % 'action' m = self.requests_mock.delete(url, status_code=204) self.actions.delete('action') self.assertEqual(1, m.call_count) def test_validate(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE_VALIDATE, json={'valid': True}) result = self.actions.validate(ACTION_DEF) self.assertIsNotNone(result) self.assertIn('valid', result) self.assertTrue(result['valid']) last_request = self.requests_mock.last_request self.assertEqual(ACTION_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_validate_with_file(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE_VALIDATE, json={'valid': True}) # The contents of action_v2.yaml must be identical to ACTION_DEF path = pkg.resource_filename( 'mistralclient', 'tests/unit/resources/action_v2.yaml' ) result = self.actions.validate(path) self.assertIsNotNone(result) self.assertIn('valid', result) self.assertTrue(result['valid']) last_request = self.requests_mock.last_request self.assertEqual(ACTION_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_validate_failed(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE_VALIDATE, json={"valid": False, "error": "mocked error message"}) result = self.actions.validate(INVALID_ACTION_DEF) self.assertIsNotNone(result) self.assertIn('valid', result) self.assertFalse(result['valid']) self.assertIn('error', result) self.assertIn("mocked error message", result['error']) last_request = self.requests_mock.last_request self.assertEqual('text/plain', last_request.headers['content-type']) def test_validate_api_failed(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE_VALIDATE, status_code=500, json={}) self.assertRaises( api_base.APIException, self.actions.validate, ACTION_DEF ) last_request = self.requests_mock.last_request self.assertEqual('text/plain', last_request.headers['content-type']) self.assertEqual(ACTION_DEF, last_request.text) python-mistralclient-3.3.0/mistralclient/tests/unit/v2/test_environments.py0000666000175100017510000001261613241060623027421 0ustar zuulzuul00000000000000# Copyright 2015 - StackStorm, 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 copy import json import pkg_resources as pkg from six.moves.urllib import parse from six.moves.urllib import request from mistralclient.api import base as api_base from mistralclient.api.v2 import environments from mistralclient.tests.unit.v2 import base from mistralclient import utils ENVIRONMENT = { 'name': 'env1', 'description': 'Test Environment #1', 'scope': 'private', 'variables': { 'server': 'localhost' } } URL_TEMPLATE = '/environments' URL_TEMPLATE_NAME = '/environments/%s' class TestEnvironmentsV2(base.BaseClientV2Test): def test_create(self): data = copy.deepcopy(ENVIRONMENT) self.requests_mock.post(self.TEST_URL + URL_TEMPLATE, status_code=201, json=data) env = self.environments.create(**data) self.assertIsNotNone(env) expected_data = copy.deepcopy(data) expected_data['variables'] = json.dumps(expected_data['variables']) self.assertEqual(expected_data, self.requests_mock.last_request.json()) def test_create_with_json_file_uri(self): # The contents of env_v2.json must be equivalent to ENVIRONMENT path = pkg.resource_filename( 'mistralclient', 'tests/unit/resources/env_v2.json' ) # Convert the file path to file URI uri = parse.urljoin('file:', request.pathname2url(path)) data = collections.OrderedDict( utils.load_content( utils.get_contents_if_file(uri) ) ) self.requests_mock.post(self.TEST_URL + URL_TEMPLATE, status_code=201, json=data) file_input = {'file': uri} env = self.environments.create(**file_input) self.assertIsNotNone(env) expected_data = copy.deepcopy(data) expected_data['variables'] = json.dumps(expected_data['variables']) self.assertEqual(expected_data, self.requests_mock.last_request.json()) def test_create_without_name(self): data = copy.deepcopy(ENVIRONMENT) data.pop('name') e = self.assertRaises(api_base.APIException, self.environments.create, **data) self.assertEqual(400, e.error_code) def test_update(self): data = copy.deepcopy(ENVIRONMENT) self.requests_mock.put(self.TEST_URL + URL_TEMPLATE, json=data) env = self.environments.update(**data) self.assertIsNotNone(env) expected_data = copy.deepcopy(data) expected_data['variables'] = json.dumps(expected_data['variables']) self.assertEqual(expected_data, self.requests_mock.last_request.json()) def test_update_with_yaml_file(self): # The contents of env_v2.json must be equivalent to ENVIRONMENT path = pkg.resource_filename( 'mistralclient', 'tests/unit/resources/env_v2.json' ) data = collections.OrderedDict( utils.load_content( utils.get_contents_if_file(path) ) ) self.requests_mock.put(self.TEST_URL + URL_TEMPLATE, json=data) file_input = {'file': path} env = self.environments.update(**file_input) self.assertIsNotNone(env) expected_data = copy.deepcopy(data) expected_data['variables'] = json.dumps(expected_data['variables']) self.assertEqual(expected_data, self.requests_mock.last_request.json()) def test_update_without_name(self): data = copy.deepcopy(ENVIRONMENT) data.pop('name') e = self.assertRaises(api_base.APIException, self.environments.update, **data) self.assertEqual(400, e.error_code) def test_list(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'environments': [ENVIRONMENT]}) environment_list = self.environments.list() self.assertEqual(1, len(environment_list)) env = environment_list[0] self.assertDictEqual( environments.Environment(self.environments, ENVIRONMENT).to_dict(), env.to_dict() ) def test_get(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE_NAME % 'env', json=ENVIRONMENT) env = self.environments.get('env') self.assertIsNotNone(env) self.assertDictEqual( environments.Environment(self.environments, ENVIRONMENT).to_dict(), env.to_dict() ) def test_delete(self): self.requests_mock.delete(self.TEST_URL + URL_TEMPLATE_NAME % 'env', status_code=204) self.environments.delete('env') python-mistralclient-3.3.0/mistralclient/tests/unit/v2/test_keystone.py0000666000175100017510000000505613241060623026533 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 mistralclient.auth.keystone from mistralclient.tests.unit.v2 import base class TestKeystone(base.BaseClientV2Test): def setUp(self): super(TestKeystone, self).setUp() self.keystone = mistralclient.auth.keystone.KeystoneAuthHandler() def test_get_auth_token(self): auth = self.keystone._get_auth( auth_token="token", auth_url="url", project_id="project_id", ) self.assertEqual("url", auth.auth_url) elements = auth.get_cache_id_elements() self.assertIsNotNone(elements["token"]) self.assertIsNotNone(elements["project_id"]) def test_remove_domain(self): params = { "param1": "p", "target_param2": "p2", "user_domain_param3": "p3", "target_project_domain_param4": "p4" } dedomained = self.keystone._remove_domain(params) self.assertIn("param1", dedomained) self.assertIn("target_param2", dedomained) self.assertNotIn("user_domain_param3", dedomained) self.assertNotIn("target_project_domain_param4", dedomained) def test_separate_target_reqs(self): params = { "a": 1, "target_b": 2, "c": 3, "target_d": 4, "target_target": 5, "param_target": 6 } nontarget, target = self.keystone._separate_target_reqs(params) self.assertIn("a", nontarget) self.assertIn("c", nontarget) self.assertIn("param_target", nontarget) self.assertIn("b", target) self.assertIn("d", target) self.assertIn("target", target) def test_verify(self): self.assertTrue(self.keystone._verification_needed("", False)) self.assertFalse(self.keystone._verification_needed("", True)) self.assertFalse(self.keystone._verification_needed("cert", True)) self.assertEqual( self.keystone._verification_needed("cert", False), "cert" ) python-mistralclient-3.3.0/mistralclient/tests/unit/v2/test_action_executions.py0000666000175100017510000001054213241060623030411 0ustar zuulzuul00000000000000# Copyright 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. from mistralclient.api.v2 import action_executions from mistralclient.tests.unit.v2 import base # TODO(everyone): later we need additional tests verifying all the errors etc. ACTION_EXEC = { 'id': "1", 'name': 'my_action_execution', 'workflow_name': 'my_wf', 'state': 'RUNNING', } URL_TEMPLATE = '/action_executions' URL_TEMPLATE_ID = '/action_executions/%s' class TestActionExecutions(base.BaseClientV2Test): def test_create(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE, json=ACTION_EXEC, status_code=201) body = { 'name': ACTION_EXEC['name'] } action_execution = self.action_executions.create( 'my_action_execution', {} ) self.assertIsNotNone(action_execution) self.assertEqual(action_executions.ActionExecution( self.action_executions, ACTION_EXEC ).to_dict(), action_execution.to_dict()) self.assertEqual(body, self.requests_mock.last_request.json()) def test_update(self): url = self.TEST_URL + URL_TEMPLATE_ID % ACTION_EXEC['id'] self.requests_mock.put(url, json=ACTION_EXEC) body = { 'state': ACTION_EXEC['state'] } action_execution = self.action_executions.update( ACTION_EXEC['id'], ACTION_EXEC['state'] ) self.assertIsNotNone(action_execution) expected = action_executions.ActionExecution( self.action_executions, ACTION_EXEC ).to_dict() self.assertEqual( expected, action_execution.to_dict() ) self.assertEqual(body, self.requests_mock.last_request.json()) def test_list(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'action_executions': [ACTION_EXEC]}) action_execution_list = self.action_executions.list() self.assertEqual(1, len(action_execution_list)) action_execution = action_execution_list[0] expected = action_executions.ActionExecution( self.action_executions, ACTION_EXEC ).to_dict() self.assertEqual(expected, action_execution.to_dict()) def test_list_with_limit(self): self.requests_mock.get( self.TEST_URL + URL_TEMPLATE, json={'action_executions': [ACTION_EXEC]} ) action_execution_list = self.action_executions.list(limit=1) self.assertEqual(1, len(action_execution_list)) last_request = self.requests_mock.last_request # Make sure that limit is passed to the server correctly. self.assertEqual(['1'], last_request.qs['limit']) def test_list_with_no_limit(self): self.requests_mock.get( self.TEST_URL + URL_TEMPLATE, json={'action_executions': [ACTION_EXEC]} ) action_execution_list = self.action_executions.list(limit=-1) self.assertEqual(1, len(action_execution_list)) last_request = self.requests_mock.last_request self.assertNotIn('limit', last_request.qs) def test_get(self): url = self.TEST_URL + URL_TEMPLATE_ID % ACTION_EXEC['id'] self.requests_mock.get(url, json=ACTION_EXEC) action_execution = self.action_executions.get(ACTION_EXEC['id']) expected = action_executions.ActionExecution( self.action_executions, ACTION_EXEC ).to_dict() self.assertEqual(expected, action_execution.to_dict()) def test_delete(self): url = self.TEST_URL + URL_TEMPLATE_ID % ACTION_EXEC['id'] self.requests_mock.delete(url, status_code=204) self.action_executions.delete(ACTION_EXEC['id']) python-mistralclient-3.3.0/mistralclient/tests/unit/v2/base.py0000666000175100017510000000317213241060623024542 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import mock from mistralclient.api.v2 import client from mistralclient.tests.unit import base class BaseClientV2Test(base.BaseClientTest): TEST_URL = 'http://mistral.example.com' def setUp(self): super(BaseClientV2Test, self).setUp() with mock.patch( 'mistralclient.auth.keystone.KeystoneAuthHandler.authenticate', return_value={'session': None}): self._client = client.Client(project_name="test", mistral_url=self.TEST_URL) self.workbooks = self._client.workbooks self.executions = self._client.executions self.tasks = self._client.tasks self.workflows = self._client.workflows self.environments = self._client.environments self.action_executions = self._client.action_executions self.actions = self._client.actions self.services = self._client.services self.members = self._client.members python-mistralclient-3.3.0/mistralclient/tests/unit/v2/test_workbooks.py0000666000175100017510000001647213241060623026716 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, 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 pkg_resources as pkg from six.moves.urllib import parse from six.moves.urllib import request from mistralclient.api import base as api_base from mistralclient.api.v2 import workbooks from mistralclient.tests.unit.v2 import base # TODO(everyone): later we need additional tests verifying all the errors etc. WB_DEF = """ --- version: 2.0 name: wb workflows: wf1: type: direct input: - param1 - param2 tasks: task1: action: std.http url="localhost:8989" on-success: - test_subsequent test_subsequent: action: std.http url="http://some_url" server_id=1 """ INVALID_WB_DEF = """ version: 2.0 name: wb workflows: wf1: type: direct tasks: task1: action: std.http url="localhost:8989" workflow: wf2 """ WORKBOOK = {'definition': WB_DEF} URL_TEMPLATE = '/workbooks' URL_TEMPLATE_NAME = '/workbooks/%s' URL_TEMPLATE_VALIDATE = '/workbooks/validate' class TestWorkbooksV2(base.BaseClientV2Test): def test_create(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE, json=WORKBOOK, status_code=201) wb = self.workbooks.create(WB_DEF) self.assertIsNotNone(wb) self.assertEqual(WB_DEF, wb.definition) last_request = self.requests_mock.last_request self.assertEqual(WB_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_create_with_file_uri(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE, json=WORKBOOK, status_code=201) # The contents of wb_v2.yaml must be identical to WB_DEF path = pkg.resource_filename( 'mistralclient', 'tests/unit/resources/wb_v2.yaml' ) # Convert the file path to file URI uri = parse.urljoin('file:', request.pathname2url(path)) wb = self.workbooks.create(uri) self.assertIsNotNone(wb) self.assertEqual(WB_DEF, wb.definition) last_request = self.requests_mock.last_request self.assertEqual(WB_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_update(self): self.requests_mock.put(self.TEST_URL + URL_TEMPLATE, json=WORKBOOK) wb = self.workbooks.update(WB_DEF) self.assertIsNotNone(wb) self.assertEqual(WB_DEF, wb.definition) last_request = self.requests_mock.last_request self.assertEqual(WB_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_update_with_file(self): self.requests_mock.put(self.TEST_URL + URL_TEMPLATE, json=WORKBOOK) # The contents of wb_v2.yaml must be identical to WB_DEF path = pkg.resource_filename( 'mistralclient', 'tests/unit/resources/wb_v2.yaml' ) wb = self.workbooks.update(path) self.assertIsNotNone(wb) self.assertEqual(WB_DEF, wb.definition) last_request = self.requests_mock.last_request self.assertEqual(WB_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_list(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'workbooks': [WORKBOOK]}) workbook_list = self.workbooks.list() self.assertEqual(1, len(workbook_list)) wb = workbook_list[0] self.assertEqual( workbooks.Workbook(self.workbooks, WORKBOOK).to_dict(), wb.to_dict() ) def test_get(self): url = self.TEST_URL + URL_TEMPLATE_NAME % 'wb' self.requests_mock.get(url, json=WORKBOOK) wb = self.workbooks.get('wb') self.assertIsNotNone(wb) self.assertEqual( workbooks.Workbook(self.workbooks, WORKBOOK).to_dict(), wb.to_dict() ) def test_delete(self): url = self.TEST_URL + URL_TEMPLATE_NAME % 'wb' self.requests_mock.delete(url, status_code=204) self.workbooks.delete('wb') def test_validate(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE_VALIDATE, json={'valid': True}) result = self.workbooks.validate(WB_DEF) self.assertIsNotNone(result) self.assertIn('valid', result) self.assertTrue(result['valid']) last_request = self.requests_mock.last_request self.assertEqual(WB_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_validate_with_file(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE_VALIDATE, json={'valid': True}) # The contents of wb_v2.yaml must be identical to WB_DEF path = pkg.resource_filename( 'mistralclient', 'tests/unit/resources/wb_v2.yaml' ) result = self.workbooks.validate(path) self.assertIsNotNone(result) self.assertIn('valid', result) self.assertTrue(result['valid']) last_request = self.requests_mock.last_request self.assertEqual(WB_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_validate_failed(self): mock_result = { "valid": False, "error": "Task properties 'action' and 'workflow' " "can't be specified both" } self.requests_mock.post(self.TEST_URL + URL_TEMPLATE_VALIDATE, json=mock_result) result = self.workbooks.validate(INVALID_WB_DEF) self.assertIsNotNone(result) self.assertIn('valid', result) self.assertFalse(result['valid']) self.assertIn('error', result) self.assertIn( "Task properties 'action' and 'workflow' " "can't be specified both", result['error'] ) last_request = self.requests_mock.last_request self.assertEqual(INVALID_WB_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_validate_api_failed(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE_VALIDATE, status_code=500) self.assertRaises( api_base.APIException, self.workbooks.validate, WB_DEF ) last_request = self.requests_mock.last_request self.assertEqual(WB_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) python-mistralclient-3.3.0/mistralclient/tests/unit/v2/test_cli_services.py0000666000175100017510000000226213241060623027340 0ustar zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import mock from mistralclient.api.v2 import services from mistralclient.commands.v2 import services as service_cmd from mistralclient.tests.unit import base SERVICE_DICT = { 'name': 'service_name', 'type': 'service_type', } SERVICE = services.Service(mock, SERVICE_DICT) class TestCLIServicesV2(base.BaseCommandTest): def test_list(self): self.client.services.list.return_value = [SERVICE] expected = (SERVICE_DICT['name'], SERVICE_DICT['type'],) result = self.call(service_cmd.List) self.assertListEqual([expected], result[1]) python-mistralclient-3.3.0/mistralclient/tests/unit/v2/test_tasks.py0000666000175100017510000000767513241060623026030 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, 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 from mistralclient.api.v2 import tasks from mistralclient.tests.unit.v2 import base # TODO(everyone): later we need additional tests verifying all the errors etc. TASK = { 'id': "1", 'workflow_execution_id': '123', 'name': 'my_task', 'workflow_name': 'my_wf', 'state': 'RUNNING', 'tags': ['deployment', 'demo'], 'result': {'some': 'result'} } URL_TEMPLATE = '/tasks' URL_TEMPLATE_ID = '/tasks/%s' class TestTasksV2(base.BaseClientV2Test): def test_list(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'tasks': [TASK]}) task_list = self.tasks.list() self.assertEqual(1, len(task_list)) task = task_list[0] self.assertEqual( tasks.Task(self.tasks, TASK).to_dict(), task.to_dict() ) def test_list_with_fields(self): field_params = "?fields=id,name" self.requests_mock.get(self.TEST_URL + URL_TEMPLATE + field_params, json={'tasks': [TASK]}) self.tasks.list(fields=["id,name"]) self.assertTrue(self.requests_mock.called_once) def test_list_with_no_limit(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'tasks': [TASK]}) task_list = self.tasks.list(limit=-1) self.assertEqual(1, len(task_list)) last_request = self.requests_mock.last_request self.assertNotIn('limit', last_request.qs) def test_get(self): url = self.TEST_URL + URL_TEMPLATE_ID % TASK['id'] self.requests_mock.get(url, json=TASK) task = self.tasks.get(TASK['id']) self.assertEqual( tasks.Task(self.tasks, TASK).to_dict(), task.to_dict() ) def test_rerun(self): url = self.TEST_URL + URL_TEMPLATE_ID % TASK['id'] self.requests_mock.put(url, json=TASK) task = self.tasks.rerun(TASK['id']) self.assertDictEqual( tasks.Task(self.tasks, TASK).to_dict(), task.to_dict() ) body = { 'reset': True, 'state': 'RUNNING', 'id': TASK['id'] } self.assertDictEqual(body, self.requests_mock.last_request.json()) def test_rerun_no_reset(self): url = self.TEST_URL + URL_TEMPLATE_ID % TASK['id'] self.requests_mock.put(url, json=TASK) task = self.tasks.rerun(TASK['id'], reset=False) self.assertDictEqual( tasks.Task(self.tasks, TASK).to_dict(), task.to_dict() ) body = { 'reset': False, 'state': 'RUNNING', 'id': TASK['id'] } self.assertDictEqual(body, self.requests_mock.last_request.json()) def test_rerun_update_env(self): url = self.TEST_URL + URL_TEMPLATE_ID % TASK['id'] self.requests_mock.put(url, json=TASK) task = self.tasks.rerun(TASK['id'], env={'k1': 'foobar'}) self.assertDictEqual( tasks.Task(self.tasks, TASK).to_dict(), task.to_dict() ) body = { 'reset': True, 'state': 'RUNNING', 'id': TASK['id'], 'env': json.dumps({'k1': 'foobar'}) } self.assertDictEqual(body, self.requests_mock.last_request.json()) python-mistralclient-3.3.0/mistralclient/tests/unit/v2/test_cli_workbooks.py0000666000175100017510000000705113241060623027536 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import mock from mistralclient.api.v2 import workbooks from mistralclient.commands.v2 import workbooks as workbook_cmd from mistralclient.tests.unit import base WORKBOOK_DICT = { 'name': 'a', 'tags': ['a', 'b'], 'created_at': '1', 'updated_at': '1' } WB_DEF = """ --- version: '2.0 name: wb workflows: wf1: tasks: task1: action: nova.servers_get server="1" """ WB_WITH_DEF_DICT = WORKBOOK_DICT.copy() WB_WITH_DEF_DICT.update({'definition': WB_DEF}) WORKBOOK = workbooks.Workbook(mock, WORKBOOK_DICT) WORKBOOK_WITH_DEF = workbooks.Workbook(mock, WB_WITH_DEF_DICT) class TestCLIWorkbooksV2(base.BaseCommandTest): @mock.patch('argparse.open', create=True) def test_create(self, mock_open): self.client.workbooks.create.return_value = WORKBOOK result = self.call(workbook_cmd.Create, app_args=['wb.yaml']) self.assertEqual(('a', 'a, b', '1', '1'), result[1]) @mock.patch('argparse.open', create=True) def test_update(self, mock_open): self.client.workbooks.update.return_value = WORKBOOK result = self.call(workbook_cmd.Update, app_args=['definition']) self.assertEqual(('a', 'a, b', '1', '1'), result[1]) def test_list(self): self.client.workbooks.list.return_value = [WORKBOOK] result = self.call(workbook_cmd.List) self.assertEqual([('a', 'a, b', '1', '1')], result[1]) def test_get(self): self.client.workbooks.get.return_value = WORKBOOK result = self.call(workbook_cmd.Get, app_args=['name']) self.assertEqual(('a', 'a, b', '1', '1'), result[1]) def test_delete(self): self.call(workbook_cmd.Delete, app_args=['name']) self.client.workbooks.delete.assert_called_once_with('name') def test_delete_with_multi_names(self): self.call(workbook_cmd.Delete, app_args=['name1', 'name2']) self.assertEqual(2, self.client.workbooks.delete.call_count) self.assertEqual( [mock.call('name1'), mock.call('name2')], self.client.workbooks.delete.call_args_list ) def test_get_definition(self): self.client.workbooks.get.return_value = WORKBOOK_WITH_DEF self.call(workbook_cmd.GetDefinition, app_args=['name']) self.app.stdout.write.assert_called_with(WB_DEF) @mock.patch('argparse.open', create=True) def test_validate(self, mock_open): self.client.workbooks.validate.return_value = {'valid': True} result = self.call(workbook_cmd.Validate, app_args=['wb.yaml']) self.assertEqual((True, None), result[1]) @mock.patch('argparse.open', create=True) def test_validate_failed(self, mock_open): self.client.workbooks.validate.return_value = { 'valid': False, 'error': 'Invalid DSL...' } result = self.call(workbook_cmd.Validate, app_args=['wb.yaml']) self.assertEqual((False, 'Invalid DSL...'), result[1]) python-mistralclient-3.3.0/mistralclient/tests/unit/v2/test_cli_executions.py0000666000175100017510000001665213241060623027713 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, Inc. # Copyright 2016 - Brocade Communications Systems, Inc. # # 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 pkg_resources as pkg import six import sys from mistralclient.api.v2 import executions from mistralclient.commands.v2 import executions as execution_cmd from mistralclient.tests.unit import base EXEC = executions.Execution( mock, { 'id': '123', 'workflow_id': '123e4567-e89b-12d3-a456-426655440000', 'workflow_name': 'some', 'workflow_namespace': '', 'description': '', 'state': 'RUNNING', 'state_info': None, 'created_at': '1', 'updated_at': '1', 'task_execution_id': None } ) SUB_WF_EXEC = executions.Execution( mock, { 'id': '456', 'workflow_id': '123e4567-e89b-12d3-a456-426655440000', 'workflow_name': 'some_sub_wf', 'workflow_namespace': '', 'description': '', 'state': 'RUNNING', 'state_info': None, 'created_at': '1', 'updated_at': '1', 'task_execution_id': 'abc' } ) EX_RESULT = ( '123', '123e4567-e89b-12d3-a456-426655440000', 'some', '', '', '', 'RUNNING', None, '1', '1' ) SUB_WF_EX_RESULT = ( '456', '123e4567-e89b-12d3-a456-426655440000', 'some_sub_wf', '', '', 'abc', 'RUNNING', None, '1', '1' ) class TestCLIExecutionsV2(base.BaseCommandTest): stdout = six.moves.StringIO() stderr = six.moves.StringIO() def setUp(self): super(TestCLIExecutionsV2, self).setUp() # Redirect stdout and stderr so it doesn't pollute the test result. sys.stdout = self.stdout sys.stderr = self.stderr def tearDown(self): super(TestCLIExecutionsV2, self).tearDown() # Reset to original stdout and stderr. sys.stdout = sys.__stdout__ sys.stderr = sys.__stderr__ def test_create_wf_input_string(self): self.client.executions.create.return_value = EXEC result = self.call( execution_cmd.Create, app_args=['id', '{ "context": true }'] ) self.assertEqual( EX_RESULT, result[1] ) def test_create_wf_input_file(self): self.client.executions.create.return_value = EXEC path = pkg.resource_filename( 'mistralclient', 'tests/unit/resources/ctx.json' ) result = self.call( execution_cmd.Create, app_args=['id', path] ) self.assertEqual( EX_RESULT, result[1] ) def test_create_with_description(self): self.client.executions.create.return_value = EXEC result = self.call( execution_cmd.Create, app_args=['id', '{ "context": true }', '-d', ''] ) self.assertEqual( EX_RESULT, result[1] ) def test_update_state(self): states = ['RUNNING', 'SUCCESS', 'PAUSED', 'ERROR', 'CANCELLED'] for state in states: self.client.executions.update.return_value = executions.Execution( mock, { 'id': '123', 'workflow_id': '123e4567-e89b-12d3-a456-426655440000', 'workflow_name': 'some', 'workflow_namespace': '', 'description': '', 'state': state, 'state_info': None, 'created_at': '1', 'updated_at': '1', 'task_execution_id': None } ) ex_result = list(EX_RESULT) ex_result[6] = state ex_result = tuple(ex_result) result = self.call( execution_cmd.Update, app_args=['id', '-s', state] ) self.assertEqual( ex_result, result[1] ) def test_update_invalid_state(self): states = ['IDLE', 'WAITING', 'DELAYED'] for state in states: self.assertRaises( SystemExit, self.call, execution_cmd.Update, app_args=['id', '-s', state] ) def test_resume_update_env(self): self.client.executions.update.return_value = EXEC result = self.call( execution_cmd.Update, app_args=['id', '-s', 'RUNNING', '--env', '{"k1": "foobar"}'] ) self.assertEqual( EX_RESULT, result[1] ) def test_update_description(self): self.client.executions.update.return_value = EXEC result = self.call( execution_cmd.Update, app_args=['id', '-d', 'foobar'] ) self.assertEqual( EX_RESULT, result[1] ) def test_list(self): self.client.executions.list.return_value = [EXEC, SUB_WF_EXEC] result = self.call(execution_cmd.List) self.assertEqual( [EX_RESULT, SUB_WF_EX_RESULT], result[1] ) def test_list_with_pagination(self): self.client.executions.list.return_value = [EXEC] self.call(execution_cmd.List) self.client.executions.list.assert_called_once_with( limit=100, marker='', sort_dirs='asc', sort_keys='created_at', task=None ) self.call( execution_cmd.List, app_args=[ '--limit', '5', '--sort_dirs', 'id, Workflow', '--sort_keys', 'desc', '--marker', 'abc' ] ) self.client.executions.list.assert_called_with( limit=5, marker='abc', sort_dirs='id, Workflow', sort_keys='desc', task=None ) def test_get(self): self.client.executions.get.return_value = EXEC result = self.call(execution_cmd.Get, app_args=['id']) self.assertEqual( EX_RESULT, result[1] ) def test_get_sub_wf_ex(self): self.client.executions.get.return_value = SUB_WF_EXEC result = self.call(execution_cmd.Get, app_args=['id']) self.assertEqual( SUB_WF_EX_RESULT, result[1] ) def test_delete(self): self.call(execution_cmd.Delete, app_args=['id']) self.client.executions.delete.assert_called_once_with('id') def test_delete_with_multi_names(self): self.call(execution_cmd.Delete, app_args=['id1', 'id2']) self.assertEqual(2, self.client.executions.delete.call_count) self.assertEqual( [mock.call('id1'), mock.call('id2')], self.client.executions.delete.call_args_list ) python-mistralclient-3.3.0/mistralclient/tests/unit/v2/test_cli_bash_completion.py0000666000175100017510000000163313241060623030664 0ustar zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import mistralclient.tests.unit.base_shell_test as base class TestCLIBashCompletionV2(base.BaseShellTests): def test_bash_completion(self): bash_completion, stderr = self.shell('bash-completion') self.assertIn('bash-completion', bash_completion) self.assertFalse(stderr) python-mistralclient-3.3.0/mistralclient/tests/unit/v2/test_cli_action_execs.py0000666000175100017510000001570513241060623030167 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2016 - Brocade Communications Systems, 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 copy import json import sys from six import StringIO import mock from mistralclient.api.v2 import action_executions as action_ex from mistralclient.commands.v2 import action_executions as action_ex_cmd from mistralclient.tests.unit import base ACTION_EX_DICT = { 'id': '123', 'name': 'some', 'workflow_name': 'thing', 'workflow_namespace': '', 'task_name': 'task1', 'task_execution_id': "1-2-3-4", 'state': 'RUNNING', 'state_info': 'RUNNING somehow.', 'accepted': True, 'created_at': '1', 'updated_at': '1', } ACTION_EX_RESULT = {"test": "is", "passed": "successfully"} ACTION_EX_INPUT = {"param1": "val1", "param2": 2} ACTION_EX_WITH_OUTPUT_DICT = ACTION_EX_DICT.copy() ACTION_EX_WITH_OUTPUT_DICT.update({'output': json.dumps(ACTION_EX_RESULT)}) ACTION_EX_WITH_INPUT_DICT = ACTION_EX_DICT.copy() ACTION_EX_WITH_INPUT_DICT.update({'input': json.dumps(ACTION_EX_INPUT)}) ACTION_EX = action_ex.ActionExecution(mock, ACTION_EX_DICT) ACTION_EX_WITH_OUTPUT = action_ex.ActionExecution( mock, ACTION_EX_WITH_OUTPUT_DICT ) ACTION_EX_WITH_INPUT = action_ex.ActionExecution( mock, ACTION_EX_WITH_INPUT_DICT ) class TestCLIActionExecutions(base.BaseCommandTest): def test_create(self): (self.client.action_executions.create. return_value) = ACTION_EX_WITH_OUTPUT self.call( action_ex_cmd.Create, app_args=['some', '{"output": "Hello!"}'] ) self.assertDictEqual( ACTION_EX_RESULT, json.loads(self.app.stdout.write.call_args[0][0]) ) def test_create_save_result(self): (self.client.action_executions.create. return_value) = ACTION_EX_WITH_OUTPUT result = self.call( action_ex_cmd.Create, app_args=[ 'some', '{"output": "Hello!"}', '--save-result' ] ) self.assertEqual( ('123', 'some', 'thing', '', 'task1', '1-2-3-4', 'RUNNING', 'RUNNING somehow.', True, '1', '1'), result[1] ) def test_create_run_sync(self): (self.client.action_executions.create. return_value) = ACTION_EX_WITH_OUTPUT self.call( action_ex_cmd.Create, app_args=[ 'some', '{"output": "Hello!"}', '--run-sync' ] ) self.assertDictEqual( ACTION_EX_RESULT, json.loads(self.app.stdout.write.call_args[0][0]) ) def test_create_run_sync_and_save_result(self): (self.client.action_executions.create. return_value) = ACTION_EX_WITH_OUTPUT self.call( action_ex_cmd.Create, app_args=[ 'some', '{"output": "Hello!"}', '--save-result', '--run-sync' ] ) self.assertDictEqual( ACTION_EX_RESULT, json.loads(self.app.stdout.write.call_args[0][0]) ) def test_update(self): states = ['IDLE', 'RUNNING', 'SUCCESS', 'ERROR', 'CANCELLED'] for state in states: action_ex_dict = copy.deepcopy(ACTION_EX_DICT) action_ex_dict['state'] = state action_ex_dict['state_info'] = 'testing update' action_ex_obj = action_ex.ActionExecution(mock, action_ex_dict) self.client.action_executions.update.return_value = action_ex_obj result = self.call( action_ex_cmd.Update, app_args=['id', '--state', state] ) expected_result = ( action_ex_dict['id'], action_ex_dict['name'], action_ex_dict['workflow_name'], action_ex_dict['workflow_namespace'], action_ex_dict['task_name'], action_ex_dict['task_execution_id'], action_ex_dict['state'], action_ex_dict['state_info'], action_ex_dict['accepted'], action_ex_dict['created_at'], action_ex_dict['updated_at'] ) self.assertEqual(expected_result, result[1]) def test_update_invalid_state(self): states = ['PAUSED', 'WAITING', 'DELAYED'] # Redirect the stderr so it doesn't show during tox _stderr = sys.stderr sys.stderr = StringIO() for state in states: self.assertRaises( SystemExit, self.call, action_ex_cmd.Update, app_args=['id', '--state', state] ) # Stop the redirection print(sys.stderr.getvalue()) sys.stderr = _stderr def test_list(self): self.client.action_executions.list.return_value = [ACTION_EX] result = self.call(action_ex_cmd.List) self.assertEqual( [('123', 'some', 'thing', '', 'task1', '1-2-3-4', 'RUNNING', True, '1', '1')], result[1] ) def test_get(self): self.client.action_executions.get.return_value = ACTION_EX result = self.call(action_ex_cmd.Get, app_args=['id']) self.assertEqual( ('123', 'some', 'thing', '', 'task1', '1-2-3-4', 'RUNNING', 'RUNNING somehow.', True, '1', '1'), result[1] ) def test_get_output(self): self.client.action_executions.get.return_value = ACTION_EX_WITH_OUTPUT self.call(action_ex_cmd.GetOutput, app_args=['id']) self.assertDictEqual( ACTION_EX_RESULT, json.loads(self.app.stdout.write.call_args[0][0]) ) def test_get_input(self): self.client.action_executions.get.return_value = ACTION_EX_WITH_INPUT self.call(action_ex_cmd.GetInput, app_args=['id']) self.assertDictEqual( ACTION_EX_INPUT, json.loads(self.app.stdout.write.call_args[0][0]) ) def test_delete(self): self.call(action_ex_cmd.Delete, app_args=['id']) self.client.action_executions.delete.assert_called_once_with('id') def test_delete_with_multi_names(self): self.call(action_ex_cmd.Delete, app_args=['id1', 'id2']) self.assertEqual(2, self.client.action_executions.delete.call_count) self.assertEqual( [mock.call('id1'), mock.call('id2')], self.client.action_executions.delete.call_args_list ) python-mistralclient-3.3.0/mistralclient/tests/unit/v2/test_cli_event_triggers.py0000666000175100017510000000623213241060623030545 0ustar zuulzuul00000000000000# Copyright 2014 Mirantis, Inc. # All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import mock from mistralclient.api.v2 import event_triggers from mistralclient.commands.v2 import event_triggers as event_triggers_cmd from mistralclient.tests.unit import base TRIGGER_DICT = { 'id': '456', 'name': 'my_trigger', 'workflow_id': '123e4567-e89b-12d3-a456-426655440000', 'workflow_input': {}, 'workflow_params': {}, 'exchange': 'dummy_exchange', 'topic': 'dummy_topic', 'event': 'event.dummy', 'created_at': '1', 'updated_at': '1' } TRIGGER = event_triggers.EventTrigger(mock, TRIGGER_DICT) class TestCLITriggersV2(base.BaseCommandTest): @mock.patch('argparse.open', create=True) def test_create(self, mock_open): self.client.event_triggers.create.return_value = TRIGGER mock_open.return_value = mock.MagicMock(spec=open) result = self.call( event_triggers_cmd.Create, app_args=['my_trigger', '123e4567-e89b-12d3-a456-426655440000', 'dummy_exchange', 'dummy_topic', 'event.dummy', '--params', '{}'] ) self.assertEqual( ( '456', 'my_trigger', '123e4567-e89b-12d3-a456-426655440000', {}, 'dummy_exchange', 'dummy_topic', 'event.dummy', '1', '1' ), result[1] ) def test_list(self): self.client.event_triggers.list.return_value = [TRIGGER] result = self.call(event_triggers_cmd.List) self.assertEqual( [( '456', 'my_trigger', '123e4567-e89b-12d3-a456-426655440000', {}, 'dummy_exchange', 'dummy_topic', 'event.dummy', '1', '1' )], result[1] ) def test_get(self): self.client.event_triggers.get.return_value = TRIGGER result = self.call(event_triggers_cmd.Get, app_args=['id']) self.assertEqual( ( '456', 'my_trigger', '123e4567-e89b-12d3-a456-426655440000', {}, 'dummy_exchange', 'dummy_topic', 'event.dummy', '1', '1' ), result[1] ) def test_delete(self): self.call(event_triggers_cmd.Delete, app_args=['id']) self.client.event_triggers.delete.assert_called_once_with('id') def test_delete_with_multi_names(self): self.call(event_triggers_cmd.Delete, app_args=['id1', 'id2']) self.assertEqual(2, self.client.event_triggers.delete.call_count) self.assertEqual( [mock.call('id1'), mock.call('id2')], self.client.event_triggers.delete.call_args_list ) python-mistralclient-3.3.0/mistralclient/tests/unit/v2/test_services.py0000666000175100017510000000235113241060623026510 0ustar zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from mistralclient.api.v2 import services from mistralclient.tests.unit.v2 import base SERVICE = { 'name': 'service_name', 'type': 'service_type', } URL_TEMPLATE = '/services' class TestServicesV2(base.BaseClientV2Test): def test_list(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'services': [SERVICE]}) service_list = self.services.list() self.assertEqual(1, len(service_list)) srv = service_list[0] self.assertDictEqual( services.Service(self.services, SERVICE).to_dict(), srv.to_dict() ) python-mistralclient-3.3.0/mistralclient/tests/unit/base.py0000666000175100017510000000242113241060623024207 0ustar zuulzuul00000000000000# Copyright 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 from oslotest import base from requests_mock.contrib import fixture class BaseClientTest(base.BaseTestCase): _client = None def setUp(self): super(BaseClientTest, self).setUp() self.requests_mock = self.useFixture(fixture.Fixture()) class BaseCommandTest(base.BaseTestCase): def setUp(self): super(BaseCommandTest, self).setUp() self.app = mock.Mock() self.client = self.app.client_manager.workflow_engine def call(self, command, app_args=[], prog_name=''): cmd = command(self.app, app_args) parsed_args = cmd.get_parser(prog_name).parse_args(app_args) return cmd.take_action(parsed_args) python-mistralclient-3.3.0/mistralclient/tests/unit/test_shell.py0000666000175100017510000003155413241060623025454 0ustar zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import mock import mistralclient.tests.unit.base_shell_test as base class TestShell(base.BaseShellTests): def test_help(self): """Test that client is not created for help and bash complete""" for command in ('-h', '--help', 'help', 'help workbook-list', 'bash-completion'): with mock.patch('mistralclient.api.client.client') as client_mock: self.shell(command) self.assertFalse(client_mock.called) @mock.patch('mistralclient.api.client.client') def test_command_no_mistral_url(self, client_mock): self.shell( 'workbook-list' ) self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('', params[1]['mistral_url']) @mock.patch('mistralclient.api.client.client') def test_command_with_mistral_url(self, client_mock): self.shell( '--os-mistral-url=http://localhost:8989/v2 workbook-list' ) self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('http://localhost:8989/v2', params[1]['mistral_url']) @mock.patch('mistralclient.api.client.determine_client_version') def test_mistral_version(self, client_mock): self.shell( '--os-mistral-version=v1 workbook-list' ) self.assertTrue(client_mock.called) mistral_version = client_mock.call_args self.assertEqual('v1', mistral_version[0][0]) @mock.patch('mistralclient.api.client.determine_client_version') def test_no_mistral_version(self, client_mock): self.shell('workbook-list') self.assertTrue(client_mock.called) mistral_version = client_mock.call_args self.assertEqual('v2', mistral_version[0][0]) @mock.patch('mistralclient.api.client.client') def test_service_type(self, client_mock): self.shell('--os-mistral-service-type=test workbook-list') self.assertTrue(client_mock.called) parmters = client_mock.call_args self.assertEqual('test', parmters[1]['service_type']) @mock.patch('mistralclient.api.client.client') def test_no_service_type(self, client_mock): self.shell('workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('workflowv2', params[1]['service_type']) @mock.patch('mistralclient.api.client.client') def test_endpoint_type(self, client_mock): self.shell('--os-mistral-endpoint-type=adminURL workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('adminURL', params[1]['endpoint_type']) @mock.patch('mistralclient.api.client.client') def test_no_endpoint_type(self, client_mock): self.shell('workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('publicURL', params[1]['endpoint_type']) @mock.patch('mistralclient.api.client.client') def test_auth_url(self, client_mock): self.shell( '--os-auth-url=https://127.0.0.1:35357/v3 ' '--os-username=admin ' '--os-password=1234 ' 'workbook-list' ) self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('https://127.0.0.1:35357/v3', params[1]['auth_url']) @mock.patch('mistralclient.api.client.client') def test_no_auth_url(self, client_mock): self.shell('workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('', params[1]['auth_url']) @mock.patch('mistralclient.api.client.client') def test_default_auth_url_with_os_password(self, client_mock): self.shell('--os-username=admin --os-password=1234 workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('http://localhost:35357/v3', params[1]['auth_url']) @mock.patch('mistralclient.api.client.client') def test_default_auth_url_with_os_auth_token(self, client_mock): self.shell( '--os-auth-token=abcd1234 ' 'workbook-list' ) self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('http://localhost:35357/v3', params[1]['auth_url']) @mock.patch('mistralclient.api.client.client') def test_profile(self, client_mock): self.shell('--profile=SECRET_HMAC_KEY workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('SECRET_HMAC_KEY', params[1]['profile']) @mock.patch('mistralclient.api.client.client') def test_region_name(self, client_mock): self.shell('--os-region-name=RegionOne workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('RegionOne', params[1]['region_name']) @mock.patch('mistralclient.api.client.client') def test_tenant_id_and_tenant_name(self, client_mock): self.shell( '--os-tenant-id=123tenant --os-tenant-name=fake_tenant' ' workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('fake_tenant', params[1]['project_name']) self.assertEqual('123tenant', params[1]['project_id']) @mock.patch('mistralclient.api.client.client') def test_project_id_and_project_name(self, client_mock): self.shell( '--os-project-name=fake_tenant --os-project-id=123tenant' ' workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('fake_tenant', params[1]['project_name']) self.assertEqual('123tenant', params[1]['project_id']) @mock.patch('mistralclient.api.client.client') def test_project_domain_name(self, client_mock): self.shell('--os-project-domain-name=default workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('default', params[1]['project_domain_name']) @mock.patch('mistralclient.api.client.client') def test_project_domain_id(self, client_mock): self.shell('--os-project-domain-id=default workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('default', params[1]['project_domain_id']) @mock.patch('mistralclient.api.client.client') def test_user_domain_name(self, client_mock): self.shell('--os-user-domain-name=default workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('default', params[1]['user_domain_name']) @mock.patch('mistralclient.api.client.client') def test_user_domain_id(self, client_mock): self.shell('--os-user-domain-id=default workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('default', params[1]['user_domain_id']) @mock.patch('mistralclient.api.client.client') def test_target_user_name_and_password(self, client_mock): self.shell( '--os-target-username=admin' ' --os-target-password=secret_pass workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('admin', params[1]['target_username']) self.assertEqual('secret_pass', params[1]['target_api_key']) @mock.patch('mistralclient.api.client.client') def test_target_tenant_name_and_id(self, client_mock): self.shell( '--os-target-tenant-id=123fake' ' --os-target-tenant-name=fake_target workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('123fake', params[1]['target_project_id']) self.assertEqual('fake_target', params[1]['target_project_name']) @mock.patch('mistralclient.api.client.client') def test_target_user_domain_id(self, client_mock): self.shell('--os-target-user-domain-id=default workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('default', params[1]['target_user_domain_id']) @mock.patch('mistralclient.api.client.client') def test_target_user_domain_name(self, client_mock): self.shell('--os-target-user-domain-name=default workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('default', params[1]['target_user_domain_name']) @mock.patch('mistralclient.api.client.client') def test_target_project_domain_id(self, client_mock): self.shell('--os-target-project-domain-id=default workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('default', params[1]['target_project_domain_id']) @mock.patch('mistralclient.api.client.client') def test_target_project_domain_name(self, client_mock): self.shell('--os-target-project-domain-name=default workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('default', params[1]['target_project_domain_name']) @mock.patch('mistralclient.api.client.client') def test_no_domains_keystone_v3(self, client_mock): self.shell( '--os-auth-url=https://127.0.0.1:35357/v3 ' '--os-username=admin ' '--os-password=1234 ' 'workbook-list' ) self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('https://127.0.0.1:35357/v3', params[1]['auth_url']) # For keystone v3 'default' values are automatically substituted for # project_domain_id and user_domain_id, if nothing was provided self.assertEqual('default', params[1]['project_domain_id']) self.assertEqual('default', params[1]['user_domain_id']) self.assertEqual('default', params[1]['target_project_domain_id']) self.assertEqual('default', params[1]['target_user_domain_id']) @mock.patch('mistralclient.api.client.client') def test_with_domain_names_keystone_v3(self, client_mock): self.shell( '--os-auth-url=https://127.0.0.1:35357/v3 ' '--os-username=admin ' '--os-password=1234 ' '--os-project-domain-name=fake_domain ' '--os-user-domain-name=fake_domain ' '--os-target-project-domain-name=fake_domain ' '--os-target-user-domain-name=fake_domain ' 'workbook-list' ) self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('https://127.0.0.1:35357/v3', params[1]['auth_url']) # No need to substitute values for project_domain_id and # user_domain_id if related domain names were provided self.assertEqual('', params[1]['project_domain_id']) self.assertEqual('', params[1]['user_domain_id']) self.assertEqual('fake_domain', params[1]['project_domain_name']) self.assertEqual('fake_domain', params[1]['user_domain_name']) self.assertEqual( 'fake_domain', params[1]['target_project_domain_name'] ) self.assertEqual('fake_domain', params[1]['target_user_domain_name']) @mock.patch('mistralclient.api.client.client') def test_no_domains_keystone_v2(self, client_mock): self.shell( '--os-auth-url=https://127.0.0.1:35357/v2.0 ' '--os-username=admin ' '--os-password=1234 ' 'workbook-list' ) self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('https://127.0.0.1:35357/v2.0', params[1]['auth_url']) # For keystone v2 nothing is substituted self.assertEqual('', params[1]['project_domain_id']) self.assertEqual('', params[1]['user_domain_id']) self.assertEqual('', params[1]['target_project_domain_id']) self.assertEqual('', params[1]['target_user_domain_id']) python-mistralclient-3.3.0/mistralclient/shell.py0000666000175100017510000006760113241060623022276 0ustar zuulzuul00000000000000# Copyright 2015 - StackStorm, 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 Mistral APIs """ import argparse import logging import os import sys from cliff import app from cliff import commandmanager from osc_lib.command import command from mistralclient.api import client from mistralclient.auth import auth_types import mistralclient.commands.v2.action_executions import mistralclient.commands.v2.actions import mistralclient.commands.v2.cron_triggers import mistralclient.commands.v2.environments import mistralclient.commands.v2.event_triggers import mistralclient.commands.v2.executions import mistralclient.commands.v2.members import mistralclient.commands.v2.services import mistralclient.commands.v2.tasks import mistralclient.commands.v2.workbooks import mistralclient.commands.v2.workflows from mistralclient import exceptions as exe def env(*args, **kwargs): """Returns the first environment variable set. If all are empty, defaults to '' or keyword arg `default`. """ for arg in args: value = os.environ.get(arg) if value: return value return kwargs.get('default', '') class OpenStackHelpFormatter(argparse.HelpFormatter): def __init__(self, prog, indent_increment=2, max_help_position=32, width=None): super(OpenStackHelpFormatter, self).__init__( prog, indent_increment, max_help_position, width ) def start_section(self, heading): # Title-case the headings. heading = '%s%s' % (heading[0].upper(), heading[1:]) super(OpenStackHelpFormatter, self).start_section(heading) class HelpAction(argparse.Action): """Custom help action. Provide a custom action so the -h and --help options to the main app will print a list of the commands. The commands are determined by checking the CommandManager instance, passed in as the "default" value for the action. """ def __call__(self, parser, namespace, values, option_string=None): outputs = [] max_len = 0 app = self.default parser.print_help(app.stdout) app.stdout.write('\nCommands for API v2 :\n') for name, ep in sorted(app.command_manager): factory = ep.load() cmd = factory(self, None) one_liner = cmd.get_description().split('\n')[0] outputs.append((name, one_liner)) max_len = max(len(name), max_len) for (name, one_liner) in outputs: app.stdout.write(' %s %s\n' % (name.ljust(max_len), one_liner)) sys.exit(0) class BashCompletionCommand(command.Command): """Prints all of the commands and options for bash-completion.""" def take_action(self, parsed_args): commands = set() options = set() for option, _action in self.app.parser._option_string_actions.items(): options.add(option) for command_name, _cmd in self.app.command_manager: commands.add(command_name) print(' '.join(commands | options)) class MistralShell(app.App): def __init__(self): super(MistralShell, self).__init__( description=__doc__.strip(), version=mistralclient.__version__, command_manager=commandmanager.CommandManager('mistral.cli'), ) # Set v2 commands by default self._set_shell_commands(self._get_commands_v2()) def configure_logging(self): log_lvl = logging.DEBUG if self.options.debug else logging.WARNING logging.basicConfig( format="%(levelname)s (%(module)s) %(message)s", level=log_lvl ) logging.getLogger('iso8601').setLevel(logging.WARNING) if self.options.verbose_level <= 1: logging.getLogger('requests').setLevel(logging.WARNING) def build_option_parser(self, description, version, argparse_kwargs=None): """Return an argparse option parser for this application. Subclasses may override this method to extend the parser with more global options. :param description: full description of the application :paramtype description: str :param version: version number for the application :paramtype version: str :param argparse_kwargs: extra keyword argument passed to the ArgumentParser constructor :paramtype extra_kwargs: dict """ argparse_kwargs = argparse_kwargs or {} parser = argparse.ArgumentParser( description=description, add_help=False, formatter_class=OpenStackHelpFormatter, **argparse_kwargs ) parser.add_argument( '--version', action='version', version='%(prog)s {0}'.format(version), help='Show program\'s version number and exit.' ) parser.add_argument( '-v', '--verbose', action='count', dest='verbose_level', default=self.DEFAULT_VERBOSE_LEVEL, help='Increase verbosity of output. Can be repeated.', ) parser.add_argument( '--log-file', action='store', default=None, help='Specify a file to log output. Disabled by default.', ) parser.add_argument( '-q', '--quiet', action='store_const', dest='verbose_level', const=0, help='Suppress output except warnings and errors.', ) parser.add_argument( '-h', '--help', action=HelpAction, nargs=0, default=self, # tricky help="Show this help message and exit.", ) parser.add_argument( '--debug', default=False, action='store_true', help='Show tracebacks on errors.', ) parser.add_argument( '--os-mistral-url', action='store', dest='mistral_url', default=env('OS_MISTRAL_URL'), help='Mistral API host (Env: OS_MISTRAL_URL)' ) parser.add_argument( '--os-mistral-version', action='store', dest='mistral_version', default=env('OS_MISTRAL_VERSION', default='v2'), help='Mistral API version (default = v2) (Env: ' 'OS_MISTRAL_VERSION)' ) parser.add_argument( '--os-mistral-service-type', action='store', dest='service_type', default=env('OS_MISTRAL_SERVICE_TYPE', default='workflowv2'), help='Mistral service-type (should be the same name as in ' 'keystone-endpoint) (default = workflowv2) (Env: ' 'OS_MISTRAL_SERVICE_TYPE)' ) parser.add_argument( '--os-mistral-endpoint-type', action='store', dest='endpoint_type', default=env('OS_MISTRAL_ENDPOINT_TYPE', default='publicURL'), help='Mistral endpoint-type (should be the same name as in ' 'keystone-endpoint) (default = publicURL) (Env: ' 'OS_MISTRAL_ENDPOINT_TYPE)' ) parser.add_argument( '--os-username', action='store', dest='username', default=env('OS_USERNAME'), help='Authentication username (Env: OS_USERNAME)' ) parser.add_argument( '--os-password', action='store', dest='password', default=env('OS_PASSWORD'), help='Authentication password (Env: OS_PASSWORD)' ) parser.add_argument( '--os-tenant-id', action='store', dest='tenant_id', default=env('OS_TENANT_ID', 'OS_PROJECT_ID'), help='Authentication tenant identifier (Env: OS_TENANT_ID' ' or OS_PROJECT_ID)' ) parser.add_argument( '--os-project-id', action='store', dest='project_id', default=env('OS_TENANT_ID', 'OS_PROJECT_ID'), help='Authentication project identifier (Env: OS_TENANT_ID' ' or OS_PROJECT_ID), will use tenant_id if both tenant_id' ' and project_id are set' ) parser.add_argument( '--os-tenant-name', action='store', dest='tenant_name', default=env('OS_TENANT_NAME', 'OS_PROJECT_NAME'), help='Authentication tenant name (Env: OS_TENANT_NAME' ' or OS_PROJECT_NAME)' ) parser.add_argument( '--os-project-name', action='store', dest='project_name', default=env('OS_TENANT_NAME', 'OS_PROJECT_NAME'), help='Authentication project name (Env: OS_TENANT_NAME' ' or OS_PROJECT_NAME), will use tenant_name if both' ' tenant_name and project_name are set' ) parser.add_argument( '--os-auth-token', action='store', dest='token', default=env('OS_AUTH_TOKEN'), help='Authentication token (Env: OS_AUTH_TOKEN)' ) parser.add_argument( '--os-project-domain-name', action='store', dest='project_domain_name', default=env('OS_PROJECT_DOMAIN_NAME'), help='Authentication project domain name or ID' ' (Env: OS_PROJECT_DOMAIN_NAME or OS_PROJECT_DOMAIN_NAME)' ) parser.add_argument( '--os-project-domain-id', action='store', dest='project_domain_id', default=env('OS_PROJECT_DOMAIN_ID'), help='Authentication project domain ID' ' (Env: OS_PROJECT_DOMAIN_ID)' ) parser.add_argument( '--os-user-domain-name', action='store', dest='user_domain_name', default=env('OS_USER_DOMAIN_NAME'), help='Authentication user domain name' ' (Env: OS_USER_DOMAIN_NAME)' ) parser.add_argument( '--os-user-domain-id', action='store', dest='user_domain_id', default=env('OS_USER_DOMAIN_ID'), help='Authentication user domain name' ' (Env: OS_USER_DOMAIN_ID)' ) parser.add_argument( '--os-auth-url', action='store', dest='auth_url', default=env('OS_AUTH_URL'), help='Authentication URL (Env: OS_AUTH_URL)' ) parser.add_argument( '--os-cert', action='store', dest='os_cert', default=env('OS_CERT'), help='Client Certificate (Env: OS_CERT)' ) parser.add_argument( '--os-key', action='store', dest='os_key', default=env('OS_KEY'), help='Client Key (Env: OS_KEY)' ) parser.add_argument( '--os-cacert', action='store', dest='os_cacert', default=env('OS_CACERT'), help='Authentication CA Certificate (Env: OS_CACERT)' ) parser.add_argument( '--os-region-name', action='store', dest='region_name', default=env('OS_REGION_NAME'), help='Region name (Env: OS_REGION_NAME)' ) parser.add_argument( '--insecure', action='store_true', dest='insecure', default=env('MISTRALCLIENT_INSECURE', default=False), help='Disables SSL/TLS certificate verification ' '(Env: MISTRALCLIENT_INSECURE)' ) parser.add_argument( '--auth-type', action='store', dest='auth_type', default=env('MISTRAL_AUTH_TYPE', default='keystone'), help='Authentication type. Valid options are: %s.' ' (Env: MISTRAL_AUTH_TYPE)' % ', '.join(auth_types.ALL) ) parser.add_argument( '--openid-client-id', action='store', dest='client_id', default=env('OPENID_CLIENT_ID'), help='Client ID (according to OpenID Connect).' ' (Env: OPENID_CLIENT_ID)' ) parser.add_argument( '--openid-client-secret', action='store', dest='client_secret', default=env('OPENID_CLIENT_SECRET'), help='Client secret (according to OpenID Connect)' ' (Env: OPENID_CLIENT_SECRET)' ) parser.add_argument( '--os-target-username', action='store', dest='target_username', default=env('OS_TARGET_USERNAME', default='admin'), help='Authentication username for target cloud' ' (Env: OS_TARGET_USERNAME)' ) parser.add_argument( '--os-target-password', action='store', dest='target_password', default=env('OS_TARGET_PASSWORD'), help='Authentication password for target cloud' ' (Env: OS_TARGET_PASSWORD)' ) parser.add_argument( '--os-target-tenant-id', action='store', dest='target_tenant_id', default=env('OS_TARGET_TENANT_ID'), help='Authentication tenant identifier for target cloud' ' (Env: OS_TARGET_TENANT_ID)' ) parser.add_argument( '--os-target-tenant-name', action='store', dest='target_tenant_name', default=env('OS_TARGET_TENANT_NAME'), help='Authentication tenant name for target cloud' ' (Env: OS_TARGET_TENANT_NAME)' ) parser.add_argument( '--os-target-auth-token', action='store', dest='target_token', default=env('OS_TARGET_AUTH_TOKEN'), help='Authentication token for target cloud' ' (Env: OS_TARGET_AUTH_TOKEN)' ) parser.add_argument( '--os-target-auth-url', action='store', dest='target_auth_url', default=env('OS_TARGET_AUTH_URL'), help='Authentication URL for target cloud' ' (Env: OS_TARGET_AUTH_URL)' ) parser.add_argument( '--os-target_cacert', action='store', dest='target_cacert', default=env('OS_TARGET_CACERT'), help='Authentication CA Certificate for target cloud' ' (Env: OS_TARGET_CACERT)' ) parser.add_argument( '--os-target-region-name', action='store', dest='target_region_name', default=env('OS_TARGET_REGION_NAME'), help='Region name for target cloud' '(Env: OS_TARGET_REGION_NAME)' ) parser.add_argument( '--os-target-user-domain-name', action='store', dest='target_user_domain_name', default=env('OS_TARGET_USER_DOMAIN_NAME'), help='User domain name for target cloud' '(Env: OS_TARGET_USER_DOMAIN_NAME)' ) parser.add_argument( '--os-target-user-domain-id', action='store', dest='target_user_domain_id', default=env('OS_TARGET_USER_DOMAIN_ID'), help='User domain ID for target cloud' '(Env: OS_TARGET_USER_DOMAIN_ID)' ) parser.add_argument( '--os-target-project-domain-name', action='store', dest='target_project_domain_name', default=env('OS_TARGET_PROJECT_DOMAIN_NAME'), help='Project domain name for target cloud' '(Env: OS_TARGET_PROJECT_DOMAIN_NAME)' ) parser.add_argument( '--os-target-project-domain-id', action='store', dest='target_project_domain_id', default=env('OS_TARGET_PROJECT_DOMAIN_ID'), help='Project domain ID for target cloud' '(Env: OS_TARGET_PROJECT_DOMAIN_ID)' ) parser.add_argument( '--target_insecure', action='store_true', dest='target_insecure', default=env('TARGET_MISTRALCLIENT_INSECURE', default=False), help='Disables SSL/TLS certificate verification for target cloud ' '(Env: TARGET_MISTRALCLIENT_INSECURE)' ) parser.add_argument( '--profile', dest='profile', metavar='HMAC_KEY', default=env('OS_PROFILE'), help='HMAC key to use for encrypting context data for performance ' 'profiling of operation. This key should be one of the ' 'values configured for the osprofiler middleware in mistral, ' 'it is specified in the profiler section of the mistral ' 'configuration (i.e. /etc/mistral/mistral.conf). Without the ' 'key, profiling will not be triggered even if osprofiler is ' 'enabled on the server side.' ) return parser def initialize_app(self, argv): self._clear_shell_commands() ver = client.determine_client_version(self.options.mistral_version) self._set_shell_commands(self._get_commands(ver)) # bash-completion and help messages should not require client creation need_client = not ( ('bash-completion' in argv) or ('help' in argv) or ('-h' in argv) or ('--help' in argv) or not argv) # Set default for auth_url if not supplied. The default is not # set at the parser to support use cases where auth is not enabled. # An example use case would be a developer's environment. if not self.options.auth_url: if self.options.password or self.options.token: self.options.auth_url = 'http://localhost:35357/v3' if (self.options.auth_type == 'keystone' and not self.options.auth_url.endswith("/v2.0")): # Assume that keystone V3 is used and try to be more user-friendly, # i.e provide default values for domains if (not self.options.project_domain_id and not self.options.project_domain_name): self.options.project_domain_id = "default" if (not self.options.user_domain_id and not self.options.user_domain_name): self.options.user_domain_id = "default" if (not self.options.target_project_domain_id and not self.options.target_project_domain_name): self.options.target_project_domain_id = "default" if (not self.options.target_user_domain_id and not self.options.target_user_domain_name): self.options.target_user_domain_id = "default" if self.options.auth_url and not self.options.token: if not self.options.username: raise exe.IllegalArgumentException( ("You must provide a username " "via --os-username env[OS_USERNAME]") ) if not self.options.password: raise exe.IllegalArgumentException( ("You must provide a password " "via --os-password env[OS_PASSWORD]") ) self.client = self._create_client() if need_client else None # Adding client_manager variable to make mistral client work with # unified OpenStack client. ClientManager = type( 'ClientManager', (object,), dict(workflow_engine=self.client) ) self.client_manager = ClientManager() def _create_client(self): kwargs = { 'cert': self.options.os_cert, 'key': self.options.os_key, 'user_domain_name': self.options.user_domain_name, 'user_domain_id': self.options.user_domain_id, 'project_domain_name': self.options.project_domain_name, 'project_domain_id': self.options.project_domain_id, 'target_project_domain_name': self.options.target_project_domain_name, 'target_project_domain_id': self.options.target_project_domain_id, 'target_user_domain_name': self.options.target_user_domain_name, 'target_user_domain_id': self.options.target_user_domain_id } return client.client( mistral_url=self.options.mistral_url, username=self.options.username, api_key=self.options.password, project_name=self.options.tenant_name or self.options.project_name, auth_url=self.options.auth_url, project_id=self.options.tenant_id or self.options.project_id, endpoint_type=self.options.endpoint_type, service_type=self.options.service_type, region_name=self.options.region_name, auth_token=self.options.token, cacert=self.options.os_cacert, insecure=self.options.insecure, profile=self.options.profile, auth_type=self.options.auth_type, client_id=self.options.client_id, client_secret=self.options.client_secret, target_username=self.options.target_username, target_api_key=self.options.target_password, target_project_name=self.options.target_tenant_name, target_auth_url=self.options.target_auth_url, target_project_id=self.options.target_tenant_id, target_auth_token=self.options.target_token, target_cacert=self.options.target_cacert, target_region_name=self.options.target_region_name, target_insecure=self.options.target_insecure, **kwargs ) def _set_shell_commands(self, cmds_dict): for k, v in cmds_dict.items(): self.command_manager.add_command(k, v) def _clear_shell_commands(self): exclude_cmds = ['help', 'complete'] cmds = self.command_manager.commands.copy() for k, v in cmds.items(): if k not in exclude_cmds: self.command_manager.commands.pop(k) def _get_commands(self, version): if version == 2: return self._get_commands_v2() return {} @staticmethod def _get_commands_v2(): return { 'bash-completion': BashCompletionCommand, 'workbook-list': mistralclient.commands.v2.workbooks.List, 'workbook-get': mistralclient.commands.v2.workbooks.Get, 'workbook-create': mistralclient.commands.v2.workbooks.Create, 'workbook-delete': mistralclient.commands.v2.workbooks.Delete, 'workbook-update': mistralclient.commands.v2.workbooks.Update, 'workbook-get-definition': mistralclient.commands.v2.workbooks.GetDefinition, 'workbook-validate': mistralclient.commands.v2.workbooks.Validate, 'workflow-list': mistralclient.commands.v2.workflows.List, 'workflow-get': mistralclient.commands.v2.workflows.Get, 'workflow-create': mistralclient.commands.v2.workflows.Create, 'workflow-delete': mistralclient.commands.v2.workflows.Delete, 'workflow-update': mistralclient.commands.v2.workflows.Update, 'workflow-get-definition': mistralclient.commands.v2.workflows.GetDefinition, 'workflow-validate': mistralclient.commands.v2.workflows.Validate, 'environment-create': mistralclient.commands.v2.environments.Create, 'environment-delete': mistralclient.commands.v2.environments.Delete, 'environment-update': mistralclient.commands.v2.environments.Update, 'environment-list': mistralclient.commands.v2.environments.List, 'environment-get': mistralclient.commands.v2.environments.Get, 'run-action': mistralclient.commands.v2.action_executions.Create, 'action-execution-list': mistralclient.commands.v2.action_executions.List, 'action-execution-get': mistralclient.commands.v2.action_executions.Get, 'action-execution-get-input': mistralclient.commands.v2.action_executions.GetInput, 'action-execution-get-output': mistralclient.commands.v2.action_executions.GetOutput, 'action-execution-update': mistralclient.commands.v2.action_executions.Update, 'action-execution-delete': mistralclient.commands.v2.action_executions.Delete, 'execution-create': mistralclient.commands.v2.executions.Create, 'execution-delete': mistralclient.commands.v2.executions.Delete, 'execution-update': mistralclient.commands.v2.executions.Update, 'execution-list': mistralclient.commands.v2.executions.List, 'execution-get': mistralclient.commands.v2.executions.Get, 'execution-get-input': mistralclient.commands.v2.executions.GetInput, 'execution-get-output': mistralclient.commands.v2.executions.GetOutput, 'task-list': mistralclient.commands.v2.tasks.List, 'task-get': mistralclient.commands.v2.tasks.Get, 'task-get-published': mistralclient.commands.v2.tasks.GetPublished, 'task-get-result': mistralclient.commands.v2.tasks.GetResult, 'task-rerun': mistralclient.commands.v2.tasks.Rerun, 'action-list': mistralclient.commands.v2.actions.List, 'action-get': mistralclient.commands.v2.actions.Get, 'action-create': mistralclient.commands.v2.actions.Create, 'action-delete': mistralclient.commands.v2.actions.Delete, 'action-update': mistralclient.commands.v2.actions.Update, 'action-get-definition': mistralclient.commands.v2.actions.GetDefinition, 'action-validate': mistralclient.commands.v2.actions.Validate, 'cron-trigger-list': mistralclient.commands.v2.cron_triggers.List, 'cron-trigger-get': mistralclient.commands.v2.cron_triggers.Get, 'cron-trigger-create': mistralclient.commands.v2.cron_triggers.Create, 'cron-trigger-delete': mistralclient.commands.v2.cron_triggers.Delete, 'event-trigger-list': mistralclient.commands.v2.event_triggers.List, 'event-trigger-get': mistralclient.commands.v2.event_triggers.Get, 'event-trigger-create': mistralclient.commands.v2.event_triggers.Create, 'event-trigger-delete': mistralclient.commands.v2.event_triggers.Delete, 'service-list': mistralclient.commands.v2.services.List, 'member-create': mistralclient.commands.v2.members.Create, 'member-delete': mistralclient.commands.v2.members.Delete, 'member-update': mistralclient.commands.v2.members.Update, 'member-list': mistralclient.commands.v2.members.List, 'member-get': mistralclient.commands.v2.members.Get, } def main(argv=sys.argv[1:]): return MistralShell().run(argv) if __name__ == '__main__': sys.exit(main(sys.argv[1:])) python-mistralclient-3.3.0/AUTHORS0000664000175100017510000000634613241061114017004 0ustar zuulzuul00000000000000Adriano Petrich Anastasia Kuznetsova Andras Kovi Andreas Jaeger Angus Salkeld Bob Haddleton Boris Bobrov Brad P. Crochet Cao Xuan Hoang Christian Berendt Corey Bryant Daryl Mowrer David C Kennedy Dawid Deja Dina Belova Dmitri Zimine Doug Hellmann Dougal Matthews Ed Cranford Flavio Percoco Goutham Pratapa GouthamPratapa Hangdong Zhang Hardik Parekh Istvan Imre Jamie Lennox Jeremy Liu Jeremy Stanley Ji zhaoxuan Juan Antonio Osorio Robles KATO Tomoyuki Kevin_Zheng Kirill Izotov Kupai József Leandro I. Costantino Limor Stotland Lingxian Kong LingxianKong LingxianKong Lucky samadhiya Marcos Fermin Lobo Michal Gershenzon Michal Gershenzon Mike Fedosin Nikolay Mahotkin OpenStack Release Bot Pierre-Arthur MATHIEU Prince Katiyar Renat Akhmerov Renat Akhmerov Robert Collins Sharat Sharma Sirushti Murugesan Stephen Finucane Steve Martinelli Tang Chen Tetiana Lashchova Thomas Goirand Thomas Herve TimurNurlygayanov Tony Xu Toure Dunnon Tovin Seven W Chan Winson Chan Zhenguo Niu Zuul fengchaoyang gengchc2 hardik houweichao hparekh pawnesh.kumar rakhmerov reedip ricolin ricolin shu-mutou venkatamahesh wangzhenyu xianming mao yfzhao zhangguoqing