Retire Packaging Deb project repos
This commit is part of a series to retire the Packaging Deb project. Step 2 is to remove all content from the project repos, replacing it with a README notification where to find ongoing work, and how to recover the repo if needed at some future point (as in https://docs.openstack.org/infra/manual/drivers.html#retiring-a-project). Change-Id: I59dd6c58e15d7d0b5b12bbc892aadfdc662305c3
This commit is contained in:
parent
6696c55574
commit
2c4701236a
|
@ -1,53 +0,0 @@
|
||||||
*.py[cod]
|
|
||||||
*.sqlite
|
|
||||||
|
|
||||||
# C extensions
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Packages
|
|
||||||
*.egg*
|
|
||||||
dist
|
|
||||||
build
|
|
||||||
.venv
|
|
||||||
.eggs
|
|
||||||
eggs
|
|
||||||
parts
|
|
||||||
bin
|
|
||||||
var
|
|
||||||
sdist
|
|
||||||
develop-eggs
|
|
||||||
.installed.cfg
|
|
||||||
lib
|
|
||||||
lib64
|
|
||||||
|
|
||||||
# Installer logs
|
|
||||||
pip-log.txt
|
|
||||||
|
|
||||||
# Unit test / coverage reports
|
|
||||||
.coverage
|
|
||||||
cover/*
|
|
||||||
.tox
|
|
||||||
nosetests.xml
|
|
||||||
functional_creds.conf
|
|
||||||
AUTHORS
|
|
||||||
ChangeLog
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
*.mo
|
|
||||||
|
|
||||||
# Mr Developer
|
|
||||||
.mr.developer.cfg
|
|
||||||
.project
|
|
||||||
.pydevproject
|
|
||||||
.idea
|
|
||||||
.DS_Store
|
|
||||||
etc/mistral.conf
|
|
||||||
|
|
||||||
#swap file
|
|
||||||
*.swp
|
|
||||||
|
|
||||||
# Files created by releasenotes build
|
|
||||||
releasenotes/build
|
|
||||||
|
|
||||||
# Files created by doc build
|
|
||||||
doc/source/api
|
|
|
@ -1,4 +0,0 @@
|
||||||
[gerrit]
|
|
||||||
host=review.openstack.org
|
|
||||||
port=29418
|
|
||||||
project=openstack/python-mistralclient.git
|
|
|
@ -1,16 +0,0 @@
|
||||||
If 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
|
|
175
LICENSE
175
LICENSE
|
@ -1,175 +0,0 @@
|
||||||
|
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
This project is no longer maintained.
|
||||||
|
|
||||||
|
The contents of this repository are still available in the Git
|
||||||
|
source code management system. To see the contents of this
|
||||||
|
repository before it reached its end of life, please check out the
|
||||||
|
previous commit with "git checkout HEAD^1".
|
||||||
|
|
||||||
|
For ongoing work on maintaining OpenStack packages in the Debian
|
||||||
|
distribution, please see the Debian OpenStack packaging team at
|
||||||
|
https://wiki.debian.org/OpenStack/.
|
||||||
|
|
||||||
|
For any further questions, please email
|
||||||
|
openstack-dev@lists.openstack.org or join #openstack-dev on
|
||||||
|
Freenode.
|
100
README.rst
100
README.rst
|
@ -1,100 +0,0 @@
|
||||||
========================
|
|
||||||
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://<Keystone_host>:5000/v2.0
|
|
||||||
$ export OS_USERNAME=admin
|
|
||||||
$ export OS_TENANT_NAME=tenant
|
|
||||||
$ export OS_PASSWORD=secret
|
|
||||||
$ export OS_MISTRAL_URL=http://<Mistral host>: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=<path_to_ca_cert>
|
|
||||||
|
|
||||||
.. 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/
|
|
|
@ -1,7 +0,0 @@
|
||||||
Python Mistral bindings class refrence
|
|
||||||
======================================
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
api/autoindex
|
|
|
@ -1,2 +0,0 @@
|
||||||
Using Mistral with KeyCloak Server
|
|
||||||
==================================
|
|
|
@ -1,36 +0,0 @@
|
||||||
Using 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://<Keystone_host>:5000/v2.0
|
|
||||||
$ export OS_USERNAME=admin
|
|
||||||
$ export OS_TENANT_NAME=tenant
|
|
||||||
$ export OS_PASSWORD=secret
|
|
||||||
$ export OS_MISTRAL_URL=http://<Mistral host>:8989/v2
|
|
||||||
|
|
||||||
When authenticating against keystone over https:
|
|
||||||
|
|
||||||
.. code-block:: shell
|
|
||||||
|
|
||||||
$ export OS_CACERT=<path_to_ca_cert>
|
|
||||||
|
|
||||||
Once you've configured your authentication parameters, you can run **mistral**
|
|
||||||
commands. All commands take the form of::
|
|
||||||
|
|
||||||
mistral <command> [arguments...]
|
|
||||||
|
|
||||||
Run **mistral --help** to get a full list of all possible commands, and run
|
|
||||||
**mistral help <command>** to get detailed help for that command.
|
|
|
@ -1,42 +0,0 @@
|
||||||
Using 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
|
|
|
@ -1,111 +0,0 @@
|
||||||
# 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
|
|
||||||
# "<project> v<release> 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 = ''
|
|
|
@ -1,40 +0,0 @@
|
||||||
Python 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
|
|
||||||
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
|
|
|
@ -1,12 +0,0 @@
|
||||||
# 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_secret
|
|
|
@ -1,26 +0,0 @@
|
||||||
#!/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
|
|
|
@ -1,20 +0,0 @@
|
||||||
---
|
|
||||||
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 %>
|
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
---
|
|
||||||
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 %>
|
|
|
@ -1,14 +0,0 @@
|
||||||
---
|
|
||||||
version: '2.0'
|
|
||||||
|
|
||||||
name: wb
|
|
||||||
|
|
||||||
workflows:
|
|
||||||
wf1:
|
|
||||||
type: direct
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
hello:
|
|
||||||
action: std.echo output="Hello"
|
|
||||||
publish:
|
|
||||||
result: <% task(hello).result %>
|
|
|
@ -1,15 +0,0 @@
|
||||||
---
|
|
||||||
version: '2.0'
|
|
||||||
|
|
||||||
name: wb
|
|
||||||
tags: [tag]
|
|
||||||
|
|
||||||
workflows:
|
|
||||||
wf1:
|
|
||||||
type: direct
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
hello:
|
|
||||||
action: std.echo output="Hello"
|
|
||||||
publish:
|
|
||||||
result: <% task(hello).result %>
|
|
|
@ -1,20 +0,0 @@
|
||||||
---
|
|
||||||
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 %>
|
|
|
@ -1,11 +0,0 @@
|
||||||
---
|
|
||||||
version: '2.0'
|
|
||||||
|
|
||||||
wf_single:
|
|
||||||
type: direct
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
hello:
|
|
||||||
action: std.echo output="Hello"
|
|
||||||
publish:
|
|
||||||
result: <% task(hello).result %>
|
|
|
@ -1,28 +0,0 @@
|
||||||
---
|
|
||||||
version: '2.0'
|
|
||||||
|
|
||||||
wf:
|
|
||||||
type: direct
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
hello:
|
|
||||||
action: std.echo output="Hello"
|
|
||||||
wait-before: 5
|
|
||||||
publish:
|
|
||||||
result: <% task(hello).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]
|
|
|
@ -1,8 +0,0 @@
|
||||||
---
|
|
||||||
version: '2.0'
|
|
||||||
|
|
||||||
wrapping_wf:
|
|
||||||
type: direct
|
|
||||||
tasks:
|
|
||||||
hello:
|
|
||||||
workflow: wf
|
|
|
@ -1,58 +0,0 @@
|
||||||
#!/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_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 <<EOF > ${CREDS_FILE}
|
|
||||||
# Credentials for functional testing
|
|
||||||
[auth]
|
|
||||||
uri = $OS_AUTH_URL
|
|
||||||
[admin]
|
|
||||||
user = $OS_USERNAME
|
|
||||||
tenant = $OS_TENANT_NAME
|
|
||||||
pass = $OS_PASSWORD
|
|
||||||
[demo]
|
|
||||||
user = $OS_ALT_USERNAME
|
|
||||||
tenant = $OS_ALT_TENANT_NAME
|
|
||||||
pass = $OS_ALT_PASSWORD
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cd $MISTRALCLIENT_DIR
|
|
||||||
|
|
||||||
# Run tests
|
|
||||||
tox -efunctional -- nosetests -sv mistralclient/tests/functional
|
|
|
@ -1,19 +0,0 @@
|
||||||
# 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()
|
|
|
@ -1,164 +0,0 @@
|
||||||
# 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
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
resp = self.http_client.post(url, data)
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
resp = self.http_client.put(url, data)
|
|
||||||
|
|
||||||
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):
|
|
||||||
resp = self.http_client.get(url)
|
|
||||||
|
|
||||||
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):
|
|
||||||
resp = self.http_client.get(url)
|
|
||||||
|
|
||||||
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):
|
|
||||||
resp = self.http_client.delete(url)
|
|
||||||
|
|
||||||
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
|
|
|
@ -1,26 +0,0 @@
|
||||||
# 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")
|
|
|
@ -1,203 +0,0 @@
|
||||||
# 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 os
|
|
||||||
|
|
||||||
from oslo_utils import importutils
|
|
||||||
import requests
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
|
|
||||||
AUTH_TOKEN = 'auth_token'
|
|
||||||
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_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.pop('session', None)
|
|
||||||
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_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.session:
|
|
||||||
self.crud_provider = self.session
|
|
||||||
else:
|
|
||||||
self.crud_provider = requests
|
|
||||||
|
|
||||||
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.')
|
|
||||||
|
|
||||||
# These are already set by the session, so it's not needed
|
|
||||||
if not self.session:
|
|
||||||
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.crud_provider.get(self.base_url + url, **options)
|
|
||||||
|
|
||||||
@log_request
|
|
||||||
def post(self, url, body, headers=None):
|
|
||||||
options = self._get_request_options('post', headers)
|
|
||||||
|
|
||||||
return self.crud_provider.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.crud_provider.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.crud_provider.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 not self.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:
|
|
||||||
headers['X-Target-Insecure'] = 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
|
|
|
@ -1,87 +0,0 @@
|
||||||
# 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 mistralclient.api import base
|
|
||||||
|
|
||||||
|
|
||||||
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'
|
|
||||||
|
|
||||||
qparams = {}
|
|
||||||
|
|
||||||
if task_execution_id:
|
|
||||||
url = '/tasks/%s/action_executions' % task_execution_id
|
|
||||||
|
|
||||||
if limit:
|
|
||||||
qparams['limit'] = limit
|
|
||||||
|
|
||||||
return self._list(url, 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)
|
|
|
@ -1,123 +0,0 @@
|
||||||
# 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:
|
|
||||||
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)
|
|
|
@ -1,82 +0,0 @@
|
||||||
# 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.')
|
|
||||||
|
|
||||||
auth_handler = auth.get_auth_handler(auth_type)
|
|
||||||
auth_response = auth_handler.authenticate(req, session=session) or {}
|
|
||||||
|
|
||||||
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)
|
|
|
@ -1,68 +0,0 @@
|
||||||
# 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)
|
|
|
@ -1,85 +0,0 @@
|
||||||
# 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)
|
|
|
@ -1,61 +0,0 @@
|
||||||
# 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)
|
|
|
@ -1,110 +0,0 @@
|
||||||
# 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, workflow_input=None, description='',
|
|
||||||
**params):
|
|
||||||
self._ensure_not_empty(workflow_identifier=workflow_identifier)
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'description': description
|
|
||||||
}
|
|
||||||
|
|
||||||
if uuidutils.is_uuid_like(workflow_identifier):
|
|
||||||
data.update({'workflow_id': workflow_identifier})
|
|
||||||
else:
|
|
||||||
data.update({'workflow_name': workflow_identifier})
|
|
||||||
|
|
||||||
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:
|
|
||||||
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)
|
|
|
@ -1,76 +0,0 @@
|
||||||
# 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)
|
|
|
@ -1,26 +0,0 @@
|
||||||
# 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')
|
|
|
@ -1,79 +0,0 @@
|
||||||
# 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='', **filters):
|
|
||||||
url = '/tasks'
|
|
||||||
|
|
||||||
if workflow_execution_id:
|
|
||||||
url = '/executions/%s/tasks' % workflow_execution_id
|
|
||||||
|
|
||||||
url += '%s'
|
|
||||||
|
|
||||||
qparams = {}
|
|
||||||
|
|
||||||
if marker:
|
|
||||||
qparams['marker'] = marker
|
|
||||||
|
|
||||||
if limit:
|
|
||||||
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(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)
|
|
|
@ -1,92 +0,0 @@
|
||||||
# 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)
|
|
|
@ -1,128 +0,0 @@
|
||||||
# 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, 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' % 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, 'workflows')]
|
|
||||||
|
|
||||||
def update(self, definition, 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?scope=%s' % (url_pre, 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, marker='', limit=None, sort_keys='', sort_dirs='',
|
|
||||||
**filters):
|
|
||||||
qparams = {}
|
|
||||||
|
|
||||||
if marker:
|
|
||||||
qparams['marker'] = marker
|
|
||||||
|
|
||||||
if limit:
|
|
||||||
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):
|
|
||||||
self._ensure_not_empty(identifier=identifier)
|
|
||||||
|
|
||||||
return self._get('/workflows/%s' % identifier)
|
|
||||||
|
|
||||||
def delete(self, identifier):
|
|
||||||
self._ensure_not_empty(identifier=identifier)
|
|
||||||
|
|
||||||
self._delete('/workflows/%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(
|
|
||||||
'/workflows/validate',
|
|
||||||
definition,
|
|
||||||
headers={'content-type': 'text/plain'}
|
|
||||||
)
|
|
||||||
|
|
||||||
if resp.status_code != 200:
|
|
||||||
self._raise_api_exception(resp)
|
|
||||||
|
|
||||||
return base.extract_json(resp, None)
|
|
|
@ -1,37 +0,0 @@
|
||||||
# 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()
|
|
|
@ -1,21 +0,0 @@
|
||||||
# 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()
|
|
|
@ -1,170 +0,0 @@
|
||||||
# Copyright 2016 - Nokia Networks
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import pprint
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from 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).
|
|
||||||
* realm_name: KeyCloak realm name.
|
|
||||||
* username: User name (Optional, if None then access_token must be
|
|
||||||
provided).
|
|
||||||
* password: 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('realm_name')
|
|
||||||
username = req.get('username')
|
|
||||||
password = req.get('password')
|
|
||||||
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 client_secret:
|
|
||||||
raise ValueError('Client secret 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
|
|
||||||
)
|
|
||||||
|
|
||||||
response['project_id'] = realm_name
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
def _authenticate_with_token(auth_url, client_id, client_secret,
|
|
||||||
auth_token, cacert=None, insecure=None):
|
|
||||||
# TODO(rakhmerov): Implement.
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
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)
|
|
||||||
)
|
|
||||||
|
|
||||||
client_auth = (client_id, client_secret)
|
|
||||||
|
|
||||||
body = {
|
|
||||||
'grant_type': 'password',
|
|
||||||
'username': username,
|
|
||||||
'password': password,
|
|
||||||
'scope': 'profile'
|
|
||||||
}
|
|
||||||
|
|
||||||
resp = requests.post(
|
|
||||||
access_token_endpoint,
|
|
||||||
auth=client_auth,
|
|
||||||
data=body,
|
|
||||||
verify=not insecure
|
|
||||||
)
|
|
||||||
|
|
||||||
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']
|
|
||||||
|
|
||||||
|
|
||||||
# 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(
|
|
||||||
"https://my.keycloak:8443/auth",
|
|
||||||
client_id="mistral_client",
|
|
||||||
client_secret="4a080907-921b-409a-b793-c431609c3a47",
|
|
||||||
realm_name="mistral",
|
|
||||||
username="user",
|
|
||||||
password="secret",
|
|
||||||
insecure=True
|
|
||||||
)
|
|
||||||
|
|
||||||
print("Access token: %s" % a_token)
|
|
|
@ -1,146 +0,0 @@
|
||||||
# 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 keystoneclient import client
|
|
||||||
from mistralclient import auth
|
|
||||||
from oslo_serialization import jsonutils
|
|
||||||
|
|
||||||
import mistralclient.api.httpclient as api
|
|
||||||
|
|
||||||
|
|
||||||
class KeystoneAuthHandler(auth.AuthHandler):
|
|
||||||
|
|
||||||
def authenticate(self, req, session=None):
|
|
||||||
"""Performs authentication via Keystone.
|
|
||||||
|
|
||||||
:param req: Request dict containing list of parameters required
|
|
||||||
for Keystone authentication.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if not isinstance(req, dict):
|
|
||||||
raise TypeError('The input "req" is not typeof dict.')
|
|
||||||
|
|
||||||
session = session
|
|
||||||
mistral_url = req.get('mistral_url')
|
|
||||||
endpoint_type = req.get('endpoint_type', 'publicURL')
|
|
||||||
service_type = req.get('service_type', 'workflowv2')
|
|
||||||
|
|
||||||
auth_url = req.get('auth_url')
|
|
||||||
username = req.get('username')
|
|
||||||
user_id = req.get('user_id')
|
|
||||||
api_key = req.get('api_key')
|
|
||||||
auth_token = req.get('auth_token')
|
|
||||||
project_name = req.get('project_name')
|
|
||||||
project_id = req.get('project_id')
|
|
||||||
region_name = req.get('region_name')
|
|
||||||
user_domain_name = req.get('user_domain_name', 'Default')
|
|
||||||
project_domain_name = req.get('project_domain_name', 'Default')
|
|
||||||
cacert = req.get('cacert')
|
|
||||||
insecure = req.get('insecure', False)
|
|
||||||
|
|
||||||
target_auth_url = req.get('target_auth_url')
|
|
||||||
target_username = req.get('target_username')
|
|
||||||
target_user_id = req.get('target_user_id')
|
|
||||||
target_api_key = req.get('target_api_key')
|
|
||||||
target_auth_token = req.get('target_auth_token')
|
|
||||||
target_project_name = req.get('target_project_name')
|
|
||||||
target_project_id = req.get('target_project_id')
|
|
||||||
target_region_name = req.get('target_region_name')
|
|
||||||
target_user_domain_name = req.get('target_user_domain_name', 'Default')
|
|
||||||
target_project_domain_name = req.get(
|
|
||||||
'target_project_domain_name',
|
|
||||||
'Default'
|
|
||||||
)
|
|
||||||
target_cacert = req.get('target_cacert')
|
|
||||||
target_insecure = req.get('target_insecure')
|
|
||||||
|
|
||||||
if project_name and project_id:
|
|
||||||
raise RuntimeError(
|
|
||||||
'Only project name or project id should be set'
|
|
||||||
)
|
|
||||||
|
|
||||||
if username and user_id:
|
|
||||||
raise RuntimeError(
|
|
||||||
'Only user name or user id should be set'
|
|
||||||
)
|
|
||||||
|
|
||||||
auth_response = {}
|
|
||||||
|
|
||||||
if session:
|
|
||||||
keystone = client.Client(session=session)
|
|
||||||
elif auth_url:
|
|
||||||
keystone = client.Client(
|
|
||||||
username=username,
|
|
||||||
user_id=user_id,
|
|
||||||
password=api_key,
|
|
||||||
token=auth_token,
|
|
||||||
tenant_id=project_id,
|
|
||||||
tenant_name=project_name,
|
|
||||||
auth_url=auth_url,
|
|
||||||
cacert=cacert,
|
|
||||||
insecure=insecure,
|
|
||||||
user_domain_name=user_domain_name,
|
|
||||||
project_domain_name=project_domain_name
|
|
||||||
)
|
|
||||||
keystone.authenticate()
|
|
||||||
auth_response.update({
|
|
||||||
api.AUTH_TOKEN: keystone.auth_token,
|
|
||||||
api.PROJECT_ID: keystone.project_id,
|
|
||||||
api.USER_ID: keystone.user_id,
|
|
||||||
})
|
|
||||||
|
|
||||||
if session or auth_url:
|
|
||||||
if not mistral_url:
|
|
||||||
try:
|
|
||||||
mistral_url = keystone.service_catalog.url_for(
|
|
||||||
service_type=service_type,
|
|
||||||
endpoint_type=endpoint_type,
|
|
||||||
region_name=region_name
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
mistral_url = None
|
|
||||||
|
|
||||||
auth_response['mistral_url'] = mistral_url
|
|
||||||
|
|
||||||
if target_auth_url:
|
|
||||||
target_keystone = client.Client(
|
|
||||||
username=target_username,
|
|
||||||
user_id=target_user_id,
|
|
||||||
password=target_api_key,
|
|
||||||
token=target_auth_token,
|
|
||||||
tenant_id=target_project_id,
|
|
||||||
tenant_name=target_project_name,
|
|
||||||
project_id=target_project_id,
|
|
||||||
project_name=target_project_name,
|
|
||||||
auth_url=target_auth_url,
|
|
||||||
cacert=target_cacert,
|
|
||||||
insecure=target_insecure,
|
|
||||||
region_name=target_region_name,
|
|
||||||
user_domain_name=target_user_domain_name,
|
|
||||||
project_domain_name=target_project_domain_name
|
|
||||||
)
|
|
||||||
|
|
||||||
target_keystone.authenticate()
|
|
||||||
|
|
||||||
auth_response.update({
|
|
||||||
api.TARGET_AUTH_TOKEN: target_keystone.auth_token,
|
|
||||||
api.TARGET_PROJECT_ID: target_keystone.project_id,
|
|
||||||
api.TARGET_USER_ID: target_keystone.user_id,
|
|
||||||
api.TARGET_AUTH_URI: target_auth_url,
|
|
||||||
api.TARGET_SERVICE_CATALOG: jsonutils.dumps(
|
|
||||||
target_keystone.auth_ref
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
return auth_response
|
|
|
@ -1,338 +0,0 @@
|
||||||
# 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',
|
|
||||||
'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.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 '<none>'
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
data = (tuple('<none>' for _ in range(len(columns))),)
|
|
||||||
|
|
||||||
return columns, data
|
|
||||||
|
|
||||||
|
|
||||||
def format(action_ex=None):
|
|
||||||
columns = (
|
|
||||||
'ID',
|
|
||||||
'Name',
|
|
||||||
'Workflow name',
|
|
||||||
'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.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 '<none>'
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
data = (tuple('<none>' 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 <target> 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)."
|
|
||||||
)
|
|
|
@ -1,255 +0,0 @@
|
||||||
# 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 '<none>',
|
|
||||||
action.created_at,
|
|
||||||
)
|
|
||||||
|
|
||||||
if hasattr(action, 'updated_at'):
|
|
||||||
data += (action.updated_at,)
|
|
||||||
else:
|
|
||||||
data += (None,)
|
|
||||||
else:
|
|
||||||
data = (tuple('<none>' 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('<none>' 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)
|
|
|
@ -1,85 +0,0 @@
|
||||||
# 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
|
|
|
@ -1,216 +0,0 @@
|
||||||
# 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('<none>' 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='<YYYY-MM-DD HH:MM>'
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'--count',
|
|
||||||
type=int,
|
|
||||||
help="Number of wanted executions",
|
|
||||||
metavar='<integer>'
|
|
||||||
)
|
|
||||||
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)."
|
|
||||||
)
|
|
|
@ -1,186 +0,0 @@
|
||||||
# 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 '<none>',)
|
|
||||||
else:
|
|
||||||
data += (None,)
|
|
||||||
|
|
||||||
else:
|
|
||||||
data = (tuple('<none>' 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 '<none>',)
|
|
||||||
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 '<none>',)
|
|
||||||
else:
|
|
||||||
data += (None,)
|
|
||||||
|
|
||||||
else:
|
|
||||||
data = (tuple('<none>' 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)
|
|
|
@ -1,167 +0,0 @@
|
||||||
# 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('<none>' 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)."
|
|
||||||
)
|
|
|
@ -1,338 +0,0 @@
|
||||||
# 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',
|
|
||||||
'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.description,
|
|
||||||
execution.task_execution_id or '<none>',
|
|
||||||
execution.state,
|
|
||||||
state_info,
|
|
||||||
execution.created_at,
|
|
||||||
execution.updated_at or '<none>'
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
data = (tuple('<none>' 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',
|
|
||||||
help='Workflow ID or name. Workflow name will be deprecated since '
|
|
||||||
'Mitaka.'
|
|
||||||
)
|
|
||||||
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'
|
|
||||||
)
|
|
||||||
|
|
||||||
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,
|
|
||||||
wf_input,
|
|
||||||
parsed_args.description,
|
|
||||||
**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")
|
|
|
@ -1,235 +0,0 @@
|
||||||
# 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('<none>' 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)
|
|
|
@ -1,37 +0,0 @@
|
||||||
# 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('<none>' 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()
|
|
|
@ -1,222 +0,0 @@
|
||||||
# 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__)
|
|
||||||
|
|
||||||
|
|
||||||
def format_list(task=None):
|
|
||||||
return format(task, lister=True)
|
|
||||||
|
|
||||||
|
|
||||||
def format(task=None, lister=False):
|
|
||||||
columns = (
|
|
||||||
'ID',
|
|
||||||
'Name',
|
|
||||||
'Workflow name',
|
|
||||||
'Execution ID',
|
|
||||||
'State',
|
|
||||||
'State info',
|
|
||||||
'Created at',
|
|
||||||
'Updated at'
|
|
||||||
)
|
|
||||||
|
|
||||||
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_execution_id,
|
|
||||||
task.state,
|
|
||||||
state_info,
|
|
||||||
task.created_at,
|
|
||||||
task.updated_at or '<none>'
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
data = (tuple('<none>' for _ in range(len(columns))),)
|
|
||||||
|
|
||||||
return columns, 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 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,
|
|
||||||
**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 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 format(execution)
|
|
|
@ -1,194 +0,0 @@
|
||||||
# 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 '<none>',
|
|
||||||
workbook.created_at,
|
|
||||||
)
|
|
||||||
|
|
||||||
if hasattr(workbook, 'updated_at'):
|
|
||||||
data += (workbook.updated_at,)
|
|
||||||
else:
|
|
||||||
data += (None,)
|
|
||||||
|
|
||||||
else:
|
|
||||||
data = (tuple('<none>' 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('<none>' 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)
|
|
|
@ -1,247 +0,0 @@
|
||||||
# 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',
|
|
||||||
'Project ID',
|
|
||||||
'Tags',
|
|
||||||
'Input',
|
|
||||||
'Created at',
|
|
||||||
'Updated at'
|
|
||||||
)
|
|
||||||
|
|
||||||
if workflow:
|
|
||||||
tags = getattr(workflow, 'tags', None) or []
|
|
||||||
|
|
||||||
data = (
|
|
||||||
workflow.id,
|
|
||||||
workflow.name,
|
|
||||||
workflow.project_id,
|
|
||||||
base.wrap(', '.join(tags)) or '<none>',
|
|
||||||
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('<none>' 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(
|
|
||||||
'--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(),
|
|
||||||
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).'
|
|
||||||
)
|
|
||||||
|
|
||||||
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.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(
|
|
||||||
'--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
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
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('<none>' 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)
|
|
|
@ -1,40 +0,0 @@
|
||||||
# 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
|
|
|
@ -1,22 +0,0 @@
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
"""oslo.i18n integration module.
|
|
||||||
See 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
|
|
|
@ -1,62 +0,0 @@
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
"""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='<workflow-api-version>',
|
|
||||||
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
|
|
|
@ -1,709 +0,0 @@
|
||||||
# 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',
|
|
||||||
default='Default'),
|
|
||||||
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',
|
|
||||||
default='Default'),
|
|
||||||
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', default='Default'),
|
|
||||||
help='Authentication project domain name'
|
|
||||||
' (Env: OS_PROJECT_DOMAIN_NAME)'
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
'--os-user-domain-name',
|
|
||||||
action='store',
|
|
||||||
dest='user_domain_name',
|
|
||||||
default=env('OS_USER_DOMAIN_NAME', default='Default'),
|
|
||||||
help='Authentication user domain name'
|
|
||||||
' (Env: OS_USER_DOMAIN_NAME)'
|
|
||||||
)
|
|
||||||
|
|
||||||
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', 'Default'),
|
|
||||||
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-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(
|
|
||||||
'--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))
|
|
||||||
|
|
||||||
do_help = ('help' in argv) or ('-h' 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'
|
|
||||||
|
|
||||||
# bash-completion should not require authentification.
|
|
||||||
if do_help or ('bash-completion' in argv):
|
|
||||||
self.options.auth_url = None
|
|
||||||
|
|
||||||
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]")
|
|
||||||
)
|
|
||||||
|
|
||||||
kwargs = {
|
|
||||||
'cert': self.options.os_cert,
|
|
||||||
'key': self.options.os_key,
|
|
||||||
'user_domain_name': self.options.user_domain_name,
|
|
||||||
'project_domain_name': self.options.project_domain_name
|
|
||||||
}
|
|
||||||
|
|
||||||
self.client = 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
|
|
||||||
)
|
|
||||||
|
|
||||||
# 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 _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:]))
|
|
|
@ -1,148 +0,0 @@
|
||||||
# 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')
|
|
||||||
else:
|
|
||||||
username = os.environ.get('OS_ALT_USERNAME')
|
|
||||||
password = os.environ.get('OS_ALT_PASSWORD')
|
|
||||||
tenant_name = os.environ.get('OS_ALT_TENANT_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')
|
|
||||||
|
|
||||||
# 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'],
|
|
||||||
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()
|
|
||||||
projects = self.parser.listing(
|
|
||||||
admin_clients.openstack('project show', params=project_name)
|
|
||||||
)
|
|
||||||
|
|
||||||
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'],
|
|
||||||
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)
|
|
|
@ -1,279 +0,0 @@
|
||||||
# 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.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, admin=True, scope='private'):
|
|
||||||
params = '{0}'.format(wf_def)
|
|
||||||
|
|
||||||
if scope == 'public':
|
|
||||||
params += ' --public'
|
|
||||||
|
|
||||||
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
|
|
|
@ -1,488 +0,0 @@
|
||||||
# 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' % (self.wf[0]["ID"], new_status)
|
|
||||||
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
|
|
||||||
)
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,39 +0,0 @@
|
||||||
# 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)
|
|
|
@ -1,47 +0,0 @@
|
||||||
# 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
|
|
|
@ -1,10 +0,0 @@
|
||||||
|
|
||||||
---
|
|
||||||
version: 2.0
|
|
||||||
|
|
||||||
my_action:
|
|
||||||
base: std.echo
|
|
||||||
base-input:
|
|
||||||
output: 'Bye!'
|
|
||||||
output:
|
|
||||||
info: <% $.output %>
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"context": {
|
|
||||||
"server": {
|
|
||||||
"name": "name"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
"name": "env1",
|
|
||||||
"description": "Test Environment #1",
|
|
||||||
"scope": "private",
|
|
||||||
"variables": {
|
|
||||||
"server": "localhost"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
---
|
|
||||||
|
|
||||||
"name": "env1"
|
|
||||||
"description": "Test Environment #1"
|
|
||||||
"scope": "private"
|
|
||||||
"variables":
|
|
||||||
"server": "localhost"
|
|
|
@ -1,21 +0,0 @@
|
||||||
|
|
||||||
---
|
|
||||||
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
|
|
|
@ -1,10 +0,0 @@
|
||||||
|
|
||||||
---
|
|
||||||
version: 2.0
|
|
||||||
|
|
||||||
my_wf:
|
|
||||||
type: direct
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
task1:
|
|
||||||
action: std.echo output="hello, world"
|
|
|
@ -1,276 +0,0 @@
|
||||||
# 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(keystone_client_mock):
|
|
||||||
keystone_client_instance = keystone_client_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('keystoneclient.client.Client')
|
|
||||||
def test_mistral_url_from_catalog_v2(self, keystone_client_mock):
|
|
||||||
keystone_client_instance = self.setup_keystone_mock(
|
|
||||||
keystone_client_mock
|
|
||||||
)
|
|
||||||
|
|
||||||
url_for = mock.Mock(return_value='http://mistral_host:8989/v2')
|
|
||||||
keystone_client_instance.service_catalog.url_for = url_for
|
|
||||||
|
|
||||||
mistralclient = client.client(
|
|
||||||
username='mistral',
|
|
||||||
project_name='mistral',
|
|
||||||
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('keystoneclient.client.Client')
|
|
||||||
def test_mistral_url_from_catalog(self, keystone_client_mock):
|
|
||||||
keystone_client_instance = self.setup_keystone_mock(
|
|
||||||
keystone_client_mock
|
|
||||||
)
|
|
||||||
|
|
||||||
url_for = mock.Mock(return_value='http://mistral_host:8989/v2')
|
|
||||||
|
|
||||||
keystone_client_instance.service_catalog.url_for = url_for
|
|
||||||
|
|
||||||
mistralclient = client.client(
|
|
||||||
username='mistral',
|
|
||||||
project_name='mistral',
|
|
||||||
auth_url=AUTH_HTTP_URL_v3,
|
|
||||||
service_type='workflowv2'
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
'http://mistral_host:8989/v2',
|
|
||||||
mistralclient.actions.http_client.base_url
|
|
||||||
)
|
|
||||||
|
|
||||||
@mock.patch('keystoneclient.client.Client')
|
|
||||||
@mock.patch('mistralclient.api.httpclient.HTTPClient')
|
|
||||||
def test_mistral_url_default(self, http_client_mock, keystone_client_mock):
|
|
||||||
keystone_client_instance = self.setup_keystone_mock(
|
|
||||||
keystone_client_mock
|
|
||||||
)
|
|
||||||
|
|
||||||
url_for = mock.Mock(side_effect=Exception)
|
|
||||||
keystone_client_instance.service_catalog.url_for = url_for
|
|
||||||
|
|
||||||
client.client(
|
|
||||||
username='mistral',
|
|
||||||
project_name='mistral',
|
|
||||||
auth_url=AUTH_HTTP_URL_v3
|
|
||||||
)
|
|
||||||
|
|
||||||
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(MISTRAL_HTTP_URL, mistral_url_for_http)
|
|
||||||
self.assertEqual(
|
|
||||||
keystone_client_instance.auth_token, kwargs['auth_token']
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
keystone_client_instance.project_id, kwargs['project_id']
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
keystone_client_instance.user_id, kwargs['user_id']
|
|
||||||
)
|
|
||||||
|
|
||||||
@mock.patch('keystoneclient.client.Client')
|
|
||||||
@mock.patch('mistralclient.api.httpclient.HTTPClient')
|
|
||||||
def test_target_parameters_processed(
|
|
||||||
self,
|
|
||||||
http_client_mock,
|
|
||||||
keystone_client_mock
|
|
||||||
):
|
|
||||||
keystone_client_instance = self.setup_keystone_mock(
|
|
||||||
keystone_client_mock
|
|
||||||
)
|
|
||||||
|
|
||||||
url_for = mock.Mock(return_value='http://mistral_host:8989/v2')
|
|
||||||
keystone_client_instance.service_catalog.url_for = url_for
|
|
||||||
|
|
||||||
client.client(
|
|
||||||
target_username='tmistral',
|
|
||||||
target_project_name='tmistralp',
|
|
||||||
target_auth_url=AUTH_HTTP_URL_v3,
|
|
||||||
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(MISTRAL_HTTP_URL, mistral_url_for_http)
|
|
||||||
|
|
||||||
expected_values = {
|
|
||||||
'target_project_id': keystone_client_instance.project_id,
|
|
||||||
'target_auth_token': keystone_client_instance.auth_token,
|
|
||||||
'target_user_id': keystone_client_instance.user_id,
|
|
||||||
'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('keystoneclient.client.Client')
|
|
||||||
@mock.patch('mistralclient.api.httpclient.HTTPClient')
|
|
||||||
def test_mistral_url_https_insecure(self, http_client_mock,
|
|
||||||
keystone_client_mock):
|
|
||||||
keystone_client_instance = self.setup_keystone_mock( # noqa
|
|
||||||
keystone_client_mock
|
|
||||||
)
|
|
||||||
|
|
||||||
expected_args = (
|
|
||||||
MISTRAL_HTTPS_URL,
|
|
||||||
)
|
|
||||||
|
|
||||||
client.client(
|
|
||||||
mistral_url=MISTRAL_HTTPS_URL,
|
|
||||||
username='mistral',
|
|
||||||
project_name='mistral',
|
|
||||||
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('keystoneclient.client.Client')
|
|
||||||
@mock.patch('mistralclient.api.httpclient.HTTPClient')
|
|
||||||
def test_mistral_url_https_secure(self, http_client_mock,
|
|
||||||
keystone_client_mock):
|
|
||||||
fd, cert_path = tempfile.mkstemp(suffix='.pem')
|
|
||||||
|
|
||||||
keystone_client_instance = self.setup_keystone_mock( # noqa
|
|
||||||
keystone_client_mock
|
|
||||||
)
|
|
||||||
|
|
||||||
expected_args = (
|
|
||||||
MISTRAL_HTTPS_URL,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
client.client(
|
|
||||||
mistral_url=MISTRAL_HTTPS_URL,
|
|
||||||
username='mistral',
|
|
||||||
project_name='mistral',
|
|
||||||
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('keystoneclient.client.Client')
|
|
||||||
def test_mistral_url_https_bad_cacert(self, keystone_client_mock):
|
|
||||||
keystone_client_instance = self.setup_keystone_mock( # noqa
|
|
||||||
keystone_client_mock
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertRaises(
|
|
||||||
ValueError,
|
|
||||||
client.client,
|
|
||||||
mistral_url=MISTRAL_HTTPS_URL,
|
|
||||||
username='mistral',
|
|
||||||
project_name='mistral',
|
|
||||||
auth_url=AUTH_HTTP_URL_v3,
|
|
||||||
cacert='/path/to/foobar',
|
|
||||||
insecure=False
|
|
||||||
)
|
|
||||||
|
|
||||||
@mock.patch('logging.Logger.warning')
|
|
||||||
@mock.patch('keystoneclient.client.Client')
|
|
||||||
def test_mistral_url_https_bad_insecure(self, keystone_client_mock,
|
|
||||||
log_warning_mock):
|
|
||||||
fd, path = tempfile.mkstemp(suffix='.pem')
|
|
||||||
|
|
||||||
keystone_client_instance = self.setup_keystone_mock(
|
|
||||||
keystone_client_mock
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
client.client(
|
|
||||||
mistral_url=MISTRAL_HTTPS_URL,
|
|
||||||
user_id=keystone_client_instance.user_id,
|
|
||||||
project_id=keystone_client_instance.project_id,
|
|
||||||
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('keystoneclient.client.Client')
|
|
||||||
@mock.patch('mistralclient.api.httpclient.HTTPClient')
|
|
||||||
def test_mistral_profile_enabled(self, http_client_mock,
|
|
||||||
keystone_client_mock):
|
|
||||||
keystone_client_instance = self.setup_keystone_mock( # noqa
|
|
||||||
keystone_client_mock
|
|
||||||
)
|
|
||||||
|
|
||||||
client.client(
|
|
||||||
username='mistral',
|
|
||||||
project_name='mistral',
|
|
||||||
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)
|
|
|
@ -1,276 +0,0 @@
|
||||||
# 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(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()
|
|
|
@ -1,136 +0,0 @@
|
||||||
# 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):
|
|
||||||
|
|
||||||
@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'])
|
|
|
@ -1,65 +0,0 @@
|
||||||
# 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))
|
|
|
@ -1,37 +0,0 @@
|
||||||
# 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.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()
|
|
||||||
|
|
||||||
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
|
|
|
@ -1,114 +0,0 @@
|
||||||
# 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_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'])
|
|
|
@ -1,269 +0,0 @@
|
||||||
# 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_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)
|
|
|
@ -1,228 +0,0 @@
|
||||||
# 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',
|
|
||||||
'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['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
|
|
||||||
)
|
|
|
@ -1,199 +0,0 @@
|
||||||
# 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])
|
|
|
@ -1,23 +0,0 @@
|
||||||
# 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)
|
|
|
@ -1,178 +0,0 @@
|
||||||
# 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
|
|
||||||
)
|
|
|
@ -1,125 +0,0 @@
|
||||||
# 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
|
|
||||||
)
|
|
|
@ -1,100 +0,0 @@
|
||||||
# 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
|
|
||||||
)
|
|
|
@ -1,289 +0,0 @@
|
||||||
# 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',
|
|
||||||
'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',
|
|
||||||
'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',
|
|
||||||
'',
|
|
||||||
'<none>',
|
|
||||||
'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',
|
|
||||||
'description': '',
|
|
||||||
'state': state,
|
|
||||||
'state_info': None,
|
|
||||||
'created_at': '1',
|
|
||||||
'updated_at': '1',
|
|
||||||
'task_execution_id': None
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
ex_result = list(EX_RESULT)
|
|
||||||
ex_result[5] = 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
|
|
||||||
)
|
|
|
@ -1,102 +0,0 @@
|
||||||
# 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']
|
|
||||||
)
|
|
|
@ -1,37 +0,0 @@
|
||||||
# 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])
|
|
|
@ -1,128 +0,0 @@
|
||||||
# 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.tests.unit import base
|
|
||||||
|
|
||||||
TASK_DICT = {
|
|
||||||
'id': '123',
|
|
||||||
'name': 'some',
|
|
||||||
'workflow_name': 'thing',
|
|
||||||
'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])
|
|
||||||
|
|
||||||
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])
|
|
|
@ -1,119 +0,0 @@
|
||||||
# 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])
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue