python-mistralclient-3.7.0/0000775000175000017500000000000013326122642015735 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/playbooks/0000775000175000017500000000000013326122642017740 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/playbooks/legacy/0000775000175000017500000000000013326122642021204 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/playbooks/legacy/python-mistralclient-devstack-dsvm/0000775000175000017500000000000013326122642030146 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/playbooks/legacy/python-mistralclient-devstack-dsvm/post.yaml0000666000175000017500000000063313326122347032025 0ustar zuulzuul00000000000000- hosts: primary tasks: - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=/logs/** - --include=*/ - --exclude=* - --prune-empty-dirs python-mistralclient-3.7.0/playbooks/legacy/python-mistralclient-devstack-dsvm/run.yaml0000666000175000017500000000455713326122347031655 0ustar zuulzuul00000000000000- hosts: all name: Autoconverted job legacy-python-mistralclient-devstack-dsvm from old job gate-python-mistralclient-devstack-dsvm-ubuntu-xenial tasks: - name: Ensure legacy workspace directory file: path: '{{ ansible_user_dir }}/workspace' state: directory - shell: cmd: | set -e set -x cat > clonemap.yaml << EOF clonemap: - name: openstack-infra/devstack-gate dest: devstack-gate EOF /usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \ git://git.openstack.org \ openstack-infra/devstack-gate executable: /bin/bash chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' - shell: cmd: | set -e set -x export PYTHONUNBUFFERED=true export DEVSTACK_GATE_NEUTRON=1 if [ "python-mistralclient" = "python-mistralclient" ] ; then # This puts the repo in PROJECTS export DEVSTACK_PROJECT_FROM_GIT="python-mistralclient" else export PROJECTS="openstack/python-mistralclient $PROJECTS" fi export ENABLED_SERVICES=heat,h-api,h-api-cfn,h-api-cw,h-eng,tempest export PROJECTS="openstack/heat $PROJECTS" export PROJECTS="openstack/mistral $PROJECTS" export PROJECTS="openstack/mistral-dashboard $PROJECTS" export DEVSTACK_LOCAL_CONFIG="enable_plugin mistral https://git.openstack.org/openstack/mistral" export DEVSTACK_LOCAL_CONFIG+=$'\n'"enable_plugin heat git://git.openstack.org/openstack/heat" if [ "" == "-non-apache" ]; then export DEVSTACK_LOCAL_CONFIG+=$'\n'"MISTRAL_USE_MOD_WSGI=False" fi if [ "" == "-kombu" ]; then export DEVSTACK_LOCAL_CONFIG+=$'\n'"MISTRAL_RPC_IMPLEMENTATION=kombu" fi function post_test_hook { cd /opt/stack/new/python-mistralclient/functionaltests ./post_test_hook.sh } export -f post_test_hook cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh executable: /bin/bash chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' python-mistralclient-3.7.0/tools/0000775000175000017500000000000013326122642017075 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/tools/mistral.bash_completion0000666000175000017500000000142013326122347023641 0ustar zuulzuul00000000000000_mistral_opts="" # lazy init _mistral_flags="" # lazy init _mistral_opts_exp="" # lazy init _mistral() { local cur prev mbc cflags COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" if [ "x$_mistral_opts" == "x" ] ; then mbc="`mistral bash-completion`" _mistral_opts="`echo "$mbc" | sed -e "s/\s-[a-z0-9_-]*//g" -e "s/\s\s*/ /g"`" _mistral_flags="`echo " $mbc" | sed -e "s/ [^-][^-][a-z0-9_-]*//g" -e "s/\s\s*/ /g"`" _mistral_opts_exp="`echo "$_mistral_opts" | sed -e "s/\s/|/g"`" fi if [[ " ${COMP_WORDS[@]} " =~ " "($_mistral_opts_exp)" " && "$prev" != "help" ]] ; then COMPREPLY=($(compgen -W "${_mistral_flags}" -- ${cur})) else COMPREPLY=($(compgen -W "${_mistral_opts}" -- ${cur})) fi return 0 } complete -F _mistral mistral python-mistralclient-3.7.0/tools/update_env_deps0000777000175000017500000000102513326122347022172 0ustar zuulzuul00000000000000TOX_ENVLIST=`grep envlist tox.ini | cut -d '=' -f 2 | tr ',' ' '` TESTENVS=`grep testenv tox.ini | awk -F ':' '{print $2}' | tr '[]' ' '` UNFILTERED_ENVLIST=`echo "$TOX_ENVLIST $TESTENVS"` ENVLIST=$( awk 'BEGIN{RS=ORS=" "}!a[$0]++' <<<$UNFILTERED_ENVLIST ); for env in $ENVLIST do ENV_PATH=.tox/$env PIP_PATH=$ENV_PATH/bin/pip echo -e "\nUpdate environment ${env}...\n" if [ ! -d $ENV_PATH -o ! -f $PIP_PATH ] then tox --notest -e$env else $PIP_PATH install -r requirements.txt -r test-requirements.txt fi done python-mistralclient-3.7.0/tools/config/0000775000175000017500000000000013326122642020342 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/tools/config/generate_sample.sh0000777000175000017500000000536113326122347024045 0ustar zuulzuul00000000000000#!/usr/bin/env bash print_hint() { echo "Try \`${0##*/} --help' for more information." >&2 } PARSED_OPTIONS=$(getopt -n "${0##*/}" -o hb:p:o: \ --long help,base-dir:,package-name:,output-dir: -- "$@") if [ $? != 0 ] ; then print_hint ; exit 1 ; fi eval set -- "$PARSED_OPTIONS" while true; do case "$1" in -h|--help) echo "${0##*/} [options]" echo "" echo "options:" echo "-h, --help show brief help" echo "-b, --base-dir=DIR project base directory" echo "-p, --package-name=NAME project package name" echo "-o, --output-dir=DIR file output directory" exit 0 ;; -b|--base-dir) shift BASEDIR=`echo $1 | sed -e 's/\/*$//g'` shift ;; -p|--package-name) shift PACKAGENAME=`echo $1` shift ;; -o|--output-dir) shift OUTPUTDIR=`echo $1 | sed -e 's/\/*$//g'` shift ;; --) break ;; esac done BASEDIR=${BASEDIR:-`pwd`} if ! [ -d $BASEDIR ] then echo "${0##*/}: missing project base directory" >&2 ; print_hint ; exit 1 elif [[ $BASEDIR != /* ]] then BASEDIR=$(cd "$BASEDIR" && pwd) fi PACKAGENAME=${PACKAGENAME:-${BASEDIR##*/}} TARGETDIR=$BASEDIR/$PACKAGENAME if ! [ -d $TARGETDIR ] then echo "${0##*/}: invalid project package name" >&2 ; print_hint ; exit 1 fi OUTPUTDIR=${OUTPUTDIR:-$BASEDIR/etc} # NOTE(bnemec): Some projects put their sample config in etc/, # some in etc/$PACKAGENAME/ if [ -d $OUTPUTDIR/$PACKAGENAME ] then OUTPUTDIR=$OUTPUTDIR/$PACKAGENAME elif ! [ -d $OUTPUTDIR ] then echo "${0##*/}: cannot access \`$OUTPUTDIR': No such file or directory" >&2 exit 1 fi BASEDIRESC=`echo $BASEDIR | sed -e 's/\//\\\\\//g'` find $TARGETDIR -type f -name "*.pyc" -delete FILES=$(find $TARGETDIR -type f -name "*.py" ! -path "*/tests/*" \ -exec grep -l "Opt(" {} + | sed -e "s/^$BASEDIRESC\///g" | sort -u) EXTRA_MODULES_FILE="`dirname $0`/oslo.config.generator.rc" if test -r "$EXTRA_MODULES_FILE" then source "$EXTRA_MODULES_FILE" fi export EVENTLET_NO_GREENDNS=yes OS_VARS=$(set | sed -n '/^OS_/s/=[^=]*$//gp' | xargs) [ "$OS_VARS" ] && eval "unset \$OS_VARS" DEFAULT_MODULEPATH=mistral.openstack.common.config.generator MODULEPATH=${MODULEPATH:-$DEFAULT_MODULEPATH} OUTPUTFILE=$OUTPUTDIR/$PACKAGENAME.conf.sample python -m $MODULEPATH $FILES > $OUTPUTFILE # Hook to allow projects to append custom config file snippets CONCAT_FILES=$(ls $BASEDIR/tools/config/*.conf.sample 2>/dev/null) for CONCAT_FILE in $CONCAT_FILES; do cat $CONCAT_FILE >> $OUTPUTFILE done python-mistralclient-3.7.0/tools/run_pep80000777000175000017500000000002613326122347020565 0ustar zuulzuul00000000000000#!/bin/sh tox -epep8 python-mistralclient-3.7.0/releasenotes/0000775000175000017500000000000013326122642020426 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/releasenotes/source/0000775000175000017500000000000013326122642021726 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/releasenotes/source/ocata.rst0000666000175000017500000000023013326122347023546 0ustar zuulzuul00000000000000=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: origin/stable/ocata python-mistralclient-3.7.0/releasenotes/source/queens.rst0000666000175000017500000000022313326122347023761 0ustar zuulzuul00000000000000=================================== Queens Series Release Notes =================================== .. release-notes:: :branch: stable/queens python-mistralclient-3.7.0/releasenotes/source/mitaka.rst0000666000175000017500000000021113326122347023724 0ustar zuulzuul00000000000000=========================== Mitaka Series Release Notes =========================== .. release-notes:: :branch: origin/stable/mitaka python-mistralclient-3.7.0/releasenotes/source/unreleased.rst0000666000175000017500000000015313326122347024612 0ustar zuulzuul00000000000000============================ Current Series Release Notes ============================ .. release-notes:: python-mistralclient-3.7.0/releasenotes/source/newton.rst0000666000175000017500000000021113326122347023770 0ustar zuulzuul00000000000000=========================== Newton Series Release Notes =========================== .. release-notes:: :branch: origin/stable/newton python-mistralclient-3.7.0/releasenotes/source/liberty.rst0000666000175000017500000000021513326122347024134 0ustar zuulzuul00000000000000============================ Liberty Series Release Notes ============================ .. release-notes:: :branch: origin/stable/liberty python-mistralclient-3.7.0/releasenotes/source/pike.rst0000666000175000017500000000021713326122347023414 0ustar zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike python-mistralclient-3.7.0/releasenotes/source/_static/0000775000175000017500000000000013326122642023354 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/releasenotes/source/_static/.placeholder0000666000175000017500000000000013326122347025631 0ustar zuulzuul00000000000000python-mistralclient-3.7.0/releasenotes/source/_templates/0000775000175000017500000000000013326122642024063 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/releasenotes/source/_templates/.placeholder0000666000175000017500000000000013326122347026340 0ustar zuulzuul00000000000000python-mistralclient-3.7.0/releasenotes/source/conf.py0000666000175000017500000002120413326122347023230 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # # Mistral documentation build configuration file, created by # sphinx-quickstart on Fri Nov 1 02:06:28 2013. # # This file is execfile()d with the current directory set to its containing # dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'openstackdocstheme', 'reno.sphinxext', ] # Add any paths that contain templates here, relative to this directory. # templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Mistral Client Release Notes' copyright = u'2016, Mistral developers' # Release notes are version independent. # The short X.Y version. version = '' # The full version, including alpha/beta/rc tags. release = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # Must set this variable to include year, month, day, hours, and minutes. html_last_updated_fmt = '%Y-%m-%d %H:%M' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'MistralreleaseNotesdoc' # -- Options for LaTeX output ------------------------------------------------ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, # documentclass [howto/manual]). latex_documents = [ ('index', 'MistralClientReleaseNotes.tex', u'Mistral Client Release Notes Documentation', u'Mistral developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'mistral_clientreleasenotes', u'Mistral Client Release Notes Documentation', [u'Mistral developers'], 1) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ---------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'MistralClientReleaseNotes', u'Mistral Client Release Notes Documentation', u'Mistral developers', 'MistralClientReleaseNotes', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] # -- Options for openstackdocstheme ------------------------------------------- repository_name = 'openstack/python-mistralclient' bug_project = 'python-mistralclient' bug_tag = '' python-mistralclient-3.7.0/releasenotes/source/index.rst0000666000175000017500000000026413326122347023575 0ustar zuulzuul00000000000000Mistral Client Release Notes ============================ Contents ======== .. toctree:: :maxdepth: 2 unreleased queens pike ocata newton mitaka liberty python-mistralclient-3.7.0/releasenotes/notes/0000775000175000017500000000000013326122642021556 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/releasenotes/notes/force-delete-executions-d08ce88a5deb3291.yaml0000666000175000017500000000043213326122347031470 0ustar zuulzuul00000000000000--- features: - | Adding a --force optional parameter to delete excetutions. Without it only finished executions can be deleted. If --force is passed the execution will be deleted but mistral will generate some errors as expected objects in memory no longer exist python-mistralclient-3.7.0/releasenotes/notes/fix-region-name-2031ff4b83b6308e.yaml0000666000175000017500000000030713326122347027563 0ustar zuulzuul00000000000000--- fixes: - | ``--os-region-name`` or ``OS_REGION_NAME`` is fully supported now and will be passed to Mistral service in order to get OpenStack service client for the specific region. python-mistralclient-3.7.0/releasenotes/notes/reno.cache0000664000175000017500000000231513326122642023507 0ustar zuulzuul00000000000000--- file-contents: releasenotes/notes/fix-regression-with-execution-force-delete-af8d1968cb2673ef.yaml: fixes: ['mistralclient 3.5.0 introduced a new --force option to delete executions that are still running. However, this had the unintended impact of passing force=false when it wasn''t provided. This is incompatible with previous releases of the Mistral API which reject requests as they don''t recognise "force". '] releasenotes/notes/force-delete-executions-d08ce88a5deb3291.yaml: features: ['Adding a --force optional parameter to delete excetutions. Without it only finished executions can be deleted. If --force is passed the execution will be deleted but mistral will generate some errors as expected objects in memory no longer exist '] notes: - files: - - releasenotes/notes/fix-regression-with-execution-force-delete-af8d1968cb2673ef.yaml - !!binary | MTQ2ZDFjMTdlMjRmOTM2ZjhiYzM2NWM2OWJmOWY3MmEwODRlNjJhZQ== version: 3.6.0 - files: - - releasenotes/notes/force-delete-executions-d08ce88a5deb3291.yaml - !!binary | ZTQwMGJlZDZiMDg4ODI0N2VhZmM5MGZmMzM4MTY1Y2ZlMDFlMDM3Zg== version: 3.5.0 ././@LongLink0000000000000000000000000000015700000000000011220 Lustar 00000000000000python-mistralclient-3.7.0/releasenotes/notes/fix-regression-with-execution-force-delete-af8d1968cb2673ef.yamlpython-mistralclient-3.7.0/releasenotes/notes/fix-regression-with-execution-force-delete-af8d1968cb20000666000175000017500000000052413326122347033517 0ustar zuulzuul00000000000000--- fixes: - | mistralclient 3.5.0 introduced a new --force option to delete executions that are still running. However, this had the unintended impact of passing force=false when it wasn't provided. This is incompatible with previous releases of the Mistral API which reject requests as they don't recognise "force". python-mistralclient-3.7.0/releasenotes/notes/.placeholder0000666000175000017500000000000013326122347024033 0ustar zuulzuul00000000000000python-mistralclient-3.7.0/releasenotes/notes/set-default-limit-value-7e293d843d6d85ac.yaml0000666000175000017500000000041213326122347031337 0ustar zuulzuul00000000000000--- critical: - | The "--limit" parameter of heavy CLI commands like "task-list" and "execution-list" and "action-execution-list" is set to "100" by default to avoid the huge load on server. To fetch the full result set, user may use "--limit -1". python-mistralclient-3.7.0/releasenotes/notes/add-namespaces-to-cli-9d20e7fcb07c223f.yaml0000666000175000017500000000102713326122347030772 0ustar zuulzuul00000000000000--- features: - | Using namespaces is now supported from the client. It is possible to use the namespace flag with all workflows commands and when creating a workflow execution. Namespace feature allows a user to add two workflows with the same name to Mistral as long as they are within two different namespaces. Namespace will also be visible in the table printed when using the CLI to get info about workflows. Plus, the CLI will print the workflow namespace along with the workflow name where needed. python-mistralclient-3.7.0/releasenotes/notes/remove-keystoneclient-dependency-f2981f29e6673f71.yaml0000666000175000017500000000035513326122347033221 0ustar zuulzuul00000000000000--- other: - The dependency to python-keystoneclient was removed. Relying solely on keystoneauth1. - The user has to set the "OS_USER_DOMAIN_NAME" and "OS_PROJECT_DOMAIN_NAME" explicitly if keystone v3 version is being used. python-mistralclient-3.7.0/setup.cfg0000666000175000017500000001243713326122642017567 0ustar zuulzuul00000000000000[metadata] name = python-mistralclient summary = Mistral Client Library description-file = README.rst license = Apache Software License classifiers = Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.5 Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = https://docs.openstack.org/python-mistralclient/latest/ [files] packages = mistralclient [build_sphinx] builders = html,man source-dir = doc/source build-dir = doc/build all_files = 1 warning-is-error = 1 [upload_sphinx] upload-dir = doc/build/html [entry_points] console_scripts = mistral = mistralclient.shell:main openstack.cli.extension = workflow_engine = mistralclient.osc.plugin openstack.workflow_engine.v2 = workbook_list = mistralclient.commands.v2.workbooks:List workbook_show = mistralclient.commands.v2.workbooks:Get workbook_create = mistralclient.commands.v2.workbooks:Create workbook_delete = mistralclient.commands.v2.workbooks:Delete workbook_update = mistralclient.commands.v2.workbooks:Update workbook_definition_show = mistralclient.commands.v2.workbooks:GetDefinition workbook_validate = mistralclient.commands.v2.workbooks:Validate workflow_list = mistralclient.commands.v2.workflows:List workflow_show = mistralclient.commands.v2.workflows:Get workflow_create = mistralclient.commands.v2.workflows:Create workflow_delete = mistralclient.commands.v2.workflows:Delete workflow_update = mistralclient.commands.v2.workflows:Update workflow_definition_show = mistralclient.commands.v2.workflows:GetDefinition workflow_validate = mistralclient.commands.v2.workflows:Validate workflow_env_create = mistralclient.commands.v2.environments:Create workflow_env_delete = mistralclient.commands.v2.environments:Delete workflow_env_update = mistralclient.commands.v2.environments:Update workflow_env_list = mistralclient.commands.v2.environments:List workflow_env_show = mistralclient.commands.v2.environments:Get action_execution_run = mistralclient.commands.v2.action_executions:Create action_execution_list = mistralclient.commands.v2.action_executions:List action_execution_show = mistralclient.commands.v2.action_executions:Get action_execution_input_show = mistralclient.commands.v2.action_executions:GetInput action_execution_output_show = mistralclient.commands.v2.action_executions:GetOutput action_execution_update = mistralclient.commands.v2.action_executions:Update action_execution_delete = mistralclient.commands.v2.action_executions:Delete workflow_execution_create = mistralclient.commands.v2.executions:Create workflow_execution_delete = mistralclient.commands.v2.executions:Delete workflow_execution_update = mistralclient.commands.v2.executions:Update workflow_execution_list = mistralclient.commands.v2.executions:List workflow_execution_show = mistralclient.commands.v2.executions:Get workflow_execution_input_show = mistralclient.commands.v2.executions:GetInput workflow_execution_output_show = mistralclient.commands.v2.executions:GetOutput task_execution_list = mistralclient.commands.v2.tasks:List task_execution_show = mistralclient.commands.v2.tasks:Get task_execution_published_show = mistralclient.commands.v2.tasks:GetPublished task_execution_result_show = mistralclient.commands.v2.tasks:GetResult task_execution_rerun = mistralclient.commands.v2.tasks:Rerun action_definition_list = mistralclient.commands.v2.actions:List action_definition_show = mistralclient.commands.v2.actions:Get action_definition_create = mistralclient.commands.v2.actions:Create action_definition_delete = mistralclient.commands.v2.actions:Delete action_definition_update = mistralclient.commands.v2.actions:Update action_definition_definition_show = mistralclient.commands.v2.actions:GetDefinition cron_trigger_list = mistralclient.commands.v2.cron_triggers:List cron_trigger_show = mistralclient.commands.v2.cron_triggers:Get cron_trigger_create = mistralclient.commands.v2.cron_triggers:Create cron_trigger_delete = mistralclient.commands.v2.cron_triggers:Delete event_trigger_list = mistralclient.commands.v2.event_triggers:List event_trigger_show = mistralclient.commands.v2.event_triggers:Get event_trigger_create = mistralclient.commands.v2.event_triggers:Create event_trigger_delete = mistralclient.commands.v2.event_triggers:Delete workflow_engine_service_list = mistralclient.commands.v2.services:List resource_member_list = mistralclient.commands.v2.members:List resource_member_show = mistralclient.commands.v2.members:Get resource_member_create = mistralclient.commands.v2.members:Create resource_member_delete = mistralclient.commands.v2.members:Delete resource_member_update = mistralclient.commands.v2.members:Update mistralclient.auth = keystone = mistralclient.auth.keystone:KeystoneAuthHandler keycloak-oidc = mistralclient.auth.keycloak:KeycloakAuthHandler [nosetests] cover-package = mistralclient [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext [pbr] autodoc_index_modules = True warnerrors = True [egg_info] tag_build = tag_date = 0 python-mistralclient-3.7.0/.zuul.yaml0000666000175000017500000000257513326122347017713 0ustar zuulzuul00000000000000- job: name: python-mistralclient-devstack-dsvm parent: legacy-dsvm-base run: playbooks/legacy/python-mistralclient-devstack-dsvm/run.yaml post-run: playbooks/legacy/python-mistralclient-devstack-dsvm/post.yaml timeout: 4200 required-projects: - openstack-dev/devstack - openstack-infra/devstack-gate - openstack/heat - openstack/mistral - openstack/mistral-dashboard - openstack/python-mistralclient - project: check: jobs: - python-mistralclient-devstack-dsvm # TripleO jobs that deploy Mistral. # Note we don't use a project-template here, so it's easier # to disable voting on one specific job if things go wrong. # tripleo-ci-centos-7-scenario003-multinode-oooq will only # run on stable/pike while the -container will run in Queens # and beyond. # If you need any support to debug these jobs in case of # failures, please reach us on #tripleo IRC channel. - tripleo-ci-centos-7-scenario003-multinode-oooq - tripleo-ci-centos-7-scenario003-multinode-oooq-container - openstack-tox-lower-constraints gate: jobs: - python-mistralclient-devstack-dsvm - tripleo-ci-centos-7-scenario003-multinode-oooq - tripleo-ci-centos-7-scenario003-multinode-oooq-container - openstack-tox-lower-constraints python-mistralclient-3.7.0/run_functional_tests.sh0000777000175000017500000000233513326122347022553 0ustar zuulzuul00000000000000#! /usr/bin/env bash ARG=$1 export USER_AUTH_SETTING=$(echo $OS_AUTH_URL) function pre_hook() { export WITHOUT_AUTH="True" IS_TEMPEST=$(pip freeze | grep tempest) if [ -z "$IS_TEMPEST" ] then echo "$(tput setaf 4)No such module 'tempest' in the system. Before running this script please install tempest module using : pip install git+http://github.com/openstack/tempest.git$(tput sgr 0)" fi } function run_tests_by_version() { export OS_AUTH_URL="" echo "$(tput setaf 4)Running integration CLI and python-mistralclient tests for v$1$(tput sgr 0)" export VERSION="v$1" nosetests -v mistralclient/tests/functional/cli/v$1/ nosetests -v mistralclient/tests/functional/client/v$1/ unset VERSION } function run_tests() { if [ -z "$ARG" ] then run_tests_by_version 1 run_tests_by_version 2 elif [ "$ARG" == "v1" ] then run_tests_by_version 1 elif [ "$ARG" == "v2" ] then run_tests_by_version 2 fi } function post_hook () { unset LOCAL_RUN export OS_AUTH_URL=$USER_AUTH_SETTING unset USER_AUTH_SETTING } #----------main-part---------- echo "$(tput setaf 4)Preparation for tests running...$(tput sgr 0)" pre_hook echo "$(tput setaf 4)Running tests...$(tput sgr 0)" run_tests post_hook python-mistralclient-3.7.0/mistralclient/0000775000175000017500000000000013326122642020607 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/mistralclient/tests/0000775000175000017500000000000013326122642021751 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/mistralclient/tests/functional/0000775000175000017500000000000013326122642024113 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/mistralclient/tests/functional/cli/0000775000175000017500000000000013326122642024662 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/mistralclient/tests/functional/cli/base.py0000666000175000017500000001272513326122347026161 0ustar zuulzuul00000000000000# Copyright (c) 2014 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os from six.moves import configparser from tempest.lib.cli import base CLI_DIR = os.environ.get( 'OS_MISTRALCLIENT_EXEC_DIR', os.path.join(os.path.abspath('.'), '.tox/functional/bin') ) _CREDS_FILE = 'functional_creds.conf' def credentials(group='admin'): """Retrieves credentials to run functional tests. Credentials are either read from the environment or from a config file ('functional_creds.conf'). Environment variables override those from the config file. The 'functional_creds.conf' file is the clean and new way to use (by default tox 2.0 does not pass environment variables). """ if group == 'admin': username = os.environ.get('OS_USERNAME') password = os.environ.get('OS_PASSWORD') tenant_name = os.environ.get('OS_TENANT_NAME') user_domain = os.environ.get('OS_USER_DOMAIN_NAME') project_domain = os.environ.get('OS_PROJECT_DOMAIN_NAME') else: username = os.environ.get('OS_ALT_USERNAME') password = os.environ.get('OS_ALT_PASSWORD') tenant_name = os.environ.get('OS_ALT_TENANT_NAME') user_domain = os.environ.get('OS_ALT_USER_DOMAIN_NAME') project_domain = os.environ.get('OS_ALT_PROJECT_DOMAIN_NAME') auth_url = os.environ.get('OS_AUTH_URL') config = configparser.RawConfigParser() if config.read(_CREDS_FILE): username = username or config.get(group, 'user') password = password or config.get(group, 'pass') tenant_name = tenant_name or config.get(group, 'tenant') auth_url = auth_url or config.get('auth', 'uri') user_domain = user_domain or config.get(group, 'user_domain') project_domain = project_domain or config.get(group, 'project_domain') # TODO(ddeja): Default value of OS_AUTH_URL is to provide url to v3 API. # Since tempest openstack client doesn't properly handle it, we switch # it back to v2. Once tempest openstack starts to use v3, this can be # deleted. # https://github.com/openstack/tempest/blob/master/tempest/lib/cli/base.py#L363 return { 'username': username, 'password': password, 'tenant_name': tenant_name, 'auth_url': auth_url.replace('v3', 'v2.0') } class MistralCLIAuth(base.ClientTestBase): _mistral_url = None def _get_admin_clients(self): creds = credentials() clients = base.CLIClient( username=creds['username'], password=creds['password'], tenant_name=creds['tenant_name'], project_name=creds['tenant_name'], uri=creds['auth_url'], cli_dir=CLI_DIR ) return clients def _get_clients(self): return self._get_admin_clients() def mistral(self, action, flags='', params='', fail_ok=False): """Executes Mistral command.""" mistral_url_op = "--os-mistral-url %s" % self._mistral_url if 'WITHOUT_AUTH' in os.environ: return base.execute( 'mistral %s' % mistral_url_op, action, flags, params, fail_ok, merge_stderr=False, cli_dir='' ) else: return self.clients.cmd_with_auth( 'mistral %s' % mistral_url_op, action, flags, params, fail_ok ) def get_project_id(self, project='admin'): project_name = credentials(project)['tenant_name'] admin_clients = self._get_clients() # TODO(mfedosin): when bug #1719687 is closed we should provide # domain names in related parameters, not just as abstract flags flags = "--os-user-domain-name default " \ "--os-project-domain-name default " \ "--os-identity-api-version 3" projects = self.parser.listing( admin_clients.openstack( 'project show', params=project_name, flags=flags ) ) return [o['Value'] for o in projects if o['Field'] == 'id'][0] class MistralCLIAltAuth(base.ClientTestBase): _mistral_url = None def _get_alt_clients(self): creds = credentials('demo') clients = base.CLIClient( username=creds['username'], password=creds['password'], project_name=creds['tenant_name'], tenant_name=creds['tenant_name'], uri=creds['auth_url'], cli_dir=CLI_DIR ) return clients def _get_clients(self): return self._get_alt_clients() def mistral_alt(self, action, flags='', params='', mode='alt_user'): """Executes Mistral command for alt_user from alt_tenant.""" mistral_url_op = "--os-mistral-url %s" % self._mistral_url return self.clients.cmd_with_auth( 'mistral %s' % mistral_url_op, action, flags, params) python-mistralclient-3.7.0/mistralclient/tests/functional/cli/v2/0000775000175000017500000000000013326122642025211 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/mistralclient/tests/functional/cli/v2/cli_tests_v2.py0000666000175000017500000021546613326122347030205 0ustar zuulzuul00000000000000# Copyright (c) 2014 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib import exceptions from mistralclient.tests.functional.cli import base from mistralclient.tests.functional.cli.v2 import base_v2 MISTRAL_URL = "http://localhost:8989/v2" class SimpleMistralCLITests(base.MistralCLIAuth): """Basic tests, check '-list', '-help' commands.""" _mistral_url = MISTRAL_URL @classmethod def setUpClass(cls): super(SimpleMistralCLITests, cls).setUpClass() def test_workbooks_list(self): workbooks = self.parser.listing(self.mistral('workbook-list')) self.assertTableStruct( workbooks, ['Name', 'Tags', 'Created at', 'Updated at'] ) def test_workflow_list(self): workflows = self.parser.listing(self.mistral('workflow-list')) self.assertTableStruct( workflows, ['ID', 'Name', 'Tags', 'Input', 'Scope', 'Created at', 'Updated at'] ) def test_executions_list(self): executions = self.parser.listing(self.mistral('execution-list')) self.assertTableStruct( executions, ['ID', 'Workflow name', 'Workflow ID', 'State', 'Created at', 'Updated at'] ) def test_tasks_list(self): tasks = self.parser.listing(self.mistral('task-list')) self.assertTableStruct( tasks, ['ID', 'Name', 'Workflow name', 'Workflow Execution ID', 'State'] ) def test_cron_trigger_list(self): triggers = self.parser.listing(self.mistral('cron-trigger-list')) self.assertTableStruct( triggers, ['Name', 'Workflow', 'Pattern', 'Next execution time', 'Remaining executions', 'Created at', 'Updated at'] ) def test_event_trigger_list(self): triggers = self.parser.listing(self.mistral('event-trigger-list')) self.assertTableStruct( triggers, ['ID', 'Name', 'Workflow ID', 'Exchange', 'Topic', 'Event', 'Created at', 'Updated at'] ) def test_actions_list(self): actions = self.parser.listing(self.mistral('action-list')) self.assertTableStruct( actions, ['Name', 'Is system', 'Input', 'Description', 'Tags', 'Created at', 'Updated at'] ) def test_environments_list(self): envs = self.parser.listing(self.mistral('environment-list')) self.assertTableStruct( envs, ['Name', 'Description', 'Scope', 'Created at', 'Updated at'] ) def test_action_execution_list(self): act_execs = self.parser.listing(self.mistral('action-execution-list')) self.assertTableStruct( act_execs, ['ID', 'Name', 'Workflow name', 'State', 'Accepted'] ) def test_action_execution_list_with_limit(self): act_execs = self.parser.listing( self.mistral( 'action-execution-list', params='--limit 1' ) ) self.assertEqual(1, len(act_execs)) class WorkbookCLITests(base_v2.MistralClientTestBase): """Test suite checks commands to work with workbooks.""" @classmethod def setUpClass(cls): super(WorkbookCLITests, cls).setUpClass() def test_workbook_create_delete(self): wb = self.mistral_admin('workbook-create', params=self.wb_def) wb_name = self.get_field_value(wb, "Name") self.assertTableStruct(wb, ['Field', 'Value']) wbs = self.mistral_admin('workbook-list') self.assertIn(wb_name, [w['Name'] for w in wbs]) wbs = self.mistral_admin('workbook-list') self.assertIn(wb_name, [w['Name'] for w in wbs]) self.mistral_admin('workbook-delete', params=wb_name) wbs = self.mistral_admin('workbook-list') self.assertNotIn(wb_name, [w['Name'] for w in wbs]) def test_workbook_create_with_tags(self): wb = self.workbook_create(self.wb_with_tags_def) self.assertIn('tag', self.get_field_value(wb, 'Tags')) def test_workbook_update(self): wb = self.workbook_create(self.wb_def) wb_name = self.get_field_value(wb, "Name") init_update_at = self.get_field_value(wb, "Updated at") tags = self.get_field_value(wb, 'Tags') self.assertNotIn('tag', tags) wb = self.mistral_admin('workbook-update', params=self.wb_def) update_at = self.get_field_value(wb, "Updated at") name = self.get_field_value(wb, 'Name') tags = self.get_field_value(wb, 'Tags') self.assertEqual(wb_name, name) self.assertNotIn('tag', tags) self.assertEqual(init_update_at, update_at) wb = self.mistral_admin( 'workbook-update', params=self.wb_with_tags_def ) self.assertTableStruct(wb, ['Field', 'Value']) update_at = self.get_field_value(wb, "Updated at") name = self.get_field_value(wb, 'Name') tags = self.get_field_value(wb, 'Tags') self.assertEqual(wb_name, name) self.assertIn('tag', tags) self.assertNotEqual(init_update_at, update_at) def test_workbook_get(self): created = self.workbook_create(self.wb_with_tags_def) wb_name = self.get_field_value(created, "Name") fetched = self.mistral_admin('workbook-get', params=wb_name) created_wb_name = self.get_field_value(created, 'Name') fetched_wb_name = self.get_field_value(fetched, 'Name') self.assertEqual(created_wb_name, fetched_wb_name) created_wb_tag = self.get_field_value(created, 'Tags') fetched_wb_tag = self.get_field_value(fetched, 'Tags') self.assertEqual(created_wb_tag, fetched_wb_tag) def test_workbook_get_definition(self): wb = self.workbook_create(self.wb_def) wb_name = self.get_field_value(wb, "Name") definition = self.mistral_admin( 'workbook-get-definition', params=wb_name ) self.assertNotIn('404 Not Found', definition) def test_workbook_validate_with_valid_def(self): wb = self.mistral_admin('workbook-validate', params=self.wb_def) wb_valid = self.get_field_value(wb, 'Valid') wb_error = self.get_field_value(wb, 'Error') self.assertEqual('True', wb_valid) self.assertEqual('None', wb_error) def test_workbook_validate_with_invalid_def(self): self.create_file('wb.yaml', 'name: wb\n') wb = self.mistral_admin('workbook-validate', params='wb.yaml') wb_valid = self.get_field_value(wb, 'Valid') wb_error = self.get_field_value(wb, 'Error') self.assertEqual('False', wb_valid) self.assertNotEqual('None', wb_error) class WorkflowCLITests(base_v2.MistralClientTestBase): """Test suite checks commands to work with workflows.""" @classmethod def setUpClass(cls): super(WorkflowCLITests, cls).setUpClass() def test_workflow_create_delete(self): init_wfs = self.mistral_admin('workflow-create', params=self.wf_def) wf_names = [wf['Name'] for wf in init_wfs] self.assertTableStruct(init_wfs, ['Name', 'Created at', 'Updated at']) wfs = self.mistral_admin('workflow-list') self.assertIn(wf_names[0], [workflow['Name'] for workflow in wfs]) for wf_name in wf_names: self.mistral_admin('workflow-delete', params=wf_name) wfs = self.mistral_admin('workflow-list') for wf in wf_names: self.assertNotIn(wf, [workflow['Name'] for workflow in wfs]) def test_workflow_within_namespace_create_delete(self): params = self.wf_def + ' --namespace abcdef' init_wfs = self.mistral_admin('workflow-create', params=params) wf_names = [wf['Name'] for wf in init_wfs] self.assertTableStruct(init_wfs, ['Name', 'Created at', 'Updated at']) wfs = self.mistral_admin('workflow-list') self.assertIn(wf_names[0], [workflow['Name'] for workflow in wfs]) for wf_name in wf_names: self.mistral_admin( 'workflow-delete', params=wf_name+' --namespace abcdef' ) wfs = self.mistral_admin('workflow-list') for wf in wf_names: self.assertNotIn(wf, [workflow['Name'] for workflow in wfs]) init_wfs = self.mistral_admin('workflow-create', params=params) wf_ids = [wf['ID'] for wf in init_wfs] for wf_id in wf_ids: self.mistral_admin('workflow-delete', params=wf_id) for wf in wf_names: self.assertNotIn(wf, [workflow['Name'] for workflow in wfs]) def test_create_wf_with_tags(self): init_wfs = self.workflow_create(self.wf_def) wf_name = init_wfs[1]['Name'] self.assertTableStruct( init_wfs, ['Name', 'Created at', 'Updated at', 'Tags'] ) created_wf_info = self.get_item_info( get_from=init_wfs, get_by='Name', value=wf_name ) self.assertEqual('tag', created_wf_info['Tags']) def test_workflow_update(self): wf = self.workflow_create(self.wf_def) wf_name = wf[0]['Name'] wf_id = wf[0]['ID'] created_wf_info = self.get_item_info( get_from=wf, get_by='Name', value=wf_name ) # Update a workflow with definition unchanged. upd_wf = self.mistral_admin( 'workflow-update', params='{0}'.format(self.wf_def) ) self.assertTableStruct(upd_wf, ['Name', 'Created at', 'Updated at']) updated_wf_info = self.get_item_info( get_from=upd_wf, get_by='Name', value=wf_name ) self.assertEqual(wf_name, upd_wf[0]['Name']) self.assertEqual( created_wf_info['Created at'].split(".")[0], updated_wf_info['Created at'] ) self.assertEqual( created_wf_info['Updated at'], updated_wf_info['Updated at'] ) # Update a workflow with definition changed. upd_wf = self.mistral_admin( 'workflow-update', params='{0}'.format(self.wf_with_delay_def) ) self.assertTableStruct(upd_wf, ['Name', 'Created at', 'Updated at']) updated_wf_info = self.get_item_info( get_from=upd_wf, get_by='Name', value=wf_name ) self.assertEqual(wf_name, upd_wf[0]['Name']) self.assertEqual( created_wf_info['Created at'].split(".")[0], updated_wf_info['Created at'] ) self.assertNotEqual( created_wf_info['Updated at'], updated_wf_info['Updated at'] ) # Update a workflow with uuid. upd_wf = self.mistral_admin( 'workflow-update', params='{0} --id {1}'.format(self.wf_with_delay_def, wf_id) ) self.assertTableStruct(upd_wf, ['Name', 'Created at', 'Updated at']) updated_wf_info = self.get_item_info( get_from=upd_wf, get_by='ID', value=wf_id ) self.assertEqual(wf_name, upd_wf[0]['Name']) self.assertEqual( created_wf_info['Created at'].split(".")[0], updated_wf_info['Created at'] ) self.assertNotEqual( created_wf_info['Updated at'], updated_wf_info['Updated at'] ) def test_workflow_update_within_namespace(self): namespace = 'abc' wf = self.workflow_create(self.wf_def, namespace=namespace) wf_name = wf[0]['Name'] wf_id = wf[0]['ID'] wf_namespace = wf[0]['Namespace'] created_wf_info = self.get_item_info( get_from=wf, get_by='Name', value=wf_name ) # Update a workflow with definition unchanged. upd_wf = self.mistral_admin( 'workflow-update', params='{0} --namespace {1}'.format(self.wf_def, namespace) ) self.assertTableStruct(upd_wf, ['Name', 'Created at', 'Updated at']) updated_wf_info = self.get_item_info( get_from=upd_wf, get_by='Name', value=wf_name ) self.assertEqual(wf_name, upd_wf[0]['Name']) self.assertEqual(namespace, wf_namespace) self.assertEqual(wf_namespace, upd_wf[0]['Namespace']) self.assertEqual( created_wf_info['Created at'].split(".")[0], updated_wf_info['Created at'] ) self.assertEqual( created_wf_info['Updated at'], updated_wf_info['Updated at'] ) # Update a workflow with definition changed. upd_wf = self.mistral_admin( 'workflow-update', params='{0} --namespace {1}'.format( self.wf_with_delay_def, namespace ) ) self.assertTableStruct(upd_wf, ['Name', 'Created at', 'Updated at']) updated_wf_info = self.get_item_info( get_from=upd_wf, get_by='Name', value=wf_name ) self.assertEqual(wf_name, upd_wf[0]['Name']) self.assertEqual( created_wf_info['Created at'].split(".")[0], updated_wf_info['Created at'] ) self.assertNotEqual( created_wf_info['Updated at'], updated_wf_info['Updated at'] ) # Update a workflow with uuid. upd_wf = self.mistral_admin( 'workflow-update', params='{0} --id {1}'.format(self.wf_with_delay_def, wf_id) ) self.assertTableStruct(upd_wf, ['Name', 'Created at', 'Updated at']) updated_wf_info = self.get_item_info( get_from=upd_wf, get_by='ID', value=wf_id ) self.assertEqual(wf_name, upd_wf[0]['Name']) self.assertEqual( created_wf_info['Created at'].split(".")[0], updated_wf_info['Created at'] ) self.assertNotEqual( created_wf_info['Updated at'], updated_wf_info['Updated at'] ) def test_workflow_update_truncate_input(self): input_value = "very_long_input_parameter_name_that_should_be_truncated" wf_def = """ version: "2.0" workflow1: input: - {0} tasks: task1: action: std.noop """.format(input_value) self.create_file('wf.yaml', wf_def) self.workflow_create('wf.yaml') updated_wf = self.mistral_admin('workflow-update', params='wf.yaml') updated_wf_info = self.get_item_info( get_from=updated_wf, get_by='Name', value='workflow1' ) self.assertEqual(updated_wf_info['Input'][:-3], input_value[:25]) def test_workflow_get(self): created = self.workflow_create(self.wf_def) wf_name = created[0]['Name'] fetched = self.mistral_admin('workflow-get', params=wf_name) fetched_wf_name = self.get_field_value(fetched, 'Name') self.assertEqual(wf_name, fetched_wf_name) def test_workflow_get_with_id(self): created = self.workflow_create(self.wf_def) wf_name = created[0]['Name'] wf_id = created[0]['ID'] fetched = self.mistral_admin('workflow-get', params=wf_id) fetched_wf_name = self.get_field_value(fetched, 'Name') self.assertEqual(wf_name, fetched_wf_name) def test_workflow_get_definition(self): wf = self.workflow_create(self.wf_def) wf_name = wf[0]['Name'] definition = self.mistral_admin( 'workflow-get-definition', params=wf_name ) self.assertNotIn('404 Not Found', definition) def test_workflow_validate_with_valid_def(self): wf = self.mistral_admin('workflow-validate', params=self.wf_def) wf_valid = self.get_field_value(wf, 'Valid') wf_error = self.get_field_value(wf, 'Error') self.assertEqual('True', wf_valid) self.assertEqual('None', wf_error) def test_workflow_validate_with_invalid_def(self): self.create_file('wf.yaml', 'name: wf\n') wf = self.mistral_admin('workflow-validate', params='wf.yaml') wf_valid = self.get_field_value(wf, 'Valid') wf_error = self.get_field_value(wf, 'Error') self.assertEqual('False', wf_valid) self.assertNotEqual('None', wf_error) def test_workflow_list_with_filter(self): workflows = self.parser.listing(self.mistral('workflow-list')) self.assertTableStruct( workflows, ['ID', 'Name', 'Tags', 'Input', 'Scope', 'Created at', 'Updated at'] ) # We know that we have more than one workflow by default. self.assertGreater(len(workflows), 1) # Now let's provide a filter to the list command. workflows = self.parser.listing( self.mistral( 'workflow-list', params='--filter name=std.create_instance' ) ) self.assertTableStruct( workflows, ['ID', 'Name', 'Tags', 'Input', 'Scope', 'Created at', 'Updated at'] ) self.assertEqual(1, len(workflows)) self.assertIn('std.create_instance', workflows[0]['Name']) class ExecutionCLITests(base_v2.MistralClientTestBase): """Test suite checks commands to work with executions.""" @classmethod def setUpClass(cls): super(ExecutionCLITests, cls).setUpClass() def setUp(self): super(ExecutionCLITests, self).setUp() wfs = self.workflow_create(self.wf_def) self.async_wf = self.workflow_create(self.async_wf_def)[0] self.direct_wf = wfs[0] self.reverse_wf = wfs[1] self.create_file('input', '{\n "farewell": "Bye"\n}\n') self.create_file('task_name', '{\n "task_name": "goodbye"\n}\n') def test_execution_by_id_of_workflow_within_namespace(self): namespace = 'abc' wfs = self.workflow_create(self.lowest_level_wf, namespace=namespace) wf_def_name = wfs[0]['Name'] wf_id = wfs[0]['ID'] execution = self.execution_create(wf_id) self.assertTableStruct(execution, ['Field', 'Value']) wf_name = self.get_field_value(execution, 'Workflow name') wf_namespace = self.get_field_value(execution, 'Workflow namespace') wf_id = self.get_field_value(execution, 'Workflow ID') self.assertEqual(wf_def_name, wf_name) self.assertEqual(namespace, wf_namespace) self.assertIsNotNone(wf_id) def test_execution_within_namespace_create_delete(self): namespace = 'abc' self.workflow_create(self.lowest_level_wf) self.workflow_create(self.lowest_level_wf, namespace=namespace) self.workflow_create(self.middle_wf, namespace=namespace) self.workflow_create(self.top_level_wf) wfs = self.workflow_create(self.top_level_wf, namespace=namespace) top_wf_name = wfs[0]['Name'] execution = self.mistral_admin( 'execution-create', params='{0} --namespace {1}'.format(top_wf_name, namespace) ) exec_id = self.get_field_value(execution, 'ID') self.assertTableStruct(execution, ['Field', 'Value']) wf_name = self.get_field_value(execution, 'Workflow name') wf_namespace = self.get_field_value(execution, 'Workflow namespace') wf_id = self.get_field_value(execution, 'Workflow ID') created_at = self.get_field_value(execution, 'Created at') self.assertEqual(top_wf_name, wf_name) self.assertEqual(namespace, wf_namespace) self.assertIsNotNone(wf_id) self.assertIsNotNone(created_at) execs = self.mistral_admin('execution-list') self.assertIn(exec_id, [ex['ID'] for ex in execs]) self.assertIn(wf_name, [ex['Workflow name'] for ex in execs]) self.assertIn(namespace, [ex['Workflow namespace'] for ex in execs]) params = "{} --force".format(exec_id) self.mistral_admin('execution-delete', params=params) def test_execution_create_delete(self): execution = self.mistral_admin( 'execution-create', params='{0} -d "execution test"'.format(self.direct_wf['Name']) ) exec_id = self.get_field_value(execution, 'ID') self.assertTableStruct(execution, ['Field', 'Value']) wf_name = self.get_field_value(execution, 'Workflow name') wf_id = self.get_field_value(execution, 'Workflow ID') created_at = self.get_field_value(execution, 'Created at') description = self.get_field_value(execution, 'Description') self.assertEqual(self.direct_wf['Name'], wf_name) self.assertIsNotNone(wf_id) self.assertIsNotNone(created_at) self.assertEqual("execution test", description) execs = self.mistral_admin('execution-list') self.assertIn(exec_id, [ex['ID'] for ex in execs]) self.assertIn(wf_name, [ex['Workflow name'] for ex in execs]) params = "{} --force".format(exec_id) self.mistral_admin('execution-delete', params=params) def test_execution_create_with_input_and_start_task(self): execution = self.execution_create( "%s input task_name" % self.reverse_wf['Name'] ) exec_id = self.get_field_value(execution, 'ID') result = self.wait_execution_success(exec_id) self.assertTrue(result) def test_execution_update(self): execution = self.execution_create(self.async_wf['Name']) exec_id = self.get_field_value(execution, 'ID') status = self.get_field_value(execution, 'State') self.assertEqual('RUNNING', status) # Update execution state. execution = self.mistral_admin( 'execution-update', params='{0} -s PAUSED'.format(exec_id)) updated_exec_id = self.get_field_value(execution, 'ID') status = self.get_field_value(execution, 'State') self.assertEqual(exec_id, updated_exec_id) self.assertEqual('PAUSED', status) # Update execution description. execution = self.mistral_admin( 'execution-update', params='{0} -d "execution update test"'.format(exec_id) ) description = self.get_field_value(execution, 'Description') self.assertEqual("execution update test", description) def test_execution_get(self): execution = self.execution_create(self.direct_wf['Name']) exec_id = self.get_field_value(execution, 'ID') execution = self.mistral_admin( 'execution-get', params='{0}'.format(exec_id) ) gotten_id = self.get_field_value(execution, 'ID') wf_name = self.get_field_value(execution, 'Workflow name') wf_id = self.get_field_value(execution, 'Workflow ID') self.assertIsNotNone(wf_id) self.assertEqual(exec_id, gotten_id) self.assertEqual(self.direct_wf['Name'], wf_name) def test_execution_get_input(self): execution = self.execution_create(self.direct_wf['Name']) exec_id = self.get_field_value(execution, 'ID') ex_input = self.mistral_admin('execution-get-input', params=exec_id) self.assertEqual([], ex_input) def test_execution_get_output(self): execution = self.execution_create(self.direct_wf['Name']) exec_id = self.get_field_value(execution, 'ID') ex_output = self.mistral_admin('execution-get-output', params=exec_id) self.assertEqual([], ex_output) def test_executions_list_with_task(self): wrapping_wf = self.workflow_create(self.wf_wrapping_wf) decoy = self.execution_create(wrapping_wf[-1]['Name']) wrapping_wf_ex = self.execution_create(wrapping_wf[-1]['Name']) wrapping_wf_ex_id = self.get_field_value(wrapping_wf_ex, 'ID') self.assertIsNot(wrapping_wf_ex_id, self.get_field_value(decoy, 'ID')) tasks = self.mistral_admin('task-list', params=wrapping_wf_ex_id) wrapping_task_id = tasks[-1]['ID'] wf_execs = self.mistral_cli( True, 'execution-list', params="--task {}".format(wrapping_task_id) ) self.assertEqual(1, len(wf_execs)) wf_exec = wf_execs[0] self.assertEqual(wrapping_task_id, wf_exec['Task Execution ID']) def test_executions_list_with_pagination(self): self.execution_create( params='{0} -d "a"'.format(self.direct_wf['Name']) ) self.execution_create( params='{0} -d "b"'.format(self.direct_wf['Name']) ) all_wf_execs = self.mistral_cli(True, 'execution-list') self.assertEqual(2, len(all_wf_execs)) wf_execs = self.mistral_cli( True, 'execution-list', params="--limit 1" ) self.assertEqual(1, len(wf_execs)) wf_ex1_id = all_wf_execs[0]['ID'] wf_ex2_id = all_wf_execs[1]['ID'] wf_execs = self.mistral_cli( True, 'execution-list', params="--marker %s" % wf_ex1_id ) self.assertNotIn(wf_ex1_id, [ex['ID'] for ex in wf_execs]) self.assertIn(wf_ex2_id, [ex['ID'] for ex in wf_execs]) wf_execs = self.mistral_cli( True, 'execution-list', params="--sort_keys Description" ) self.assertIn(wf_ex1_id, [ex['ID'] for ex in wf_execs]) self.assertIn(wf_ex2_id, [ex['ID'] for ex in wf_execs]) wf_ex1_index = -1 wf_ex2_index = -1 for idx, ex in enumerate(wf_execs): if ex['ID'] == wf_ex1_id: wf_ex1_index = idx elif ex['ID'] == wf_ex2_id: wf_ex2_index = idx self.assertLess(wf_ex1_index, wf_ex2_index) wf_execs = self.mistral_cli( True, 'execution-list', params="--sort_keys Description --sort_dirs=desc" ) self.assertIn(wf_ex1_id, [ex['ID'] for ex in wf_execs]) self.assertIn(wf_ex2_id, [ex['ID'] for ex in wf_execs]) wf_ex1_index = -1 wf_ex2_index = -1 for idx, ex in enumerate(wf_execs): if ex['ID'] == wf_ex1_id: wf_ex1_index = idx elif ex['ID'] == wf_ex2_id: wf_ex2_index = idx self.assertGreater(wf_ex1_index, wf_ex2_index) def test_execution_list_with_filter(self): wf_ex1 = self.execution_create( params='{0} -d "a"'.format(self.direct_wf['Name']) ) wf_ex1_id = self.get_field_value(wf_ex1, 'ID') self.execution_create( params='{0} -d "b"'.format(self.direct_wf['Name']) ) # Request a list without filters. wf_execs = self.mistral_cli(True, 'execution-list') self.assertTableStruct( wf_execs, ['ID', 'Workflow name', 'Workflow ID', 'State', 'Created at', 'Updated at'] ) self.assertEqual(2, len(wf_execs)) # Now let's provide a filter. wf_execs = self.mistral_cli( True, 'execution-list', params='--filter description=a' ) self.assertTableStruct( wf_execs, ['ID', 'Workflow name', 'Workflow ID', 'State', 'Created at', 'Updated at'] ) self.assertEqual(1, len(wf_execs)) self.assertEqual(wf_ex1_id, wf_execs[0]['ID']) self.assertEqual('a', wf_execs[0]['Description']) class CronTriggerCLITests(base_v2.MistralClientTestBase): """Test suite checks commands to work with cron-triggers.""" @classmethod def setUpClass(cls): super(CronTriggerCLITests, cls).setUpClass() def setUp(self): super(CronTriggerCLITests, self).setUp() wf = self.workflow_create(self.wf_def) self.wf_name = wf[0]['Name'] def test_cron_trigger_create_delete(self): trigger = self.mistral_admin( 'cron-trigger-create', params=('trigger %s {} --pattern "5 * * * *" --count 5' ' --first-time "4242-12-25 13:37"' % self.wf_name) ) self.assertTableStruct(trigger, ['Field', 'Value']) tr_name = self.get_field_value(trigger, 'Name') wf_name = self.get_field_value(trigger, 'Workflow') created_at = self.get_field_value(trigger, 'Created at') remain = self.get_field_value(trigger, 'Remaining executions') next_time = self.get_field_value(trigger, 'Next execution time') self.assertEqual('trigger', tr_name) self.assertEqual(self.wf_name, wf_name) self.assertIsNotNone(created_at) self.assertEqual("4242-12-25 13:37:00", next_time) self.assertEqual("5", remain) triggers = self.mistral_admin('cron-trigger-list') self.assertIn(tr_name, [tr['Name'] for tr in triggers]) self.assertIn(wf_name, [tr['Workflow'] for tr in triggers]) self.mistral('cron-trigger-delete', params=tr_name) triggers = self.mistral_admin('cron-trigger-list') self.assertNotIn(tr_name, [tr['Name'] for tr in triggers]) def test_two_cron_triggers_for_one_wf(self): self.cron_trigger_create('trigger1', self.wf_name, '{}', "5 * * * *") self.cron_trigger_create('trigger2', self.wf_name, '{}', "15 * * * *") triggers = self.mistral_admin('cron-trigger-list') self.assertIn("trigger1", [tr['Name'] for tr in triggers]) self.assertIn("trigger2", [tr['Name'] for tr in triggers]) def test_cron_trigger_get(self): trigger = self.cron_trigger_create( 'trigger', self.wf_name, '{}', "5 * * * *" ) self.assertTableStruct(trigger, ['Field', 'Value']) fetched_tr = self.mistral_admin( 'cron-trigger-get', params='trigger' ) self.assertTableStruct(trigger, ['Field', 'Value']) tr_name = self.get_field_value(fetched_tr, 'Name') wf_name = self.get_field_value(fetched_tr, 'Workflow') created_at = self.get_field_value(fetched_tr, 'Created at') self.assertEqual('trigger', tr_name) self.assertEqual(self.wf_name, wf_name) self.assertIsNotNone(created_at) class EventTriggerCLITests(base_v2.MistralClientTestBase): """Test suite checks commands to work with event-triggers.""" @classmethod def setUpClass(cls): super(EventTriggerCLITests, cls).setUpClass() def setUp(self): super(EventTriggerCLITests, self).setUp() wf = self.workflow_create(self.wf_def) self.wf_id = wf[0]['ID'] def test_event_trigger_create_delete(self): trigger = self.mistral_admin( 'event-trigger-create', params=('trigger %s dummy_exchange dummy_topic event.dummy {}' % self.wf_id)) self.assertTableStruct(trigger, ['Field', 'Value']) tr_id = self.get_field_value(trigger, 'ID') tr_name = self.get_field_value(trigger, 'Name') wf_id = self.get_field_value(trigger, 'Workflow ID') created_at = self.get_field_value(trigger, 'Created at') self.assertEqual('trigger', tr_name) self.assertEqual(self.wf_id, wf_id) self.assertIsNotNone(created_at) triggers = self.mistral_admin('event-trigger-list') self.assertIn(tr_name, [tr['Name'] for tr in triggers]) self.assertIn(wf_id, [tr['Workflow ID'] for tr in triggers]) self.mistral('event-trigger-delete', params=tr_id) triggers = self.mistral_admin('event-trigger-list') self.assertNotIn(tr_name, [tr['Name'] for tr in triggers]) def test_two_event_triggers_for_one_wf(self): self.event_trigger_create('trigger1', self.wf_id, 'dummy_exchange', 'dummy_topic', 'event.dummy', '{}') self.event_trigger_create('trigger2', self.wf_id, 'dummy_exchange', 'dummy_topic', 'dummy.event', '{}') triggers = self.mistral_admin('event-trigger-list') self.assertIn('trigger1', [tr['Name'] for tr in triggers]) self.assertIn('trigger2', [tr['Name'] for tr in triggers]) def test_event_trigger_get(self): trigger = self.event_trigger_create('trigger', self.wf_id, 'dummy_exchange', 'dummy_topic', 'event.dummy.other', '{}') self.assertTableStruct(trigger, ['Field', 'Value']) ev_tr_id = self.get_field_value(trigger, 'ID') fetched_tr = self.mistral_admin('event-trigger-get', params=ev_tr_id) self.assertTableStruct(trigger, ['Field', 'Value']) tr_name = self.get_field_value(fetched_tr, 'Name') wf_id = self.get_field_value(fetched_tr, 'Workflow ID') created_at = self.get_field_value(fetched_tr, 'Created at') self.assertEqual('trigger', tr_name) self.assertEqual(self.wf_id, wf_id) self.assertIsNotNone(created_at) class TaskCLITests(base_v2.MistralClientTestBase): """Test suite checks commands to work with tasks.""" def setUp(self): super(TaskCLITests, self).setUp() wfs = self.workflow_create(self.wf_def) self.direct_wf = wfs[0] self.reverse_wf = wfs[1] self.create_file('input', '{\n "farewell": "Bye"\n}\n') self.create_file('task_name', '{\n "task_name": "goodbye"\n}\n') def test_task_get(self): wf_ex = self.execution_create(self.direct_wf['Name']) wf_ex_id = self.get_field_value(wf_ex, 'ID') tasks = self.mistral_admin('task-list', params=wf_ex_id) created_task_id = tasks[-1]['ID'] fetched_task = self.mistral_admin('task-get', params=created_task_id) fetched_task_id = self.get_field_value(fetched_task, 'ID') fetched_task_wf_namespace = self.get_field_value( fetched_task, 'Workflow namespace' ) task_execution_id = self.get_field_value(fetched_task, 'Workflow Execution ID') self.assertEqual(created_task_id, fetched_task_id) self.assertEqual('', fetched_task_wf_namespace) self.assertEqual(wf_ex_id, task_execution_id) def test_task_get_list_within_namespace(self): namespace = 'aaa' self.workflow_create(self.wf_def, namespace=namespace) wf_ex = self.execution_create( self.direct_wf['Name'] + ' --namespace ' + namespace ) wf_ex_id = self.get_field_value(wf_ex, 'ID') tasks = self.mistral_admin('task-list', params=wf_ex_id) created_task_id = tasks[-1]['ID'] created_wf_namespace = tasks[-1]['Workflow namespace'] fetched_task = self.mistral_admin('task-get', params=created_task_id) fetched_task_id = self.get_field_value(fetched_task, 'ID') fetched_task_wf_namespace = self.get_field_value( fetched_task, 'Workflow namespace' ) task_execution_id = self.get_field_value(fetched_task, 'Workflow Execution ID') self.assertEqual(created_task_id, fetched_task_id) self.assertEqual(namespace, created_wf_namespace) self.assertEqual(created_wf_namespace, fetched_task_wf_namespace) self.assertEqual(wf_ex_id, task_execution_id) def test_task_list_with_filter(self): wf_exec = self.execution_create( "%s input task_name" % self.reverse_wf['Name'] ) exec_id = self.get_field_value(wf_exec, 'ID') self.assertTrue(self.wait_execution_success(exec_id)) # Request task executions without filters. tasks = self.parser.listing(self.mistral('task-list')) self.assertTableStruct( tasks, ['ID', 'Name', 'Workflow name', 'Workflow Execution ID', 'State'] ) self.assertEqual(2, len(tasks)) # Now let's provide a filter. tasks = self.parser.listing( self.mistral( 'task-list', params='--filter name=goodbye' ) ) self.assertTableStruct( tasks, ['ID', 'Name', 'Workflow name', 'Workflow Execution ID', 'State'] ) self.assertEqual(1, len(tasks)) self.assertEqual('goodbye', tasks[0]['Name']) def test_task_list_with_limit(self): wf_exec = self.execution_create( "%s input task_name" % self.reverse_wf['Name'] ) exec_id = self.get_field_value(wf_exec, 'ID') self.assertTrue(self.wait_execution_success(exec_id)) tasks = self.parser.listing(self.mistral('task-list')) tasks = self.parser.listing( self.mistral( 'task-list', params='--limit 1' ) ) self.assertEqual(1, len(tasks)) class ActionCLITests(base_v2.MistralClientTestBase): """Test suite checks commands to work with actions.""" @classmethod def setUpClass(cls): super(ActionCLITests, cls).setUpClass() def test_action_create_delete(self): init_acts = self.mistral_admin('action-create', params=self.act_def) self.assertTableStruct( init_acts, [ 'Name', 'Is system', 'Input', 'Description', 'Tags', 'Created at', 'Updated at' ] ) self.assertIn('greeting', [action['Name'] for action in init_acts]) self.assertIn('farewell', [action['Name'] for action in init_acts]) action_1 = self.get_item_info( get_from=init_acts, get_by='Name', value='greeting' ) action_2 = self.get_item_info( get_from=init_acts, get_by='Name', value='farewell' ) self.assertEqual('', action_1['Tags']) self.assertEqual('', action_2['Tags']) self.assertEqual('False', action_1['Is system']) self.assertEqual('False', action_2['Is system']) self.assertEqual('name', action_1['Input']) self.assertEqual('None', action_2['Input']) acts = self.mistral_admin('action-list') self.assertIn(action_1['Name'], [action['Name'] for action in acts]) self.assertIn(action_2['Name'], [action['Name'] for action in acts]) self.mistral_admin( 'action-delete', params='{0}'.format(action_1['Name']) ) self.mistral_admin( 'action-delete', params='{0}'.format(action_2['Name']) ) acts = self.mistral_admin('action-list') self.assertNotIn(action_1['Name'], [action['Name'] for action in acts]) self.assertNotIn(action_2['Name'], [action['Name'] for action in acts]) def test_action_update(self): actions = self.action_create(self.act_def) created_action = self.get_item_info( get_from=actions, get_by='Name', value='greeting' ) actions = self.mistral_admin('action-update', params=self.act_def) updated_action = self.get_item_info( get_from=actions, get_by='Name', value='greeting' ) self.assertEqual( created_action['Created at'].split(".")[0], updated_action['Created at'] ) self.assertEqual(created_action['Name'], updated_action['Name']) self.assertEqual( created_action['Updated at'], updated_action['Updated at'] ) actions = self.mistral_admin('action-update', params=self.act_tag_def) updated_action = self.get_item_info( get_from=actions, get_by='Name', value='greeting' ) self.assertEqual('tag, tag1', updated_action['Tags']) self.assertEqual( created_action['Created at'].split(".")[0], updated_action['Created at'] ) self.assertEqual(created_action['Name'], updated_action['Name']) self.assertNotEqual( created_action['Updated at'], updated_action['Updated at'] ) def test_action_update_with_id(self): acts = self.action_create(self.act_def) created_action = self.get_item_info( get_from=acts, get_by='Name', value='greeting' ) action_id = created_action['ID'] params = '{0} --id {1}'.format(self.act_tag_def, action_id) acts = self.mistral_admin('action-update', params=params) updated_action = self.get_item_info( get_from=acts, get_by='ID', value=action_id ) self.assertEqual( created_action['Created at'].split(".")[0], updated_action['Created at'] ) self.assertEqual(created_action['Name'], updated_action['Name']) self.assertNotEqual( created_action['Updated at'], updated_action['Updated at'] ) def test_action_update_truncate_input(self): input_value = "very_long_input_parameter_name_that_should_be_truncated" act_def = """ version: "2.0" action1: input: - {0} base: std.noop """.format(input_value) self.create_file('action.yaml', act_def) self.action_create('action.yaml') updated_act = self.mistral_admin('action-update', params='action.yaml') updated_act_info = self.get_item_info( get_from=updated_act, get_by='Name', value='action1' ) self.assertEqual(updated_act_info['Input'][:-3], input_value[:25]) def test_action_get_definition(self): self.action_create(self.act_def) definition = self.mistral_admin( 'action-get-definition', params='greeting' ) self.assertNotIn('404 Not Found', definition) def test_action_get_with_id(self): created = self.action_create(self.act_def) action_name = created[0]['Name'] action_id = created[0]['ID'] fetched = self.mistral_admin('action-get', params=action_id) fetched_action_name = self.get_field_value(fetched, 'Name') self.assertEqual(action_name, fetched_action_name) def test_action_list_with_filter(self): actions = self.parser.listing(self.mistral('action-list')) self.assertTableStruct( actions, ['Name', 'Is system', 'Input', 'Description', 'Tags', 'Created at', 'Updated at'] ) # NOTE(rakhmerov): This length isn't really a number of actions. # The problem is that one entity in a table may be on more than # one lines depending on their data. For example, for the # workflows that we use in our tests it works fine and parsing # algorithm is able to parse entities correctly even if they are # on multiple lines, but for actions it doesn't. So the only thing # we can do is only check if unfiltered table is bigger than # filtered. # We need to think how to improve it. unfiltered_len = len(actions) self.assertGreater(unfiltered_len, 0) # Now let's provide a filter to the list command. actions = self.parser.listing( self.mistral( 'action-list', params='--filter name=in:std.echo,std.noop' ) ) self.assertTableStruct( actions, ['Name', 'Is system', 'Input', 'Description', 'Tags', 'Created at', 'Updated at'] ) self.assertGreater(unfiltered_len, len(actions)) action_names = [a['Name'] for a in actions] self.assertIn('std.echo', action_names) self.assertIn('std.noop', action_names) self.assertNotIn('std.ssh', action_names) class EnvironmentCLITests(base_v2.MistralClientTestBase): """Test suite checks commands to work with environments.""" def setUp(self): super(EnvironmentCLITests, self).setUp() self.create_file( 'env.yaml', 'name: env\n' 'description: Test env\n' 'variables:\n' ' var: "value"' ) def test_environment_create(self): env = self.mistral_admin('environment-create', params='env.yaml') env_name = self.get_field_value(env, 'Name') env_desc = self.get_field_value(env, 'Description') self.assertTableStruct(env, ['Field', 'Value']) envs = self.mistral_admin('environment-list') self.assertIn(env_name, [en['Name'] for en in envs]) self.assertIn(env_desc, [en['Description'] for en in envs]) self.mistral_admin('environment-delete', params=env_name) envs = self.mistral_admin('environment-list') self.assertNotIn(env_name, [en['Name'] for en in envs]) def test_environment_create_without_description(self): self.create_file( 'env_without_des.yaml', 'name: env\n' 'variables:\n' ' var: "value"' ) env = self.mistral_admin( 'environment-create', params='env_without_des.yaml' ) env_name = self.get_field_value(env, 'Name') env_desc = self.get_field_value(env, 'Description') self.assertTableStruct(env, ['Field', 'Value']) envs = self.mistral_admin('environment-list') self.assertIn(env_name, [en['Name'] for en in envs]) self.assertIn(env_desc, 'None') self.mistral_admin('environment-delete', params='env') envs = self.mistral_admin('environment-list') self.assertNotIn(env_name, [en['Name'] for en in envs]) def test_environment_update(self): env = self.environment_create('env.yaml') env_name = self.get_field_value(env, 'Name') env_desc = self.get_field_value(env, 'Description') env_created_at = self.get_field_value(env, 'Created at') env_updated_at = self.get_field_value(env, 'Updated at') self.assertIsNotNone(env_created_at) self.assertEqual('None', env_updated_at) self.create_file( 'env_upd.yaml', 'name: env\n' 'description: Updated env\n' 'variables:\n' ' var: "value"' ) env = self.mistral_admin('environment-update', params='env_upd.yaml') self.assertTableStruct(env, ['Field', 'Value']) updated_env_name = self.get_field_value(env, 'Name') updated_env_desc = self.get_field_value(env, 'Description') updated_env_created_at = self.get_field_value(env, 'Created at') updated_env_updated_at = self.get_field_value(env, 'Updated at') self.assertEqual(env_name, updated_env_name) self.assertNotEqual(env_desc, updated_env_desc) self.assertEqual('Updated env', updated_env_desc) self.assertEqual(env_created_at.split('.')[0], updated_env_created_at) self.assertIsNotNone(updated_env_updated_at) def test_environment_get(self): env = self.environment_create('env.yaml') env_name = self.get_field_value(env, 'Name') env_desc = self.get_field_value(env, 'Description') env = self.mistral_admin('environment-get', params=env_name) fetched_env_name = self.get_field_value(env, 'Name') fetched_env_desc = self.get_field_value(env, 'Description') self.assertTableStruct(env, ['Field', 'Value']) self.assertEqual(env_name, fetched_env_name) self.assertEqual(env_desc, fetched_env_desc) def test_environment_get_export(self): env = self.environment_create('env.yaml') env_name = self.get_field_value(env, 'Name') env_desc = self.get_field_value(env, 'Description') env = self.mistral_admin('environment-get', params='--export {0}'.format(env_name)) fetched_env_name = self.get_field_value(env, 'name') fetched_env_desc = self.get_field_value(env, 'description') self.assertTableStruct(env, ['Field', 'Value']) self.assertEqual(env_name, fetched_env_name) self.assertEqual(env_desc, fetched_env_desc) class ActionExecutionCLITests(base_v2.MistralClientTestBase): """Test suite checks commands to work with action executions.""" def setUp(self): super(ActionExecutionCLITests, self).setUp() wfs = self.workflow_create(self.wf_def) self.direct_wf = wfs[0] direct_wf_exec = self.execution_create(self.direct_wf['Name']) self.direct_ex_id = self.get_field_value(direct_wf_exec, 'ID') def test_act_execution_get(self): self.wait_execution_success(self.direct_ex_id) task = self.mistral_admin('task-list', params=self.direct_ex_id)[0] act_ex_from_list = self.mistral_admin( 'action-execution-list', params=task['ID'] )[0] act_ex = self.mistral_admin( 'action-execution-get', params=act_ex_from_list['ID'] ) wf_name = self.get_field_value(act_ex, 'Workflow name') state = self.get_field_value(act_ex, 'State') self.assertEqual( act_ex_from_list['ID'], self.get_field_value(act_ex, 'ID') ) self.assertEqual(self.direct_wf['Name'], wf_name) self.assertEqual('SUCCESS', state) def test_act_execution_list_with_limit(self): self.wait_execution_success(self.direct_ex_id) act_execs = self.mistral_admin('action-execution-list') # The workflow execution started in setUp() # generates 2 action executions. self.assertGreater(len(act_execs), 1) act_execs = self.mistral_admin( 'action-execution-list', params="--limit 1" ) self.assertEqual(len(act_execs), 1) act_ex = act_execs[0] self.assertEqual(self.direct_wf['Name'], act_ex['Workflow name']) self.assertEqual('SUCCESS', act_ex['State']) def test_act_execution_get_list_within_namespace(self): namespace = 'bbb' self.workflow_create(self.wf_def, namespace=namespace) wf_ex = self.execution_create( self.direct_wf['Name'] + ' --namespace ' + namespace ) exec_id = self.get_field_value(wf_ex, 'ID') self.wait_execution_success(exec_id) task = self.mistral_admin('task-list', params=exec_id)[0] act_ex_from_list = self.mistral_admin( 'action-execution-list', params=task['ID'] )[0] act_ex = self.mistral_admin( 'action-execution-get', params=act_ex_from_list['ID'] ) wf_name = self.get_field_value(act_ex, 'Workflow name') wf_namespace = self.get_field_value(act_ex, 'Workflow namespace') status = self.get_field_value(act_ex, 'State') self.assertEqual( act_ex_from_list['ID'], self.get_field_value(act_ex, 'ID') ) self.assertEqual(self.direct_wf['Name'], wf_name) self.assertEqual('SUCCESS', status) self.assertEqual(namespace, wf_namespace) self.assertEqual(namespace, act_ex_from_list['Workflow namespace']) def test_act_execution_create_delete(self): action_ex = self.mistral_admin( 'run-action', params="std.echo '{0}' --save-result".format( '{"output": "Hello!"}') ) action_ex_id = self.get_field_value(action_ex, 'ID') self.assertTableStruct(action_ex, ['Field', 'Value']) name = self.get_field_value(action_ex, 'Name') wf_name = self.get_field_value(action_ex, 'Workflow name') task_name = self.get_field_value(action_ex, 'Task name') self.assertEqual('std.echo', name) self.assertEqual('None', wf_name) self.assertEqual('None', task_name) action_exs = self.mistral_admin('action-execution-list') self.assertIn(action_ex_id, [ex['ID'] for ex in action_exs]) self.mistral_admin('action-execution-delete', params=action_ex_id) action_exs = self.mistral_admin('action-execution-list') self.assertNotIn(action_ex_id, [ex['ID'] for ex in action_exs]) class NegativeCLITests(base_v2.MistralClientTestBase): """This class contains negative tests.""" def test_wb_list_extra_param(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workbook-list', params='param' ) def test_wb_get_unexist_wb(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workbook-get', params='wb' ) def test_wb_get_without_param(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workbook-get' ) def test_wb_create_same_name(self): self.workbook_create(self.wb_def) self.assertRaises( exceptions.CommandFailed, self.workbook_create, self.wb_def ) def test_wb_create_with_wrong_path_to_definition(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workbook_create', 'wb' ) def test_wb_delete_unexist_wb(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workbook-delete', params='wb' ) def test_wb_update_wrong_path_to_def(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workbook-update', params='wb' ) def test_wb_update_nonexistant_wb(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workbook-update', params=self.wb_with_tags_def ) def test_wb_create_empty_def(self): self.create_file('empty') self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workbook-create', params='empty' ) def test_wb_update_empty_def(self): self.create_file('empty') self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workbook-update', params='empty' ) def test_wb_get_definition_unexist_wb(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workbook-get-definition', params='wb' ) def test_wb_create_invalid_def(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workbook-create', params=self.wf_def ) def test_wb_update_invalid_def(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workbook-update', params=self.wf_def ) def test_wb_update_without_def(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workbook-update' ) def test_wf_list_extra_param(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-list', params='param' ) def test_wf_get_unexist_wf(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-get', params='wf' ) def test_wf_get_without_param(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-get' ) def test_wf_create_without_definition(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-create', params='' ) def test_wf_create_with_wrong_path_to_definition(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-create', params='wf' ) def test_wf_delete_unexist_wf(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-delete', params='wf' ) def test_wf_update_unexist_wf(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-update', params='wf' ) def test_wf_get_definition_unexist_wf(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-get-definition', params='wf' ) def test_wf_get_definition_missed_param(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-get-definition' ) def test_wf_create_invalid_def(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-create', params=self.wb_def ) def test_wf_update_invalid_def(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-update', params=self.wb_def ) def test_wf_create_empty_def(self): self.create_file('empty') self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-create', params='empty' ) def test_wf_update_empty_def(self): self.create_file('empty') self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-update', params='empty' ) def test_ex_list_extra_param(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'execution-list', params='param' ) def test_ex_create_unexist_wf(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'execution-create', params='wf' ) def test_ex_create_unexist_task(self): wf = self.workflow_create(self.wf_def) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'execution-create', params='%s param {}' % wf[0]['Name'] ) def test_ex_create_with_invalid_input(self): wf = self.workflow_create(self.wf_def) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'execution-create', params="%s input" % wf[0]['Name'] ) def test_ex_get_nonexist_execution(self): wf = self.workflow_create(self.wf_def) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'execution-get', params='%s id' % wf[0]['Name'] ) def test_ex_create_without_wf_name(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'execution-create' ) def test_ex_create_reverse_wf_without_start_task(self): wf = self.workflow_create(self.wf_def) self.create_file('input', '{\n "farewell": "Bye"\n}\n') self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'execution-create ', params=wf[1]['Name'] ) def test_ex_create_missed_input(self): self.create_file('empty') wf = self.workflow_create(self.wf_def) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'execution-create empty', params=wf[1]['Name'] ) def test_ex_update_both_state_and_description(self): wf = self.workflow_create(self.wf_def) execution = self.execution_create(params=wf[0]['Name']) exec_id = self.get_field_value(execution, 'ID') self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'execution-update', params='%s -s ERROR -d update' % exec_id ) def test_ex_delete_nonexistent_execution(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'execution-delete', params='1a2b3c' ) def test_tr_create_without_pattern(self): wf = self.workflow_create(self.wf_def) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'cron-trigger-create', params='tr %s {}' % wf[0]['Name'] ) def test_tr_create_invalid_pattern(self): wf = self.workflow_create(self.wf_def) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'cron-trigger-create', params='tr %s {} --pattern "q"' % wf[0]['Name'] ) def test_tr_create_invalid_pattern_value_out_of_range(self): wf = self.workflow_create(self.wf_def) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'cron-trigger-create', params='tr %s {} --pattern "80 * * * *"' % wf[0]['Name'] ) def test_tr_create_nonexistent_wf(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'cron-trigger-create', params='tr wb.wf1 {} --pattern "* * * * *"' ) def test_tr_delete_nonexistant_tr(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'cron-trigger-delete', params='tr' ) def test_tr_get_nonexistant_tr(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'cron-trigger-get', params='tr' ) def test_tr_create_invalid_count(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'cron-trigger-create', params='tr wb.wf1 {} --pattern "* * * * *" --count q' ) def test_tr_create_negative_count(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'cron-trigger-create', params='tr wb.wf1 {} --pattern "* * * * *" --count -1') def test_tr_create_invalid_first_date(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'cron-trigger-create', params='tr wb.wf1 {} --pattern "* * * * *" --first-date "q"' ) def test_tr_create_count_only(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'cron-trigger-create', params='tr wb.wf1 {} --count 42' ) def test_tr_create_date_and_count_without_pattern(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'cron-trigger-create', params='tr wb.wf1 {} --count 42 --first-time "4242-12-25 13:37"' ) def test_event_tr_create_missing_argument(self): wf = self.workflow_create(self.wf_def) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'event-trigger-create', params='tr %s exchange topic' % wf[0]['ID'] ) def test_event_tr_create_nonexistent_wf(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'event-trigger-create', params='456 4307362e-4a4a-4021-aa58-0fab23c9c751 ' 'exchange topic event {} ' ) def test_event_tr_delete_nonexistent_tr(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'event-trigger-delete', params='789' ) def test_event_tr_get_nonexistent_tr(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'event-trigger-get', params='789' ) def test_action_get_nonexistent(self): self.assertRaises( exceptions.CommandFailed, self.mistral, 'action-get', params='nonexist' ) def test_action_double_creation(self): self.action_create(self.act_def) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'action-create', params='{0}'.format(self.act_def) ) def test_action_create_without_def(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'action-create', params='' ) def test_action_create_invalid_def(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'action-create', params='{0}'.format(self.wb_def) ) def test_action_delete_nonexistent_act(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'action-delete', params='nonexist' ) def test_action_delete_standard_action(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'action-delete', params='heat.events_get' ) def test_action_get_definition_nonexistent_action(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'action-get-definition', params='nonexist' ) def test_task_get_nonexistent_task(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'task-get', params='nonexist' ) def test_env_get_without_param(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'environment-get' ) def test_env_get_nonexistent(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'environment-get', params='nonexist' ) def test_env_create_same_name(self): self.create_file( 'env.yaml', 'name: env\n' 'description: Test env\n' 'variables:\n' ' var: "value"' ) self.environment_create('env.yaml') self.assertRaises( exceptions.CommandFailed, self.environment_create, 'env.yaml' ) def test_env_create_empty(self): self.create_file('env.yaml') self.assertRaises( exceptions.CommandFailed, self.environment_create, 'env.yaml' ) def test_env_create_with_wrong_path_to_definition(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'execution_create', 'env' ) def test_env_delete_unexist_env(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'environment-delete', params='env' ) def test_env_update_wrong_path_to_def(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'environment-update', params='env' ) def test_env_update_empty(self): self.create_file('env.yaml') self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'environment-update', params='env' ) def test_env_update_nonexistant_env(self): self.create_file( 'env.yaml', 'name: env' 'variables:\n var: "value"' ) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'environment-update', params='env.yaml' ) def test_env_create_without_name(self): self.create_file('env.yaml', 'variables:\n var: "value"') self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'environment-create', params='env.yaml' ) def test_env_create_without_variables(self): self.create_file('env.yaml', 'name: env') self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'environment-create', params='env.yaml' ) def test_action_execution_get_without_params(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'action-execution-get' ) def test_action_execution_get_unexistent_obj(self): self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'action-execution-get', params='123456' ) def test_action_execution_update(self): wfs = self.workflow_create(self.wf_def) direct_wf_exec = self.execution_create(wfs[0]['Name']) direct_ex_id = self.get_field_value(direct_wf_exec, 'ID') self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'action-execution-update', params='%s ERROR' % direct_ex_id ) def test_target_action_execution(self): command = ( '--debug ' '--os-target-tenant-name={tenantname} ' '--os-target-username={username} ' '--os-target-password="{password}" ' '--os-target-auth-url="{auth_url}" ' '--target_insecure ' 'run-action heat.stacks_list' ).format( tenantname=self.clients.tenant_name, username=self.clients.username, password=self.clients.password, auth_url=self.clients.uri ) self.mistral_alt_user(cmd=command) python-mistralclient-3.7.0/mistralclient/tests/functional/cli/v2/base_v2.py0000666000175000017500000002130513326122347027111 0ustar zuulzuul00000000000000# Copyright (c) 2014 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os import time from tempest.lib import exceptions from mistralclient.tests.functional.cli import base MISTRAL_URL = "http://localhost:8989/v2" class MistralClientTestBase(base.MistralCLIAuth, base.MistralCLIAltAuth): _mistral_url = MISTRAL_URL @classmethod def setUpClass(cls): super(MistralClientTestBase, cls).setUpClass() cls.wb_def = os.path.relpath( 'functionaltests/resources/v2/wb_v2.yaml', os.getcwd() ) cls.wb_with_tags_def = os.path.relpath( 'functionaltests/resources/v2/wb_with_tags_v2.yaml', os.getcwd() ) cls.wf_def = os.path.relpath( 'functionaltests/resources/v2/wf_v2.yaml', os.getcwd() ) cls.wf_single_def = os.path.relpath( 'functionaltests/resources/v2/wf_single_v2.yaml', os.getcwd() ) cls.wf_with_delay_def = os.path.relpath( 'functionaltests/resources/v2/wf_delay_v2.yaml', os.getcwd() ) cls.wf_wrapping_wf = os.path.relpath( 'functionaltests/resources/v2/wf_wrapping_wf_v2.yaml', os.getcwd() ) cls.top_level_wf = os.path.relpath( 'functionaltests/resources/v2/for_namespaces/top_level_wf.yaml', os.getcwd() ) cls.middle_wf = os.path.relpath( 'functionaltests/resources/v2/for_namespaces/middle_wf.yaml', os.getcwd() ) cls.lowest_level_wf = os.path.relpath( 'functionaltests/resources/v2/for_namespaces/lowest_level_wf.yaml', os.getcwd() ) cls.async_wf_def = os.path.relpath( 'functionaltests/resources/v2/async.yaml', os.getcwd() ) cls.act_def = os.path.relpath( 'functionaltests/resources/v2/action_v2.yaml', os.getcwd() ) cls.act_tag_def = os.path.relpath( 'functionaltests/resources/v2/action_v2_tags.yaml', os.getcwd() ) def setUp(self): super(MistralClientTestBase, self).setUp() def get_field_value(self, obj, field): return [ o['Value'] for o in obj if o['Field'] == "{0}".format(field) ][0] def get_item_info(self, get_from, get_by, value): return [i for i in get_from if i[get_by] == value][0] def mistral_admin(self, cmd, params=""): self.clients = self._get_admin_clients() return self.parser.listing( self.mistral('{0}'.format(cmd), params='{0}'.format(params)) ) def mistral_alt_user(self, cmd, params=""): self.clients = self._get_alt_clients() return self.parser.listing( self.mistral_alt('{0}'.format(cmd), params='{0}'.format(params)) ) def mistral_cli(self, admin, cmd, params=''): if admin: return self.mistral_admin(cmd, params) else: return self.mistral_alt_user(cmd, params) def workbook_create(self, wb_def, admin=True, scope='private'): params = '{0}'.format(wb_def) if scope == 'public': params += ' --public' wb = self.mistral_cli( admin, 'workbook-create', params=params ) wb_name = self.get_field_value(wb, "Name") self.addCleanup( self.mistral_cli, admin, 'workbook-delete', params=wb_name ) self.addCleanup( self.mistral_cli, admin, 'workflow-delete', params='wb.wf1' ) return wb def workflow_create(self, wf_def, namespace='', admin=True, scope='private'): params = '{0}'.format(wf_def) if scope == 'public': params += ' --public' if namespace: params += " --namespace " + namespace wf = self.mistral_cli( admin, 'workflow-create', params=params ) for workflow in wf: self.addCleanup( self.mistral_cli, admin, 'workflow-delete', params=workflow['ID'] ) return wf def workflow_member_create(self, wf_id): cmd_param = ( '%s workflow %s' % (wf_id, self.get_project_id("demo")) ) member = self.mistral_admin("member-create", params=cmd_param) self.addCleanup( self.mistral_admin, 'member-delete', params=cmd_param ) return member def action_create(self, act_def, admin=True, scope='private'): params = '{0}'.format(act_def) if scope == 'public': params += ' --public' acts = self.mistral_cli( admin, 'action-create', params=params ) for action in acts: self.addCleanup( self.mistral_cli, admin, 'action-delete', params=action['Name'] ) return acts def cron_trigger_create(self, name, wf_name, wf_input, pattern=None, count=None, first_time=None, admin=True): optional_params = "" if pattern: optional_params += ' --pattern "{}"'.format(pattern) if count: optional_params += ' --count {}'.format(count) if first_time: optional_params += ' --first-time "{}"'.format(first_time) trigger = self.mistral_cli( admin, 'cron-trigger-create', params='{} {} {} {}'.format(name, wf_name, wf_input, optional_params)) self.addCleanup(self.mistral_cli, admin, 'cron-trigger-delete', params=name) return trigger def event_trigger_create(self, name, wf_id, exchange, topic, event, wf_input, admin=True): trigger = self.mistral_cli( admin, 'event-trigger-create', params=' '.join((name, wf_id, exchange, topic, event, wf_input))) ev_tr_id = self.get_field_value(trigger, 'ID') self.addCleanup(self.mistral_cli, admin, 'event-trigger-delete', params=ev_tr_id) return trigger def execution_create(self, params, admin=True): ex = self.mistral_cli(admin, 'execution-create', params=params) exec_id = self.get_field_value(ex, 'ID') self.addCleanup( self.mistral_cli, admin, 'execution-delete', params="{} --force".format(exec_id) ) return ex def environment_create(self, params, admin=True): env = self.mistral_cli(admin, 'environment-create', params=params) env_name = self.get_field_value(env, 'Name') self.addCleanup( self.mistral_cli, admin, 'environment-delete', params=env_name ) return env def create_file(self, file_name, file_body=""): f = open(file_name, 'w') f.write(file_body) f.close() self.addCleanup(os.remove, file_name) def wait_execution_success(self, exec_id, timeout=180): start_time = time.time() ex = self.mistral_admin('execution-get', params=exec_id) exec_state = self.get_field_value(ex, 'State') expected_states = ['SUCCESS', 'RUNNING'] while exec_state != 'SUCCESS': if time.time() - start_time > timeout: msg = ("Execution exceeds timeout {0} to change state " "to SUCCESS. Execution: {1}".format(timeout, ex)) raise exceptions.TimeoutException(msg) ex = self.mistral_admin('execution-get', params=exec_id) exec_state = self.get_field_value(ex, 'State') if exec_state not in expected_states: msg = ("Execution state %s is not in expected " "states: %s" % (exec_state, expected_states)) raise exceptions.TempestException(msg) time.sleep(2) return True python-mistralclient-3.7.0/mistralclient/tests/functional/cli/v2/cli_multi_tenancy_tests.py0000666000175000017500000003633613326122347032526 0ustar zuulzuul00000000000000# Copyright (c) 2014 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib import exceptions from mistralclient.tests.functional.cli.v2 import base_v2 class StandardItemsAvailabilityCLITests(base_v2.MistralClientTestBase): def test_std_workflows_availability(self): wfs = self.mistral_admin("workflow-list") self.assertTableStruct( wfs, ["Name", "Tags", "Input", "Created at", "Updated at"] ) self.assertIn("std.create_instance", [workflow["Name"] for workflow in wfs]) wfs = self.mistral_alt_user("workflow-list") self.assertTableStruct( wfs, ["Name", "Tags", "Input", "Created at", "Updated at"] ) self.assertIn("std.create_instance", [workflow["Name"] for workflow in wfs]) def test_std_actions_availability(self): acts = self.mistral_admin("action-list") self.assertTableStruct( acts, ["Name", "Is system", "Input", "Description", "Tags", "Created at", "Updated at"] ) self.assertIn("glance.images_list", [action["Name"] for action in acts]) acts = self.mistral_alt_user("action-list") self.assertTableStruct( acts, ["Name", "Is system", "Input", "Description", "Tags", "Created at", "Updated at"] ) self.assertIn("glance.images_list", [action["Name"] for action in acts]) class WorkbookIsolationCLITests(base_v2.MistralClientTestBase): def test_workbook_name_uniqueness(self): self.workbook_create(self.wb_def) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, "workbook-create", params="{0}".format(self.wb_def) ) self.workbook_create(self.wb_def, admin=False) self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "workbook-create", params="{0}".format(self.wb_def) ) def test_wb_isolation(self): wb = self.workbook_create(self.wb_def) wb_name = self.get_field_value(wb, "Name") wbs = self.mistral_admin("workbook-list") self.assertIn(wb_name, [w["Name"] for w in wbs]) alt_wbs = self.mistral_alt_user("workbook-list") self.assertNotIn(wb_name, [w["Name"] for w in alt_wbs]) def test_get_wb_from_another_tenant(self): wb = self.workbook_create(self.wb_def) name = self.get_field_value(wb, "Name") self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "workbook-get", params=name ) def test_create_public_workbook(self): wb = self.workbook_create(self.wb_def, scope='public') name = self.get_field_value(wb, "Name") same_wb = self.mistral_alt_user( "workbook-get", params=name ) self.assertEqual( name, self.get_field_value(same_wb, "Name") ) # The workflows should be public too self.mistral_alt_user( "workflow-get", params="wb.wf1" ) # The actions should be public too self.mistral_alt_user( "action-get", params="wb.ac1" ) def test_delete_wb_from_another_tenant(self): wb = self.workbook_create(self.wb_def) name = self.get_field_value(wb, "Name") self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "workbook-delete", params=name ) class WorkflowIsolationCLITests(base_v2.MistralClientTestBase): def test_workflow_name_uniqueness(self): self.workflow_create(self.wf_def) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, "workflow-create", params="{0}".format(self.wf_def) ) self.workflow_create(self.wf_def, admin=False) self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "workflow-create", params="{0}".format(self.wf_def) ) def test_wf_isolation(self): wf = self.workflow_create(self.wf_def) wfs = self.mistral_admin("workflow-list") self.assertIn(wf[0]["Name"], [w["Name"] for w in wfs]) alt_wfs = self.mistral_alt_user("workflow-list") self.assertNotIn(wf[0]["Name"], [w["Name"] for w in alt_wfs]) def test_get_wf_from_another_tenant(self): wf = self.workflow_create(self.wf_def) self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "workflow-get", params=wf[0]["ID"] ) def test_create_public_workflow(self): wf = self.workflow_create(self.wf_def, scope='public') same_wf = self.mistral_alt_user( "workflow-get", params=wf[0]["Name"] ) self.assertEqual( wf[0]["Name"], self.get_field_value(same_wf, "Name") ) def test_delete_wf_from_another_tenant(self): wf = self.workflow_create(self.wf_def) self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "workflow-delete", params=wf[0]["ID"] ) class WorkflowSharingCLITests(base_v2.MistralClientTestBase): def setUp(self): super(WorkflowSharingCLITests, self).setUp() self.wf = self.workflow_create(self.wf_def, admin=True) def _update_shared_workflow(self, new_status='accepted'): member = self.workflow_member_create(self.wf[0]["ID"]) status = self.get_field_value(member, 'Status') self.assertEqual('pending', status) cmd_param = '%s workflow --status %s --member-id %s' % ( self.wf[0]["ID"], new_status, self.get_project_id("demo")) member = self.mistral_alt_user("member-update", params=cmd_param) status = self.get_field_value(member, 'Status') self.assertEqual(new_status, status) def test_list_accepted_shared_workflow(self): wfs = self.mistral_alt_user("workflow-list") self.assertNotIn(self.wf[0]["ID"], [w["ID"] for w in wfs]) self._update_shared_workflow(new_status='accepted') alt_wfs = self.mistral_alt_user("workflow-list") self.assertIn(self.wf[0]["ID"], [w["ID"] for w in alt_wfs]) self.assertIn( self.get_project_id("admin"), [w["Project ID"] for w in alt_wfs] ) def test_list_rejected_shared_workflow(self): self._update_shared_workflow(new_status='rejected') alt_wfs = self.mistral_alt_user("workflow-list") self.assertNotIn(self.wf[0]["ID"], [w["ID"] for w in alt_wfs]) def test_create_execution_using_shared_workflow(self): self._update_shared_workflow(new_status='accepted') execution = self.execution_create(self.wf[0]["ID"], admin=False) wf_name = self.get_field_value(execution, 'Workflow name') self.assertEqual(self.wf[0]["Name"], wf_name) def test_create_contrigger_using_shared_workflow(self): self._update_shared_workflow(new_status='accepted') trigger = self.cron_trigger_create( "test_trigger", self.wf[0]["ID"], "{}", "5 * * * *", admin=False ) wf_name = self.get_field_value(trigger, 'Workflow') self.assertEqual(self.wf[0]["Name"], wf_name) # Admin project can not delete the shared workflow, because it is used # in a cron-trigger of another project. self.assertRaises( exceptions.CommandFailed, self.mistral_admin, 'workflow-delete', params=self.wf[0]["ID"] ) class ActionIsolationCLITests(base_v2.MistralClientTestBase): def test_actions_name_uniqueness(self): self.action_create(self.act_def) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, "action-create", params="{0}".format(self.act_def) ) self.action_create(self.act_def, admin=False) self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "action-create", params="{0}".format(self.act_def) ) def test_action_isolation(self): act = self.action_create(self.act_def) acts = self.mistral_admin("action-list") self.assertIn(act[0]["Name"], [a["Name"] for a in acts]) alt_acts = self.mistral_alt_user("action-list") self.assertNotIn(act[0]["Name"], [a["Name"] for a in alt_acts]) def test_get_action_from_another_tenant(self): act = self.action_create(self.act_def) self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "action-get", params=act[0]["Name"] ) def test_delete_action_from_another_tenant(self): act = self.action_create(self.act_def) self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "action-delete", params=act[0]["Name"] ) def test_create_public_action(self): act = self.action_create(self.act_def, scope='public') same_act = self.mistral_alt_user( "action-get", params=act[0]["Name"] ) self.assertEqual( act[0]["Name"], self.get_field_value(same_act, "Name") ) class CronTriggerIsolationCLITests(base_v2.MistralClientTestBase): def test_cron_trigger_name_uniqueness(self): wf = self.workflow_create(self.wf_def) self.cron_trigger_create( "admin_trigger", wf[0]["ID"], "{}", "5 * * * *" ) self.assertRaises( exceptions.CommandFailed, self.cron_trigger_create, "admin_trigger", wf[0]["ID"], "{}" "5 * * * *", ) wf = self.workflow_create(self.wf_def, admin=False) self.cron_trigger_create( "user_trigger", wf[0]["ID"], "{}", "5 * * * *", None, None, admin=False ) self.assertRaises( exceptions.CommandFailed, self.cron_trigger_create, "user_trigger", wf[0]["ID"], "{}", "5 * * * *", None, None, admin=False ) def test_cron_trigger_isolation(self): wf = self.workflow_create(self.wf_def) self.cron_trigger_create( "trigger", wf[0]["Name"], "{}", "5 * * * *") alt_trs = self.mistral_alt_user("cron-trigger-list") self.assertNotIn("trigger", [t["Name"] for t in alt_trs]) class ExecutionIsolationCLITests(base_v2.MistralClientTestBase): def test_execution_isolation(self): wf = self.workflow_create(self.wf_def) ex = self.execution_create(wf[0]["Name"]) exec_id = self.get_field_value(ex, "ID") execs = self.mistral_admin("execution-list") self.assertIn(exec_id, [e["ID"] for e in execs]) alt_execs = self.mistral_alt_user("execution-list") self.assertNotIn(exec_id, [e["ID"] for e in alt_execs]) def test_get_execution_from_another_tenant(self): wf = self.workflow_create(self.wf_def) ex = self.execution_create(wf[0]["Name"]) exec_id = self.get_field_value(ex, "ID") self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "execution-get", params=exec_id ) class EnvironmentIsolationCLITests(base_v2.MistralClientTestBase): def setUp(self): super(EnvironmentIsolationCLITests, self).setUp() self.env_file = "env.yaml" self.create_file("{0}".format(self.env_file), "name: env\n" "description: Test env\n" "variables:\n" " var: value") def test_environment_name_uniqueness(self): self.environment_create(self.env_file) self.assertRaises( exceptions.CommandFailed, self.mistral_admin, "environment-create", params=self.env_file ) self.environment_create(self.env_file, admin=False) self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "environment-create", params=self.env_file ) def test_environment_isolation(self): env = self.environment_create(self.env_file) env_name = self.get_field_value(env, "Name") envs = self.mistral_admin("environment-list") self.assertIn(env_name, [en["Name"] for en in envs]) alt_envs = self.mistral_alt_user("environment-list") self.assertNotIn(env_name, [en["Name"] for en in alt_envs]) def test_get_env_from_another_tenant(self): env = self.environment_create(self.env_file) env_name = self.get_field_value(env, "Name") self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "environment-get", params=env_name ) def test_delete_env_from_another_tenant(self): env = self.environment_create(self.env_file) env_name = self.get_field_value(env, "Name") self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "environment-delete", params=env_name ) class ActionExecutionIsolationCLITests(base_v2.MistralClientTestBase): def test_action_execution_isolation(self): wf = self.workflow_create(self.wf_def) wf_exec = self.execution_create(wf[0]["Name"]) direct_ex_id = self.get_field_value(wf_exec, 'ID') self.wait_execution_success(direct_ex_id) act_execs = self.mistral_admin("action-execution-list") self.assertIn(wf[0]["Name"], [act["Workflow name"] for act in act_execs]) alt_act_execs = self.mistral_alt_user("action-execution-list") self.assertNotIn(wf[0]["Name"], [act["Workflow name"] for act in alt_act_execs]) def test_get_action_execution_from_another_tenant(self): wf = self.workflow_create(self.wf_def) ex = self.execution_create(wf[0]["Name"]) exec_id = self.get_field_value(ex, "ID") self.assertRaises( exceptions.CommandFailed, self.mistral_alt_user, "action-execution-get", params=exec_id ) python-mistralclient-3.7.0/mistralclient/tests/functional/cli/v2/__init__.py0000666000175000017500000000000013326122347027314 0ustar zuulzuul00000000000000python-mistralclient-3.7.0/mistralclient/tests/functional/cli/__init__.py0000666000175000017500000000000013326122347026765 0ustar zuulzuul00000000000000python-mistralclient-3.7.0/mistralclient/tests/functional/__init__.py0000666000175000017500000000000013326122347026216 0ustar zuulzuul00000000000000python-mistralclient-3.7.0/mistralclient/tests/unit/0000775000175000017500000000000013326122642022730 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/mistralclient/tests/unit/test_httpclient.py0000666000175000017500000002213113326122347026522 0ustar zuulzuul00000000000000# Copyright 2016 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import base64 import copy import mock from six.moves.urllib import parse as urlparse from oslo_utils import uuidutils from osprofiler import _utils as osprofiler_utils import osprofiler.profiler from mistralclient.api import httpclient from mistralclient.tests.unit import base API_BASE_URL = 'http://localhost:8989/v2' API_URL = '/executions' EXPECTED_URL = API_BASE_URL + API_URL AUTH_TOKEN = uuidutils.generate_uuid() PROJECT_ID = uuidutils.generate_uuid() USER_ID = uuidutils.generate_uuid() REGION_NAME = 'fake_region' PROFILER_HMAC_KEY = 'SECRET_HMAC_KEY' PROFILER_TRACE_ID = uuidutils.generate_uuid() EXPECTED_AUTH_HEADERS = { 'X-Auth-Token': AUTH_TOKEN, 'X-Project-Id': PROJECT_ID, 'X-User-Id': USER_ID, 'X-Region-Name': REGION_NAME } EXPECTED_REQ_OPTIONS = { 'headers': EXPECTED_AUTH_HEADERS } EXPECTED_BODY = { 'k1': 'abc', 'k2': 123, 'k3': True } class HTTPClientTest(base.BaseClientTest): def setUp(self): super(HTTPClientTest, self).setUp() osprofiler.profiler.init(None) self.client = httpclient.HTTPClient( API_BASE_URL, auth_token=AUTH_TOKEN, project_id=PROJECT_ID, user_id=USER_ID, region_name=REGION_NAME ) def assertExpectedAuthHeaders(self): headers = self.requests_mock.last_request.headers self.assertEqual(AUTH_TOKEN, headers['X-Auth-Token']) self.assertEqual(PROJECT_ID, headers['X-Project-Id']) self.assertEqual(USER_ID, headers['X-User-Id']) return headers def assertExpectedBody(self): text = self.requests_mock.last_request.text form = urlparse.parse_qs(text, strict_parsing=True) self.assertEqual(len(EXPECTED_BODY), len(form)) for k, v in EXPECTED_BODY.items(): self.assertEqual([str(v)], form[k]) return form def test_get_request_options(self): m = self.requests_mock.get(EXPECTED_URL, text='text') self.client.get(API_URL) self.assertTrue(m.called_once) self.assertExpectedAuthHeaders() @mock.patch.object( osprofiler.profiler._Profiler, 'get_base_id', mock.MagicMock(return_value=PROFILER_TRACE_ID) ) @mock.patch.object( osprofiler.profiler._Profiler, 'get_id', mock.MagicMock(return_value=PROFILER_TRACE_ID) ) def test_get_request_options_with_profile_enabled(self): m = self.requests_mock.get(EXPECTED_URL, text='text') osprofiler.profiler.init(PROFILER_HMAC_KEY) data = {'base_id': PROFILER_TRACE_ID, 'parent_id': PROFILER_TRACE_ID} signed_data = osprofiler_utils.signed_pack(data, PROFILER_HMAC_KEY) headers = { 'X-Trace-Info': signed_data[0], 'X-Trace-HMAC': signed_data[1] } self.client.get(API_URL) self.assertTrue(m.called_once) headers = self.assertExpectedAuthHeaders() self.assertEqual(signed_data[0], headers['X-Trace-Info']) self.assertEqual(signed_data[1], headers['X-Trace-HMAC']) def test_get_request_options_with_headers_for_get(self): m = self.requests_mock.get(EXPECTED_URL, text='text') target_auth_url = uuidutils.generate_uuid() target_auth_token = uuidutils.generate_uuid() target_user_id = 'target_user' target_project_id = 'target_project' target_service_catalog = 'this should be there' target_insecure = 'target insecure' target_region = 'target region name' target_user_domain_name = 'target user domain name' target_project_domain_name = 'target project domain name' target_client = httpclient.HTTPClient( API_BASE_URL, auth_token=AUTH_TOKEN, project_id=PROJECT_ID, user_id=USER_ID, region_name=REGION_NAME, target_auth_url=target_auth_url, target_auth_token=target_auth_token, target_project_id=target_project_id, target_user_id=target_user_id, target_service_catalog=target_service_catalog, target_region_name=target_region, target_user_domain_name=target_user_domain_name, target_project_domain_name=target_project_domain_name, target_insecure=target_insecure ) target_client.get(API_URL) self.assertTrue(m.called_once) headers = self.assertExpectedAuthHeaders() self.assertEqual(target_auth_url, headers['X-Target-Auth-Uri']) self.assertEqual(target_auth_token, headers['X-Target-Auth-Token']) self.assertEqual(target_user_id, headers['X-Target-User-Id']) self.assertEqual(target_project_id, headers['X-Target-Project-Id']) self.assertEqual(str(target_insecure), headers['X-Target-Insecure']) self.assertEqual(target_region, headers['X-Target-Region-Name']) self.assertEqual(target_user_domain_name, headers['X-Target-User-Domain-Name']) self.assertEqual(target_project_domain_name, headers['X-Target-Project-Domain-Name']) catalog = base64.b64encode(target_service_catalog.encode('utf-8')) self.assertEqual(catalog, headers['X-Target-Service-Catalog']) def test_get_request_options_with_headers_for_post(self): m = self.requests_mock.post(EXPECTED_URL, text='text') headers = {'foo': 'bar'} self.client.post(API_URL, EXPECTED_BODY, headers=headers) self.assertTrue(m.called_once) headers = self.assertExpectedAuthHeaders() self.assertEqual('application/json', headers['Content-Type']) self.assertEqual('bar', headers['foo']) self.assertExpectedBody() def test_get_request_options_with_headers_for_put(self): m = self.requests_mock.put(EXPECTED_URL, text='text') headers = {'foo': 'bar'} self.client.put(API_URL, EXPECTED_BODY, headers=headers) self.assertTrue(m.called_once) headers = self.assertExpectedAuthHeaders() self.assertEqual('application/json', headers['Content-Type']) self.assertEqual('bar', headers['foo']) self.assertExpectedBody() def test_get_request_options_with_headers_for_delete(self): m = self.requests_mock.delete(EXPECTED_URL, text='text') headers = {'foo': 'bar'} self.client.delete(API_URL, headers=headers) self.assertTrue(m.called_once) headers = self.assertExpectedAuthHeaders() self.assertEqual('bar', headers['foo']) @mock.patch.object( httpclient.HTTPClient, '_get_request_options', mock.MagicMock(return_value=copy.deepcopy(EXPECTED_REQ_OPTIONS)) ) def test_http_get(self): m = self.requests_mock.get(EXPECTED_URL, text='text') self.client.get(API_URL) httpclient.HTTPClient._get_request_options.assert_called_with( 'get', None ) self.assertTrue(m.called_once) self.assertExpectedAuthHeaders() @mock.patch.object( httpclient.HTTPClient, '_get_request_options', mock.MagicMock(return_value=copy.deepcopy(EXPECTED_REQ_OPTIONS)) ) def test_http_post(self): m = self.requests_mock.post(EXPECTED_URL, status_code=201, text='text') self.client.post(API_URL, EXPECTED_BODY) httpclient.HTTPClient._get_request_options.assert_called_with( 'post', None ) self.assertTrue(m.called_once) self.assertExpectedAuthHeaders() self.assertExpectedBody() @mock.patch.object( httpclient.HTTPClient, '_get_request_options', mock.MagicMock(return_value=copy.deepcopy(EXPECTED_REQ_OPTIONS)) ) def test_http_put(self): m = self.requests_mock.put(EXPECTED_URL, json={}) self.client.put(API_URL, EXPECTED_BODY) httpclient.HTTPClient._get_request_options.assert_called_with( 'put', None ) self.assertTrue(m.called_once) self.assertExpectedAuthHeaders() self.assertExpectedBody() @mock.patch.object( httpclient.HTTPClient, '_get_request_options', mock.MagicMock(return_value=copy.deepcopy(EXPECTED_REQ_OPTIONS)) ) def test_http_delete(self): m = self.requests_mock.delete(EXPECTED_URL, text='text') self.client.delete(API_URL) httpclient.HTTPClient._get_request_options.assert_called_with( 'delete', None ) self.assertTrue(m.called_once) self.assertExpectedAuthHeaders() python-mistralclient-3.7.0/mistralclient/tests/unit/test_client.py0000666000175000017500000002575013326122347025634 0ustar zuulzuul00000000000000# Copyright 2015 - Huawei Technologies Co., Ltd. # Copyright 2016 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import json import os import tempfile import mock from oslo_utils import uuidutils from oslotest import base import osprofiler.profiler from mistralclient.api import client AUTH_HTTP_URL_v3 = 'http://localhost:35357/v3' AUTH_HTTP_URL_v2_0 = 'http://localhost:35357/v2.0' AUTH_HTTPS_URL = AUTH_HTTP_URL_v3.replace('http', 'https') MISTRAL_HTTP_URL = 'http://localhost:8989/v2' MISTRAL_HTTPS_URL = MISTRAL_HTTP_URL.replace('http', 'https') PROFILER_HMAC_KEY = 'SECRET_HMAC_KEY' class BaseClientTests(base.BaseTestCase): @staticmethod def setup_keystone_mock(session_mock): keystone_client_instance = session_mock.return_value keystone_client_instance.auth_token = uuidutils.generate_uuid() keystone_client_instance.project_id = uuidutils.generate_uuid() keystone_client_instance.user_id = uuidutils.generate_uuid() keystone_client_instance.auth_ref = str(json.dumps({})) return keystone_client_instance @mock.patch('keystoneauth1.session.Session') def test_mistral_url_from_catalog_v2(self, session_mock): session = mock.Mock() session_mock.side_effect = [session] get_endpoint = mock.Mock(return_value='http://mistral_host:8989/v2') session.get_endpoint = get_endpoint mistralclient = client.client( username='mistral', project_name='mistral', api_key='password', auth_url=AUTH_HTTP_URL_v2_0, service_type='workflowv2' ) self.assertEqual( 'http://mistral_host:8989/v2', mistralclient.actions.http_client.base_url ) @mock.patch('keystoneauth1.session.Session') def test_mistral_url_from_catalog(self, session_mock): session = mock.Mock() session_mock.side_effect = [session] get_endpoint = mock.Mock(return_value='http://mistral_host:8989/v2') session.get_endpoint = get_endpoint mistralclient = client.client( username='mistral', project_name='mistral', api_key='password', user_domain_name='Default', project_domain_name='Default', auth_url=AUTH_HTTP_URL_v3, service_type='workflowv2' ) self.assertEqual( 'http://mistral_host:8989/v2', mistralclient.actions.http_client.base_url ) @mock.patch('keystoneauth1.session.Session') @mock.patch('mistralclient.api.httpclient.HTTPClient') def test_mistral_url_default(self, http_client_mock, session_mock): session = mock.Mock() session_mock.side_effect = [session] get_endpoint = mock.Mock(side_effect=Exception) session.get_endpoint = get_endpoint client.client( username='mistral', project_name='mistral', api_key='password', user_domain_name='Default', project_domain_name='Default', auth_url=AUTH_HTTP_URL_v3 ) self.assertTrue(http_client_mock.called) mistral_url_for_http = http_client_mock.call_args[0][0] self.assertEqual(MISTRAL_HTTP_URL, mistral_url_for_http) @mock.patch('mistralclient.auth.keystone.KeystoneAuthHandler' '._is_service_catalog_v2', return_value=True) @mock.patch('keystoneauth1.identity.generic.Password') @mock.patch('keystoneauth1.session.Session') @mock.patch('mistralclient.api.httpclient.HTTPClient') def test_target_parameters_processed( self, http_client_mock, session_mock, password_mock, catalog_type_mock ): session = mock.MagicMock() target_session = mock.MagicMock() session_mock.side_effect = [session, target_session] auth = mock.MagicMock() target_auth = mock.MagicMock() target_auth._plugin.auth_url = AUTH_HTTP_URL_v3 password_mock.side_effect = [auth, target_auth] get_endpoint = mock.Mock(return_value='http://mistral_host:8989/v2') session.get_endpoint = get_endpoint target_session.get_project_id = mock.Mock(return_value='projectid') target_session.get_user_id = mock.Mock(return_value='userid') target_session.get_auth_headers = mock.Mock(return_value={ 'X-Auth-Token': 'authtoken' }) mock_access = mock.MagicMock() mock_catalog = mock.MagicMock() mock_catalog.catalog = {} mock_access.service_catalog = mock_catalog auth.get_access = mock.Mock(return_value=mock_access) client.client( auth_url=AUTH_HTTP_URL_v3, username='user', api_key='password', user_domain_name='Default', project_domain_name='Default', target_username='tmistral', target_project_name='tmistralp', target_auth_url=AUTH_HTTP_URL_v3, target_api_key='tpassword', target_user_domain_name='Default', target_project_domain_name='Default', target_region_name='tregion' ) self.assertTrue(http_client_mock.called) mistral_url_for_http = http_client_mock.call_args[0][0] kwargs = http_client_mock.call_args[1] self.assertEqual('http://mistral_host:8989/v2', mistral_url_for_http) expected_values = { 'target_project_id': 'projectid', 'target_auth_token': 'authtoken', 'target_user_id': 'userid', 'target_auth_url': AUTH_HTTP_URL_v3, 'target_project_name': 'tmistralp', 'target_username': 'tmistral', 'target_region_name': 'tregion', 'target_service_catalog': "{}" } for key in expected_values: self.assertEqual(expected_values[key], kwargs[key]) @mock.patch('keystoneauth1.session.Session') @mock.patch('mistralclient.api.httpclient.HTTPClient') def test_mistral_url_https_insecure(self, http_client_mock, session_mock): keystone_client_instance = self.setup_keystone_mock( # noqa session_mock ) expected_args = ( MISTRAL_HTTPS_URL, ) client.client( mistral_url=MISTRAL_HTTPS_URL, username='mistral', project_name='mistral', api_key='password', user_domain_name='Default', project_domain_name='Default', auth_url=AUTH_HTTP_URL_v3, cacert=None, insecure=True ) self.assertTrue(http_client_mock.called) self.assertEqual(http_client_mock.call_args[0], expected_args) self.assertEqual(http_client_mock.call_args[1]['insecure'], True) @mock.patch('keystoneauth1.session.Session') @mock.patch('mistralclient.api.httpclient.HTTPClient') def test_mistral_url_https_secure(self, http_client_mock, session_mock): fd, cert_path = tempfile.mkstemp(suffix='.pem') keystone_client_instance = self.setup_keystone_mock( # noqa session_mock ) expected_args = ( MISTRAL_HTTPS_URL, ) try: client.client( mistral_url=MISTRAL_HTTPS_URL, username='mistral', project_name='mistral', api_key='password', user_domain_name='Default', project_domain_name='Default', auth_url=AUTH_HTTP_URL_v3, cacert=cert_path, insecure=False ) finally: os.close(fd) os.unlink(cert_path) self.assertTrue(http_client_mock.called) self.assertEqual(http_client_mock.call_args[0], expected_args) self.assertEqual(http_client_mock.call_args[1]['cacert'], cert_path) @mock.patch('keystoneauth1.session.Session') def test_mistral_url_https_bad_cacert(self, session_mock): keystone_client_instance = self.setup_keystone_mock( # noqa session_mock ) self.assertRaises( ValueError, client.client, mistral_url=MISTRAL_HTTPS_URL, username='mistral', project_name='mistral', api_key='password', user_domain_name='Default', project_domain_name='Default', auth_url=AUTH_HTTP_URL_v3, cacert='/path/to/foobar', insecure=False ) @mock.patch('logging.Logger.warning') @mock.patch('keystoneauth1.session.Session') def test_mistral_url_https_bad_insecure(self, session_mock, log_warning_mock): fd, path = tempfile.mkstemp(suffix='.pem') keystone_client_instance = self.setup_keystone_mock( session_mock ) try: client.client( mistral_url=MISTRAL_HTTPS_URL, user_id=keystone_client_instance.user_id, project_id=keystone_client_instance.project_id, api_key='password', user_domain_name='Default', project_domain_name='Default', auth_url=AUTH_HTTP_URL_v3, cacert=path, insecure=True ) finally: os.close(fd) os.unlink(path) self.assertTrue(log_warning_mock.called) @mock.patch('keystoneauth1.session.Session') @mock.patch('mistralclient.api.httpclient.HTTPClient') def test_mistral_profile_enabled(self, http_client_mock, session_mock): keystone_client_instance = self.setup_keystone_mock( # noqa session_mock ) client.client( username='mistral', project_name='mistral', api_key='password', user_domain_name='Default', project_domain_name='Default', auth_url=AUTH_HTTP_URL_v3, profile=PROFILER_HMAC_KEY ) self.assertTrue(http_client_mock.called) profiler = osprofiler.profiler.get() self.assertEqual(profiler.hmac_key, PROFILER_HMAC_KEY) @mock.patch('mistralclient.auth.get_auth_handler') def test_mistral_no_auth(self, get_auth_handler_mock): # Test that we don't authenticate if auth url wasn't provided client.client( username='mistral', project_name='mistral', api_key='password', service_type='workflowv2' ) self.assertEqual(0, get_auth_handler_mock.call_count) python-mistralclient-3.7.0/mistralclient/tests/unit/base.py0000666000175000017500000000242113326122347024217 0ustar zuulzuul00000000000000# Copyright 2013 - Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import mock from oslotest import base from requests_mock.contrib import fixture class BaseClientTest(base.BaseTestCase): _client = None def setUp(self): super(BaseClientTest, self).setUp() self.requests_mock = self.useFixture(fixture.Fixture()) class BaseCommandTest(base.BaseTestCase): def setUp(self): super(BaseCommandTest, self).setUp() self.app = mock.Mock() self.client = self.app.client_manager.workflow_engine def call(self, command, app_args=[], prog_name=''): cmd = command(self.app, app_args) parsed_args = cmd.get_parser(prog_name).parse_args(app_args) return cmd.take_action(parsed_args) python-mistralclient-3.7.0/mistralclient/tests/unit/test_utils.py0000666000175000017500000000426613326122347025515 0ustar zuulzuul00000000000000# Copyright 2015 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import json import os.path import tempfile import yaml from mistralclient import utils from oslotest import base ENV_DICT = {'k1': 'abc', 'k2': 123, 'k3': True} ENV_STR = json.dumps(ENV_DICT) ENV_YAML = yaml.safe_dump(ENV_DICT, default_flow_style=False) class UtilityTest(base.BaseTestCase): def test_load_empty(self): self.assertDictEqual(dict(), utils.load_content(None)) self.assertDictEqual(dict(), utils.load_content('')) self.assertDictEqual(dict(), utils.load_content('{}')) self.assertListEqual(list(), utils.load_content('[]')) def test_load_json_content(self): self.assertDictEqual(ENV_DICT, utils.load_content(ENV_STR)) def test_load_json_file(self): with tempfile.NamedTemporaryFile() as f: f.write(ENV_STR.encode('utf-8')) f.flush() file_path = os.path.abspath(f.name) self.assertDictEqual(ENV_DICT, utils.load_file(file_path)) def test_load_yaml_content(self): self.assertDictEqual(ENV_DICT, utils.load_content(ENV_YAML)) def test_load_yaml_file(self): with tempfile.NamedTemporaryFile() as f: f.write(ENV_YAML.encode('utf-8')) f.flush() file_path = os.path.abspath(f.name) self.assertDictEqual(ENV_DICT, utils.load_file(file_path)) def test_load_json(self): with tempfile.NamedTemporaryFile() as f: f.write(ENV_STR.encode('utf-8')) f.flush() self.assertDictEqual(ENV_DICT, utils.load_json(f.name)) self.assertDictEqual(ENV_DICT, utils.load_json(ENV_STR)) python-mistralclient-3.7.0/mistralclient/tests/unit/base_shell_test.py0000666000175000017500000000274513326122347026456 0ustar zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import sys from oslotest import base import six from mistralclient import shell class BaseShellTests(base.BaseTestCase): def shell(self, argstr): orig = (sys.stdout, sys.stderr) clean_env = {} _old_env, os.environ = os.environ, clean_env.copy() try: sys.stdout = six.moves.cStringIO() sys.stderr = six.moves.cStringIO() _shell = shell.MistralShell() _shell.run(argstr.split()) except SystemExit: exc_type, exc_value, exc_traceback = sys.exc_info() self.assertEqual(0, exc_value.code) finally: stdout = sys.stdout.getvalue() stderr = sys.stderr.getvalue() sys.stdout.close() sys.stderr.close() sys.stdout, sys.stderr = orig os.environ = _old_env return stdout, stderr python-mistralclient-3.7.0/mistralclient/tests/unit/v2/0000775000175000017500000000000013326122642023257 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/mistralclient/tests/unit/v2/test_members.py0000666000175000017500000000575213326122347026337 0ustar zuulzuul00000000000000# Copyright 2016 Catalyst IT Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import copy from mistralclient.tests.unit.v2 import base MEMBER = { 'id': '123', 'resource_id': '456', 'resource_type': 'workflow', 'project_id': 'dc4ffdee54d74028b19b1b90e77aa84f', 'member_id': '04f61e967fa14cd49950ffe2319317ad', 'status': 'pending', } WORKFLOW_MEMBERS_URL = '/workflows/%s/members' % (MEMBER['resource_id']) WORKFLOW_MEMBER_URL = '/workflows/%s/members/%s' % ( MEMBER['resource_id'], MEMBER['member_id'] ) class TestWorkflowMembers(base.BaseClientV2Test): def test_create(self): self.requests_mock.post(self.TEST_URL + WORKFLOW_MEMBERS_URL, json=MEMBER, status_code=201) mb = self.members.create( MEMBER['resource_id'], MEMBER['resource_type'], MEMBER['member_id'] ) self.assertIsNotNone(mb) self.assertDictEqual({'member_id': MEMBER['member_id']}, self.requests_mock.last_request.json()) def test_update(self): updated_member = copy.copy(MEMBER) updated_member['status'] = 'accepted' self.requests_mock.put(self.TEST_URL + WORKFLOW_MEMBER_URL, json=updated_member) mb = self.members.update( MEMBER['resource_id'], MEMBER['resource_type'], MEMBER['member_id'] ) self.assertIsNotNone(mb) self.assertDictEqual({"status": "accepted"}, self.requests_mock.last_request.json()) def test_list(self): self.requests_mock.get(self.TEST_URL + WORKFLOW_MEMBERS_URL, json={'members': [MEMBER]}) mbs = self.members.list(MEMBER['resource_id'], MEMBER['resource_type']) self.assertEqual(1, len(mbs)) def test_get(self): self.requests_mock.get(self.TEST_URL + WORKFLOW_MEMBER_URL, json=MEMBER) mb = self.members.get( MEMBER['resource_id'], MEMBER['resource_type'], MEMBER['member_id'] ) self.assertIsNotNone(mb) def test_delete(self): self.requests_mock.delete(self.TEST_URL + WORKFLOW_MEMBER_URL, status_code=204) self.members.delete( MEMBER['resource_id'], MEMBER['resource_type'], MEMBER['member_id'] ) python-mistralclient-3.7.0/mistralclient/tests/unit/v2/test_cli_bash_completion.py0000666000175000017500000000163313326122347030674 0ustar zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import mistralclient.tests.unit.base_shell_test as base class TestCLIBashCompletionV2(base.BaseShellTests): def test_bash_completion(self): bash_completion, stderr = self.shell('bash-completion') self.assertIn('bash-completion', bash_completion) self.assertFalse(stderr) python-mistralclient-3.7.0/mistralclient/tests/unit/v2/test_actions.py0000666000175000017500000002204013326122347026332 0ustar zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os.path 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' ) path = os.path.abspath(path) # Convert the file path to file URI uri = parse.urljoin('file:', request.pathname2url(path)) actions = self.actions.update(uri) self.assertIsNotNone(actions) self.assertEqual(ACTION_DEF, actions[0].definition) last_request = self.requests_mock.last_request self.assertEqual('scope=private', last_request.query) self.assertEqual('text/plain', last_request.headers['content-type']) self.assertEqual(ACTION_DEF, last_request.text) def test_list(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'actions': [ACTION]}) action_list = self.actions.list() self.assertEqual(1, len(action_list)) action = action_list[0] self.assertEqual( actions.Action(self.actions, ACTION).to_dict(), action.to_dict() ) def test_list_with_pagination(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'actions': [ACTION], 'next': '/actions?fake'}) action_list = self.actions.list( limit=1, sort_keys='created_at', sort_dirs='asc' ) self.assertEqual(1, len(action_list)) last_request = self.requests_mock.last_request # The url param order is unpredictable. self.assertEqual(['1'], last_request.qs['limit']) self.assertEqual(['created_at'], last_request.qs['sort_keys']) self.assertEqual(['asc'], last_request.qs['sort_dirs']) def test_list_with_no_limit(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'actions': [ACTION]}) action_list = self.actions.list(limit=-1) self.assertEqual(1, len(action_list)) last_request = self.requests_mock.last_request self.assertNotIn('limit', last_request.qs) def test_get(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE_NAME % 'action', json=ACTION) action = self.actions.get('action') self.assertIsNotNone(action) self.assertEqual( actions.Action(self.actions, ACTION).to_dict(), action.to_dict() ) def test_delete(self): url = self.TEST_URL + URL_TEMPLATE_NAME % 'action' m = self.requests_mock.delete(url, status_code=204) self.actions.delete('action') self.assertEqual(1, m.call_count) def test_validate(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE_VALIDATE, json={'valid': True}) result = self.actions.validate(ACTION_DEF) self.assertIsNotNone(result) self.assertIn('valid', result) self.assertTrue(result['valid']) last_request = self.requests_mock.last_request self.assertEqual(ACTION_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_validate_with_file(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE_VALIDATE, json={'valid': True}) # The contents of action_v2.yaml must be identical to ACTION_DEF path = pkg.resource_filename( 'mistralclient', 'tests/unit/resources/action_v2.yaml' ) result = self.actions.validate(path) self.assertIsNotNone(result) self.assertIn('valid', result) self.assertTrue(result['valid']) last_request = self.requests_mock.last_request self.assertEqual(ACTION_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_validate_failed(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE_VALIDATE, json={"valid": False, "error": "mocked error message"}) result = self.actions.validate(INVALID_ACTION_DEF) self.assertIsNotNone(result) self.assertIn('valid', result) self.assertFalse(result['valid']) self.assertIn('error', result) self.assertIn("mocked error message", result['error']) last_request = self.requests_mock.last_request self.assertEqual('text/plain', last_request.headers['content-type']) def test_validate_api_failed(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE_VALIDATE, status_code=500, json={}) self.assertRaises( api_base.APIException, self.actions.validate, ACTION_DEF ) last_request = self.requests_mock.last_request self.assertEqual('text/plain', last_request.headers['content-type']) self.assertEqual(ACTION_DEF, last_request.text) python-mistralclient-3.7.0/mistralclient/tests/unit/v2/test_keystone.py0000666000175000017500000000505613326122347026543 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import mistralclient.auth.keystone from mistralclient.tests.unit.v2 import base class TestKeystone(base.BaseClientV2Test): def setUp(self): super(TestKeystone, self).setUp() self.keystone = mistralclient.auth.keystone.KeystoneAuthHandler() def test_get_auth_token(self): auth = self.keystone._get_auth( auth_token="token", auth_url="url", project_id="project_id", ) self.assertEqual("url", auth.auth_url) elements = auth.get_cache_id_elements() self.assertIsNotNone(elements["token"]) self.assertIsNotNone(elements["project_id"]) def test_remove_domain(self): params = { "param1": "p", "target_param2": "p2", "user_domain_param3": "p3", "target_project_domain_param4": "p4" } dedomained = self.keystone._remove_domain(params) self.assertIn("param1", dedomained) self.assertIn("target_param2", dedomained) self.assertNotIn("user_domain_param3", dedomained) self.assertNotIn("target_project_domain_param4", dedomained) def test_separate_target_reqs(self): params = { "a": 1, "target_b": 2, "c": 3, "target_d": 4, "target_target": 5, "param_target": 6 } nontarget, target = self.keystone._separate_target_reqs(params) self.assertIn("a", nontarget) self.assertIn("c", nontarget) self.assertIn("param_target", nontarget) self.assertIn("b", target) self.assertIn("d", target) self.assertIn("target", target) def test_verify(self): self.assertTrue(self.keystone._verification_needed("", False)) self.assertFalse(self.keystone._verification_needed("", True)) self.assertFalse(self.keystone._verification_needed("cert", True)) self.assertEqual( self.keystone._verification_needed("cert", False), "cert" ) python-mistralclient-3.7.0/mistralclient/tests/unit/v2/test_workbooks.py0000666000175000017500000001655613326122347026731 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os.path import pkg_resources as pkg from six.moves.urllib import parse from six.moves.urllib import request from mistralclient.api import base as api_base from mistralclient.api.v2 import workbooks from mistralclient.tests.unit.v2 import base # TODO(everyone): later we need additional tests verifying all the errors etc. WB_DEF = """ --- version: 2.0 name: wb workflows: wf1: type: direct input: - param1 - param2 tasks: task1: action: std.http url="localhost:8989" on-success: - test_subsequent test_subsequent: action: std.http url="http://some_url" server_id=1 """ INVALID_WB_DEF = """ version: 2.0 name: wb workflows: wf1: type: direct tasks: task1: action: std.http url="localhost:8989" workflow: wf2 """ WORKBOOK = {'definition': WB_DEF} URL_TEMPLATE = '/workbooks' URL_TEMPLATE_NAME = '/workbooks/%s' URL_TEMPLATE_VALIDATE = '/workbooks/validate' class TestWorkbooksV2(base.BaseClientV2Test): def test_create(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE, json=WORKBOOK, status_code=201) wb = self.workbooks.create(WB_DEF) self.assertIsNotNone(wb) self.assertEqual(WB_DEF, wb.definition) last_request = self.requests_mock.last_request self.assertEqual(WB_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_create_with_file_uri(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE, json=WORKBOOK, status_code=201) # The contents of wb_v2.yaml must be identical to WB_DEF path = pkg.resource_filename( 'mistralclient', 'tests/unit/resources/wb_v2.yaml' ) path = os.path.abspath(path) # Convert the file path to file URI uri = parse.urljoin('file:', request.pathname2url(path)) wb = self.workbooks.create(uri) self.assertIsNotNone(wb) self.assertEqual(WB_DEF, wb.definition) last_request = self.requests_mock.last_request self.assertEqual(WB_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_update(self): self.requests_mock.put(self.TEST_URL + URL_TEMPLATE, json=WORKBOOK) wb = self.workbooks.update(WB_DEF) self.assertIsNotNone(wb) self.assertEqual(WB_DEF, wb.definition) last_request = self.requests_mock.last_request self.assertEqual(WB_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_update_with_file(self): self.requests_mock.put(self.TEST_URL + URL_TEMPLATE, json=WORKBOOK) # The contents of wb_v2.yaml must be identical to WB_DEF path = pkg.resource_filename( 'mistralclient', 'tests/unit/resources/wb_v2.yaml' ) wb = self.workbooks.update(path) self.assertIsNotNone(wb) self.assertEqual(WB_DEF, wb.definition) last_request = self.requests_mock.last_request self.assertEqual(WB_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_list(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'workbooks': [WORKBOOK]}) workbook_list = self.workbooks.list() self.assertEqual(1, len(workbook_list)) wb = workbook_list[0] self.assertEqual( workbooks.Workbook(self.workbooks, WORKBOOK).to_dict(), wb.to_dict() ) def test_get(self): url = self.TEST_URL + URL_TEMPLATE_NAME % 'wb' self.requests_mock.get(url, json=WORKBOOK) wb = self.workbooks.get('wb') self.assertIsNotNone(wb) self.assertEqual( workbooks.Workbook(self.workbooks, WORKBOOK).to_dict(), wb.to_dict() ) def test_delete(self): url = self.TEST_URL + URL_TEMPLATE_NAME % 'wb' self.requests_mock.delete(url, status_code=204) self.workbooks.delete('wb') def test_validate(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE_VALIDATE, json={'valid': True}) result = self.workbooks.validate(WB_DEF) self.assertIsNotNone(result) self.assertIn('valid', result) self.assertTrue(result['valid']) last_request = self.requests_mock.last_request self.assertEqual(WB_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_validate_with_file(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE_VALIDATE, json={'valid': True}) # The contents of wb_v2.yaml must be identical to WB_DEF path = pkg.resource_filename( 'mistralclient', 'tests/unit/resources/wb_v2.yaml' ) result = self.workbooks.validate(path) self.assertIsNotNone(result) self.assertIn('valid', result) self.assertTrue(result['valid']) last_request = self.requests_mock.last_request self.assertEqual(WB_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_validate_failed(self): mock_result = { "valid": False, "error": "Task properties 'action' and 'workflow' " "can't be specified both" } self.requests_mock.post(self.TEST_URL + URL_TEMPLATE_VALIDATE, json=mock_result) result = self.workbooks.validate(INVALID_WB_DEF) self.assertIsNotNone(result) self.assertIn('valid', result) self.assertFalse(result['valid']) self.assertIn('error', result) self.assertIn( "Task properties 'action' and 'workflow' " "can't be specified both", result['error'] ) last_request = self.requests_mock.last_request self.assertEqual(INVALID_WB_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_validate_api_failed(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE_VALIDATE, status_code=500) self.assertRaises( api_base.APIException, self.workbooks.validate, WB_DEF ) last_request = self.requests_mock.last_request self.assertEqual(WB_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) python-mistralclient-3.7.0/mistralclient/tests/unit/v2/test_action_executions.py0000666000175000017500000001054213326122347030421 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from mistralclient.api.v2 import action_executions from mistralclient.tests.unit.v2 import base # TODO(everyone): later we need additional tests verifying all the errors etc. ACTION_EXEC = { 'id': "1", 'name': 'my_action_execution', 'workflow_name': 'my_wf', 'state': 'RUNNING', } URL_TEMPLATE = '/action_executions' URL_TEMPLATE_ID = '/action_executions/%s' class TestActionExecutions(base.BaseClientV2Test): def test_create(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE, json=ACTION_EXEC, status_code=201) body = { 'name': ACTION_EXEC['name'] } action_execution = self.action_executions.create( 'my_action_execution', {} ) self.assertIsNotNone(action_execution) self.assertEqual(action_executions.ActionExecution( self.action_executions, ACTION_EXEC ).to_dict(), action_execution.to_dict()) self.assertEqual(body, self.requests_mock.last_request.json()) def test_update(self): url = self.TEST_URL + URL_TEMPLATE_ID % ACTION_EXEC['id'] self.requests_mock.put(url, json=ACTION_EXEC) body = { 'state': ACTION_EXEC['state'] } action_execution = self.action_executions.update( ACTION_EXEC['id'], ACTION_EXEC['state'] ) self.assertIsNotNone(action_execution) expected = action_executions.ActionExecution( self.action_executions, ACTION_EXEC ).to_dict() self.assertEqual( expected, action_execution.to_dict() ) self.assertEqual(body, self.requests_mock.last_request.json()) def test_list(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'action_executions': [ACTION_EXEC]}) action_execution_list = self.action_executions.list() self.assertEqual(1, len(action_execution_list)) action_execution = action_execution_list[0] expected = action_executions.ActionExecution( self.action_executions, ACTION_EXEC ).to_dict() self.assertEqual(expected, action_execution.to_dict()) def test_list_with_limit(self): self.requests_mock.get( self.TEST_URL + URL_TEMPLATE, json={'action_executions': [ACTION_EXEC]} ) action_execution_list = self.action_executions.list(limit=1) self.assertEqual(1, len(action_execution_list)) last_request = self.requests_mock.last_request # Make sure that limit is passed to the server correctly. self.assertEqual(['1'], last_request.qs['limit']) def test_list_with_no_limit(self): self.requests_mock.get( self.TEST_URL + URL_TEMPLATE, json={'action_executions': [ACTION_EXEC]} ) action_execution_list = self.action_executions.list(limit=-1) self.assertEqual(1, len(action_execution_list)) last_request = self.requests_mock.last_request self.assertNotIn('limit', last_request.qs) def test_get(self): url = self.TEST_URL + URL_TEMPLATE_ID % ACTION_EXEC['id'] self.requests_mock.get(url, json=ACTION_EXEC) action_execution = self.action_executions.get(ACTION_EXEC['id']) expected = action_executions.ActionExecution( self.action_executions, ACTION_EXEC ).to_dict() self.assertEqual(expected, action_execution.to_dict()) def test_delete(self): url = self.TEST_URL + URL_TEMPLATE_ID % ACTION_EXEC['id'] self.requests_mock.delete(url, status_code=204) self.action_executions.delete(ACTION_EXEC['id']) python-mistralclient-3.7.0/mistralclient/tests/unit/v2/test_cli_executions.py0000666000175000017500000001757013326122347027723 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, Inc. # Copyright 2016 - Brocade Communications Systems, Inc. # # All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import mock import pkg_resources as pkg import six import sys from mistralclient.api.v2 import executions from mistralclient.commands.v2 import executions as execution_cmd from mistralclient.tests.unit import base EXEC = executions.Execution( mock, { 'id': '123', 'workflow_id': '123e4567-e89b-12d3-a456-426655440000', 'workflow_name': 'some', 'workflow_namespace': '', 'root_execution_id': '', 'description': '', 'state': 'RUNNING', 'state_info': None, 'created_at': '1', 'updated_at': '1', 'task_execution_id': None } ) SUB_WF_EXEC = executions.Execution( mock, { 'id': '456', 'workflow_id': '123e4567-e89b-12d3-a456-426655440000', 'workflow_name': 'some_sub_wf', 'workflow_namespace': '', 'root_execution_id': 'ROOT_EXECUTION_ID', 'description': '', 'state': 'RUNNING', 'state_info': None, 'created_at': '1', 'updated_at': '1', 'task_execution_id': 'abc' } ) EX_RESULT = ( '123', '123e4567-e89b-12d3-a456-426655440000', 'some', '', '', '', '', 'RUNNING', None, '1', '1' ) SUB_WF_EX_RESULT = ( '456', '123e4567-e89b-12d3-a456-426655440000', 'some_sub_wf', '', '', 'abc', 'ROOT_EXECUTION_ID', 'RUNNING', None, '1', '1' ) class TestCLIExecutionsV2(base.BaseCommandTest): stdout = six.moves.StringIO() stderr = six.moves.StringIO() def setUp(self): super(TestCLIExecutionsV2, self).setUp() # Redirect stdout and stderr so it doesn't pollute the test result. sys.stdout = self.stdout sys.stderr = self.stderr def tearDown(self): super(TestCLIExecutionsV2, self).tearDown() # Reset to original stdout and stderr. sys.stdout = sys.__stdout__ sys.stderr = sys.__stderr__ def test_create_wf_input_string(self): self.client.executions.create.return_value = EXEC result = self.call( execution_cmd.Create, app_args=['id', '{ "context": true }'] ) self.assertEqual( EX_RESULT, result[1] ) def test_create_wf_input_file(self): self.client.executions.create.return_value = EXEC path = pkg.resource_filename( 'mistralclient', 'tests/unit/resources/ctx.json' ) result = self.call( execution_cmd.Create, app_args=['id', path] ) self.assertEqual( EX_RESULT, result[1] ) def test_create_with_description(self): self.client.executions.create.return_value = EXEC result = self.call( execution_cmd.Create, app_args=['id', '{ "context": true }', '-d', ''] ) self.assertEqual( EX_RESULT, result[1] ) def test_update_state(self): states = ['RUNNING', 'SUCCESS', 'PAUSED', 'ERROR', 'CANCELLED'] for state in states: self.client.executions.update.return_value = executions.Execution( mock, { 'id': '123', 'workflow_id': '123e4567-e89b-12d3-a456-426655440000', 'workflow_name': 'some', 'workflow_namespace': '', 'root_execution_id': '', 'description': '', 'state': state, 'state_info': None, 'created_at': '1', 'updated_at': '1', 'task_execution_id': None } ) ex_result = list(EX_RESULT) ex_result[7] = 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', force=False ) def test_delete_with_force(self): self.call(execution_cmd.Delete, app_args=['id', '--force']) self.client.executions.delete.assert_called_once_with( 'id', force=True ) 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', force=False), mock.call('id2', force=False)], self.client.executions.delete.call_args_list ) python-mistralclient-3.7.0/mistralclient/tests/unit/v2/test_executions.py0000666000175000017500000001752613326122347027075 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import json from mistralclient.api import base as api_base from mistralclient.api.v2 import executions from mistralclient.tests.unit.v2 import base # TODO(everyone): Later we need additional tests verifying all the errors etc. EXEC = { 'id': "123", 'workflow_id': '123e4567-e89b-12d3-a456-426655440000', 'workflow_name': 'my_wf', 'workflow_namespace': '', 'description': '', 'state': 'RUNNING', 'input': { "person": { "first_name": "John", "last_name": "Doe" } } } SUB_WF_EXEC = { 'id': "456", 'workflow_id': '123e4567-e89b-12d3-a456-426655440000', 'workflow_name': 'my_sub_wf', 'workflow_namespace': '', 'task_execution_id': "abc", 'description': '', 'state': 'RUNNING', 'input': { "person": { "first_name": "John", "last_name": "Doe" } } } SOURCE_EXEC = EXEC SOURCE_EXEC['source_execution_id'] = EXEC['workflow_id'] URL_TEMPLATE = '/executions' URL_TEMPLATE_ID = '/executions/%s' class TestExecutionsV2(base.BaseClientV2Test): def test_create(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE, json=EXEC, status_code=201) body = { 'workflow_name': EXEC['workflow_name'], 'description': '', 'input': json.dumps(EXEC['input']) } ex = self.executions.create( EXEC['workflow_name'], EXEC['workflow_namespace'], EXEC['input'] ) self.assertIsNotNone(ex) self.assertDictEqual( executions.Execution(self.executions, EXEC).to_dict(), ex.to_dict() ) self.assertDictEqual(body, self.requests_mock.last_request.json()) def test_create_with_workflow_id(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE, json=EXEC, status_code=201) body = { 'workflow_id': EXEC['workflow_id'], 'description': '', 'input': json.dumps(EXEC['input']) } ex = self.executions.create( EXEC['workflow_id'], workflow_input=EXEC['input'] ) self.assertIsNotNone(ex) self.assertDictEqual( executions.Execution(self.executions, EXEC).to_dict(), ex.to_dict() ) self.assertDictEqual(body, self.requests_mock.last_request.json()) def test_create_with_source_execution_id(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE, json=SOURCE_EXEC, status_code=201) body = { 'description': '', 'source_execution_id': SOURCE_EXEC['source_execution_id'] } ex = self.executions.create( source_execution_id=SOURCE_EXEC['source_execution_id'] ) self.assertIsNotNone(ex) self.assertDictEqual( executions.Execution(self.executions, SOURCE_EXEC).to_dict(), ex.to_dict() ) self.assertDictEqual(body, self.requests_mock.last_request.json()) def test_create_failure1(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE, json=EXEC, status_code=201) self.assertRaises(api_base.APIException, self.executions.create, '') def test_update(self): url = self.TEST_URL + URL_TEMPLATE_ID % EXEC['id'] self.requests_mock.put(url, json=EXEC) body = { 'state': EXEC['state'] } ex = self.executions.update(EXEC['id'], EXEC['state']) self.assertIsNotNone(ex) self.assertDictEqual( executions.Execution(self.executions, EXEC).to_dict(), ex.to_dict() ) self.assertDictEqual(body, self.requests_mock.last_request.json()) def test_update_env(self): url = self.TEST_URL + URL_TEMPLATE_ID % EXEC['id'] self.requests_mock.put(url, json=EXEC) body = { 'state': EXEC['state'], 'params': { 'env': {'k1': 'foobar'} } } ex = self.executions.update( EXEC['id'], EXEC['state'], env={'k1': 'foobar'} ) self.assertIsNotNone(ex) self.assertDictEqual( executions.Execution(self.executions, EXEC).to_dict(), ex.to_dict() ) self.assertDictEqual(body, self.requests_mock.last_request.json()) def test_list(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'executions': [EXEC, SUB_WF_EXEC]}) execution_list = self.executions.list() self.assertEqual(2, len(execution_list)) self.assertDictEqual( executions.Execution(self.executions, EXEC).to_dict(), execution_list[0].to_dict() ) self.assertDictEqual( executions.Execution(self.executions, SUB_WF_EXEC).to_dict(), execution_list[1].to_dict() ) def test_list_with_pagination(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'executions': [EXEC], 'next': '/executions?fake'}) execution_list = self.executions.list( limit=1, sort_keys='created_at', sort_dirs='asc' ) self.assertEqual(1, len(execution_list)) last_request = self.requests_mock.last_request # The url param order is unpredictable. self.assertEqual(['1'], last_request.qs['limit']) self.assertEqual(['created_at'], last_request.qs['sort_keys']) self.assertEqual(['asc'], last_request.qs['sort_dirs']) def test_list_with_no_limit(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'executions': [EXEC]}) execution_list = self.executions.list(limit=-1) self.assertEqual(1, len(execution_list)) last_request = self.requests_mock.last_request self.assertNotIn('limit', last_request.qs) def test_get(self): url = self.TEST_URL + URL_TEMPLATE_ID % EXEC['id'] self.requests_mock.get(url, json=EXEC) ex = self.executions.get(EXEC['id']) self.assertDictEqual( executions.Execution(self.executions, EXEC).to_dict(), ex.to_dict() ) def test_get_sub_wf_ex(self): url = self.TEST_URL + URL_TEMPLATE_ID % SUB_WF_EXEC['id'] self.requests_mock.get(url, json=SUB_WF_EXEC) ex = self.executions.get(SUB_WF_EXEC['id']) self.assertDictEqual( executions.Execution(self.executions, SUB_WF_EXEC).to_dict(), ex.to_dict() ) def test_delete_with_force(self): url = self.TEST_URL + URL_TEMPLATE_ID % EXEC['id'] self.requests_mock.delete(url, status_code=204) self.executions.delete(EXEC['id'], force=True) def test_delete(self): url = self.TEST_URL + URL_TEMPLATE_ID % EXEC['id'] self.requests_mock.delete(url, status_code=204) self.executions.delete(EXEC['id']) python-mistralclient-3.7.0/mistralclient/tests/unit/v2/test_services.py0000666000175000017500000000235113326122347026520 0ustar zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from mistralclient.api.v2 import services from mistralclient.tests.unit.v2 import base SERVICE = { 'name': 'service_name', 'type': 'service_type', } URL_TEMPLATE = '/services' class TestServicesV2(base.BaseClientV2Test): def test_list(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'services': [SERVICE]}) service_list = self.services.list() self.assertEqual(1, len(service_list)) srv = service_list[0] self.assertDictEqual( services.Service(self.services, SERVICE).to_dict(), srv.to_dict() ) python-mistralclient-3.7.0/mistralclient/tests/unit/v2/test_environments.py0000666000175000017500000001270213326122347027425 0ustar zuulzuul00000000000000# Copyright 2015 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import collections import copy import json import os.path import pkg_resources as pkg from six.moves.urllib import parse from six.moves.urllib import request from mistralclient.api import base as api_base from mistralclient.api.v2 import environments from mistralclient.tests.unit.v2 import base from mistralclient import utils ENVIRONMENT = { 'name': 'env1', 'description': 'Test Environment #1', 'scope': 'private', 'variables': { 'server': 'localhost' } } URL_TEMPLATE = '/environments' URL_TEMPLATE_NAME = '/environments/%s' class TestEnvironmentsV2(base.BaseClientV2Test): def test_create(self): data = copy.deepcopy(ENVIRONMENT) self.requests_mock.post(self.TEST_URL + URL_TEMPLATE, status_code=201, json=data) env = self.environments.create(**data) self.assertIsNotNone(env) expected_data = copy.deepcopy(data) expected_data['variables'] = json.dumps(expected_data['variables']) self.assertEqual(expected_data, self.requests_mock.last_request.json()) def test_create_with_json_file_uri(self): # The contents of env_v2.json must be equivalent to ENVIRONMENT path = pkg.resource_filename( 'mistralclient', 'tests/unit/resources/env_v2.json' ) path = os.path.abspath(path) # Convert the file path to file URI uri = parse.urljoin('file:', request.pathname2url(path)) data = collections.OrderedDict( utils.load_content( utils.get_contents_if_file(uri) ) ) self.requests_mock.post(self.TEST_URL + URL_TEMPLATE, status_code=201, json=data) file_input = {'file': uri} env = self.environments.create(**file_input) self.assertIsNotNone(env) expected_data = copy.deepcopy(data) expected_data['variables'] = json.dumps(expected_data['variables']) self.assertEqual(expected_data, self.requests_mock.last_request.json()) def test_create_without_name(self): data = copy.deepcopy(ENVIRONMENT) data.pop('name') e = self.assertRaises(api_base.APIException, self.environments.create, **data) self.assertEqual(400, e.error_code) def test_update(self): data = copy.deepcopy(ENVIRONMENT) self.requests_mock.put(self.TEST_URL + URL_TEMPLATE, json=data) env = self.environments.update(**data) self.assertIsNotNone(env) expected_data = copy.deepcopy(data) expected_data['variables'] = json.dumps(expected_data['variables']) self.assertEqual(expected_data, self.requests_mock.last_request.json()) def test_update_with_yaml_file(self): # The contents of env_v2.json must be equivalent to ENVIRONMENT path = pkg.resource_filename( 'mistralclient', 'tests/unit/resources/env_v2.json' ) data = collections.OrderedDict( utils.load_content( utils.get_contents_if_file(path) ) ) self.requests_mock.put(self.TEST_URL + URL_TEMPLATE, json=data) file_input = {'file': path} env = self.environments.update(**file_input) self.assertIsNotNone(env) expected_data = copy.deepcopy(data) expected_data['variables'] = json.dumps(expected_data['variables']) self.assertEqual(expected_data, self.requests_mock.last_request.json()) def test_update_without_name(self): data = copy.deepcopy(ENVIRONMENT) data.pop('name') e = self.assertRaises(api_base.APIException, self.environments.update, **data) self.assertEqual(400, e.error_code) def test_list(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'environments': [ENVIRONMENT]}) environment_list = self.environments.list() self.assertEqual(1, len(environment_list)) env = environment_list[0] self.assertDictEqual( environments.Environment(self.environments, ENVIRONMENT).to_dict(), env.to_dict() ) def test_get(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE_NAME % 'env', json=ENVIRONMENT) env = self.environments.get('env') self.assertIsNotNone(env) self.assertDictEqual( environments.Environment(self.environments, ENVIRONMENT).to_dict(), env.to_dict() ) def test_delete(self): self.requests_mock.delete(self.TEST_URL + URL_TEMPLATE_NAME % 'env', status_code=204) self.environments.delete('env') python-mistralclient-3.7.0/mistralclient/tests/unit/v2/test_cli_workbooks.py0000666000175000017500000001152713326122347027551 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import mock from mistralclient.api.v2 import workbooks from mistralclient.commands.v2 import workbooks as workbook_cmd from mistralclient.tests.unit import base WORKBOOK_DICT = { 'name': 'a', 'tags': ['a', 'b'], 'scope': 'private', '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', 'private', '1', '1'), result[1]) @mock.patch('argparse.open', create=True) def test_create_public(self, mock_open): wb_public_dict = WORKBOOK_DICT.copy() wb_public_dict['scope'] = 'public' workbook_public = workbooks.Workbook(mock, wb_public_dict) self.client.workbooks.create.return_value = workbook_public result = self.call( workbook_cmd.Create, app_args=['wb.yaml', '--public'] ) self.assertEqual(('a', 'a, b', 'public', '1', '1'), result[1]) self.assertEqual( 'public', self.client.workbooks.create.call_args[1]['scope'] ) @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', 'private', '1', '1'), result[1]) @mock.patch('argparse.open', create=True) def test_update_public(self, mock_open): wb_public_dict = WORKBOOK_DICT.copy() wb_public_dict['scope'] = 'public' workbook_public = workbooks.Workbook(mock, wb_public_dict) self.client.workbooks.update.return_value = workbook_public result = self.call( workbook_cmd.Update, app_args=['definition', '--public'] ) self.assertEqual(('a', 'a, b', 'public', '1', '1'), result[1]) self.assertEqual( 'public', self.client.workbooks.update.call_args[1]['scope'] ) def test_list(self): self.client.workbooks.list.return_value = [WORKBOOK] result = self.call(workbook_cmd.List) self.assertEqual([('a', 'a, b', 'private', '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', 'private', '1', '1'), result[1]) def test_delete(self): self.call(workbook_cmd.Delete, app_args=['name']) self.client.workbooks.delete.assert_called_once_with('name') def test_delete_with_multi_names(self): self.call(workbook_cmd.Delete, app_args=['name1', 'name2']) self.assertEqual(2, self.client.workbooks.delete.call_count) self.assertEqual( [mock.call('name1'), mock.call('name2')], self.client.workbooks.delete.call_args_list ) def test_get_definition(self): self.client.workbooks.get.return_value = WORKBOOK_WITH_DEF self.call(workbook_cmd.GetDefinition, app_args=['name']) self.app.stdout.write.assert_called_with(WB_DEF) @mock.patch('argparse.open', create=True) def test_validate(self, mock_open): self.client.workbooks.validate.return_value = {'valid': True} result = self.call(workbook_cmd.Validate, app_args=['wb.yaml']) self.assertEqual((True, None), result[1]) @mock.patch('argparse.open', create=True) def test_validate_failed(self, mock_open): self.client.workbooks.validate.return_value = { 'valid': False, 'error': 'Invalid DSL...' } result = self.call(workbook_cmd.Validate, app_args=['wb.yaml']) self.assertEqual((False, 'Invalid DSL...'), result[1]) python-mistralclient-3.7.0/mistralclient/tests/unit/v2/test_cli_action_execs.py0000666000175000017500000001567313326122347030203 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2016 - Brocade Communications Systems, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import copy import json import sys import six import mock from mistralclient.api.v2 import action_executions as action_ex from mistralclient.commands.v2 import action_executions as action_ex_cmd from mistralclient.tests.unit import base ACTION_EX_DICT = { 'id': '123', 'name': 'some', 'workflow_name': 'thing', 'workflow_namespace': '', 'task_name': 'task1', 'task_execution_id': "1-2-3-4", 'state': 'RUNNING', 'state_info': 'RUNNING somehow.', 'accepted': True, 'created_at': '1', 'updated_at': '1', } ACTION_EX_RESULT = {"test": "is", "passed": "successfully"} ACTION_EX_INPUT = {"param1": "val1", "param2": 2} ACTION_EX_WITH_OUTPUT_DICT = ACTION_EX_DICT.copy() ACTION_EX_WITH_OUTPUT_DICT.update({'output': json.dumps(ACTION_EX_RESULT)}) ACTION_EX_WITH_INPUT_DICT = ACTION_EX_DICT.copy() ACTION_EX_WITH_INPUT_DICT.update({'input': json.dumps(ACTION_EX_INPUT)}) ACTION_EX = action_ex.ActionExecution(mock, ACTION_EX_DICT) ACTION_EX_WITH_OUTPUT = action_ex.ActionExecution( mock, ACTION_EX_WITH_OUTPUT_DICT ) ACTION_EX_WITH_INPUT = action_ex.ActionExecution( mock, ACTION_EX_WITH_INPUT_DICT ) class TestCLIActionExecutions(base.BaseCommandTest): def test_create(self): (self.client.action_executions.create. return_value) = ACTION_EX_WITH_OUTPUT self.call( action_ex_cmd.Create, app_args=['some', '{"output": "Hello!"}'] ) self.assertDictEqual( ACTION_EX_RESULT, json.loads(self.app.stdout.write.call_args[0][0]) ) def test_create_save_result(self): (self.client.action_executions.create. return_value) = ACTION_EX_WITH_OUTPUT result = self.call( action_ex_cmd.Create, app_args=[ 'some', '{"output": "Hello!"}', '--save-result' ] ) self.assertEqual( ('123', 'some', 'thing', '', 'task1', '1-2-3-4', 'RUNNING', 'RUNNING somehow.', True, '1', '1'), result[1] ) def test_create_run_sync(self): (self.client.action_executions.create. return_value) = ACTION_EX_WITH_OUTPUT self.call( action_ex_cmd.Create, app_args=[ 'some', '{"output": "Hello!"}', '--run-sync' ] ) self.assertDictEqual( ACTION_EX_RESULT, json.loads(self.app.stdout.write.call_args[0][0]) ) def test_create_run_sync_and_save_result(self): (self.client.action_executions.create. return_value) = ACTION_EX_WITH_OUTPUT self.call( action_ex_cmd.Create, app_args=[ 'some', '{"output": "Hello!"}', '--save-result', '--run-sync' ] ) self.assertDictEqual( ACTION_EX_RESULT, json.loads(self.app.stdout.write.call_args[0][0]) ) def test_update(self): states = ['IDLE', 'RUNNING', 'SUCCESS', 'ERROR', 'CANCELLED'] for state in states: action_ex_dict = copy.deepcopy(ACTION_EX_DICT) action_ex_dict['state'] = state action_ex_dict['state_info'] = 'testing update' action_ex_obj = action_ex.ActionExecution(mock, action_ex_dict) self.client.action_executions.update.return_value = action_ex_obj result = self.call( action_ex_cmd.Update, app_args=['id', '--state', state] ) expected_result = ( action_ex_dict['id'], action_ex_dict['name'], action_ex_dict['workflow_name'], action_ex_dict['workflow_namespace'], action_ex_dict['task_name'], action_ex_dict['task_execution_id'], action_ex_dict['state'], action_ex_dict['state_info'], action_ex_dict['accepted'], action_ex_dict['created_at'], action_ex_dict['updated_at'] ) self.assertEqual(expected_result, result[1]) def test_update_invalid_state(self): states = ['PAUSED', 'WAITING', 'DELAYED'] # Redirect the stderr so it doesn't show during tox _stderr = sys.stderr sys.stderr = six.StringIO() for state in states: self.assertRaises( SystemExit, self.call, action_ex_cmd.Update, app_args=['id', '--state', state] ) # Stop the redirection print(sys.stderr.getvalue()) sys.stderr = _stderr def test_list(self): self.client.action_executions.list.return_value = [ACTION_EX] result = self.call(action_ex_cmd.List) self.assertEqual( [('123', 'some', 'thing', '', 'task1', '1-2-3-4', 'RUNNING', True, '1', '1')], result[1] ) def test_get(self): self.client.action_executions.get.return_value = ACTION_EX result = self.call(action_ex_cmd.Get, app_args=['id']) self.assertEqual( ('123', 'some', 'thing', '', 'task1', '1-2-3-4', 'RUNNING', 'RUNNING somehow.', True, '1', '1'), result[1] ) def test_get_output(self): self.client.action_executions.get.return_value = ACTION_EX_WITH_OUTPUT self.call(action_ex_cmd.GetOutput, app_args=['id']) self.assertDictEqual( ACTION_EX_RESULT, json.loads(self.app.stdout.write.call_args[0][0]) ) def test_get_input(self): self.client.action_executions.get.return_value = ACTION_EX_WITH_INPUT self.call(action_ex_cmd.GetInput, app_args=['id']) self.assertDictEqual( ACTION_EX_INPUT, json.loads(self.app.stdout.write.call_args[0][0]) ) def test_delete(self): self.call(action_ex_cmd.Delete, app_args=['id']) self.client.action_executions.delete.assert_called_once_with('id') def test_delete_with_multi_names(self): self.call(action_ex_cmd.Delete, app_args=['id1', 'id2']) self.assertEqual(2, self.client.action_executions.delete.call_count) self.assertEqual( [mock.call('id1'), mock.call('id2')], self.client.action_executions.delete.call_args_list ) python-mistralclient-3.7.0/mistralclient/tests/unit/v2/base.py0000666000175000017500000000317213326122347024552 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import mock from mistralclient.api.v2 import client from mistralclient.tests.unit import base class BaseClientV2Test(base.BaseClientTest): TEST_URL = 'http://mistral.example.com' def setUp(self): super(BaseClientV2Test, self).setUp() with mock.patch( 'mistralclient.auth.keystone.KeystoneAuthHandler.authenticate', return_value={'session': None}): self._client = client.Client(project_name="test", mistral_url=self.TEST_URL) self.workbooks = self._client.workbooks self.executions = self._client.executions self.tasks = self._client.tasks self.workflows = self._client.workflows self.environments = self._client.environments self.action_executions = self._client.action_executions self.actions = self._client.actions self.services = self._client.services self.members = self._client.members python-mistralclient-3.7.0/mistralclient/tests/unit/v2/test_cli_cron_triggers.py0000666000175000017500000001366213326122347030402 0ustar zuulzuul00000000000000# Copyright 2014 Mirantis, Inc. # All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import mock from mistralclient.api.v2 import cron_triggers from mistralclient.commands.v2 import cron_triggers as cron_triggers_cmd from mistralclient.tests.unit import base TRIGGER_DICT = { 'name': 'my_trigger', 'workflow_name': 'flow1', 'workflow_input': {}, 'workflow_params': {}, 'pattern': '* * * * *', 'next_execution_time': '4242-12-20 13:37', 'remaining_executions': 5, 'created_at': '1', 'updated_at': '1' } TRIGGER = cron_triggers.CronTrigger(mock, TRIGGER_DICT) class TestCLITriggersV2(base.BaseCommandTest): @mock.patch('mistralclient.commands.v2.cron_triggers.Create.' '_convert_time_string_to_utc') @mock.patch('argparse.open', create=True) def test_create(self, mock_open, mock_convert): self.client.cron_triggers.create.return_value = TRIGGER mock_open.return_value = mock.MagicMock(spec=open) result = self.call( cron_triggers_cmd.Create, app_args=['my_trigger', 'flow1', '--pattern', '* * * * *', '--params', '{}', '--count', '5', '--first-time', '4242-12-20 13:37', '--utc'] ) mock_convert.assert_not_called() self.assertEqual( ( 'my_trigger', 'flow1', {}, '* * * * *', '4242-12-20 13:37', 5, '1', '1' ), result[1] ) @mock.patch('mistralclient.commands.v2.cron_triggers.Create.' '_convert_time_string_to_utc') @mock.patch('argparse.open', create=True) def test_create_no_utc(self, mock_open, mock_convert): self.client.cron_triggers.create.return_value = TRIGGER mock_open.return_value = mock.MagicMock(spec=open) mock_convert.return_value = '4242-12-20 18:37' result = self.call( cron_triggers_cmd.Create, app_args=['my_trigger', 'flow1', '--pattern', '* * * * *', '--params', '{}', '--count', '5', '--first-time', '4242-12-20 13:37'] ) mock_convert.assert_called_once_with('4242-12-20 13:37') self.client.cron_triggers.create.assert_called_once_with( 'my_trigger', 'flow1', {}, {}, '* * * * *', '4242-12-20 18:37', 5) self.assertEqual( ( 'my_trigger', 'flow1', {}, '* * * * *', '4242-12-20 13:37', 5, '1', '1' ), result[1] ) @mock.patch('mistralclient.commands.v2.cron_triggers.time') def test_convert_time_string_to_utc_from_utc(self, mock_time): cmd = cron_triggers_cmd.Create(self.app, None) mock_time.daylight = 0 mock_time.altzone = 0 mock_time.timezone = 0 mock_localtime = mock.Mock() mock_localtime.tm_isdst = 0 mock_time.localtime.return_value = mock_localtime utc_value = cmd._convert_time_string_to_utc('4242-12-20 13:37') expected_time = '4242-12-20 13:37' self.assertEqual(expected_time, utc_value) @mock.patch('mistralclient.commands.v2.cron_triggers.time') def test_convert_time_string_to_utc_from_dst(self, mock_time): cmd = cron_triggers_cmd.Create(self.app, None) mock_time.daylight = 1 mock_time.altzone = (4 * 60 * 60) mock_time.timezone = (5 * 60 * 60) mock_localtime = mock.Mock() mock_localtime.tm_isdst = 1 mock_time.localtime.return_value = mock_localtime utc_value = cmd._convert_time_string_to_utc('4242-12-20 13:37') expected_time = '4242-12-20 17:37' self.assertEqual(expected_time, utc_value) @mock.patch('mistralclient.commands.v2.cron_triggers.time') def test_convert_time_string_to_utc_no_dst(self, mock_time): cmd = cron_triggers_cmd.Create(self.app, None) mock_time.daylight = 1 mock_time.altzone = (4 * 60 * 60) mock_time.timezone = (5 * 60 * 60) mock_localtime = mock.Mock() mock_localtime.tm_isdst = 0 mock_time.localtime.return_value = mock_localtime utc_value = cmd._convert_time_string_to_utc('4242-12-20 13:37') expected_time = '4242-12-20 18:37' self.assertEqual(expected_time, utc_value) def test_list(self): self.client.cron_triggers.list.return_value = [TRIGGER] result = self.call(cron_triggers_cmd.List) self.assertEqual( [( 'my_trigger', 'flow1', {}, '* * * * *', '4242-12-20 13:37', 5, '1', '1' )], result[1] ) def test_get(self): self.client.cron_triggers.get.return_value = TRIGGER result = self.call(cron_triggers_cmd.Get, app_args=['name']) self.assertEqual( ( 'my_trigger', 'flow1', {}, '* * * * *', '4242-12-20 13:37', 5, '1', '1' ), result[1] ) def test_delete(self): self.call(cron_triggers_cmd.Delete, app_args=['name']) self.client.cron_triggers.delete.assert_called_once_with('name') def test_delete_with_multi_names(self): self.call(cron_triggers_cmd.Delete, app_args=['name1', 'name2']) self.assertEqual(2, self.client.cron_triggers.delete.call_count) self.assertEqual( [mock.call('name1'), mock.call('name2')], self.client.cron_triggers.delete.call_args_list ) python-mistralclient-3.7.0/mistralclient/tests/unit/v2/test_workflows.py0000666000175000017500000001425613326122347026741 0ustar zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os.path import pkg_resources as pkg from six.moves.urllib import parse from six.moves.urllib import request from mistralclient.api.v2 import workflows from mistralclient.tests.unit.v2 import base WF_DEF = """ --- version: 2.0 my_wf: type: direct tasks: task1: action: std.echo output="hello, world" """ WORKFLOW = { 'id': '123', 'name': 'my_wf', 'input': '', 'definition': WF_DEF } URL_TEMPLATE = '/workflows' URL_TEMPLATE_SCOPE = '/workflows?scope=private' URL_TEMPLATE_NAME = '/workflows/%s' class TestWorkflowsV2(base.BaseClientV2Test): def test_create(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE_SCOPE, json={'workflows': [WORKFLOW]}, status_code=201) wfs = self.workflows.create(WF_DEF) self.assertIsNotNone(wfs) self.assertEqual(WF_DEF, wfs[0].definition) last_request = self.requests_mock.last_request self.assertEqual(WF_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_create_with_file(self): self.requests_mock.post(self.TEST_URL + URL_TEMPLATE_SCOPE, json={'workflows': [WORKFLOW]}, status_code=201) # The contents of wf_v2.yaml must be identical to WF_DEF path = pkg.resource_filename( 'mistralclient', 'tests/unit/resources/wf_v2.yaml' ) wfs = self.workflows.create(path) self.assertIsNotNone(wfs) self.assertEqual(WF_DEF, wfs[0].definition) last_request = self.requests_mock.last_request self.assertEqual(WF_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_update(self): self.requests_mock.put(self.TEST_URL + URL_TEMPLATE_SCOPE, json={'workflows': [WORKFLOW]}) wfs = self.workflows.update(WF_DEF) self.assertIsNotNone(wfs) self.assertEqual(WF_DEF, wfs[0].definition) last_request = self.requests_mock.last_request self.assertEqual(WF_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_update_with_id(self): self.requests_mock.put(self.TEST_URL + URL_TEMPLATE_NAME % '123', json=WORKFLOW) wf = self.workflows.update(WF_DEF, id='123') self.assertIsNotNone(wf) self.assertEqual(WF_DEF, wf.definition) last_request = self.requests_mock.last_request self.assertEqual('namespace=&scope=private', last_request.query) self.assertEqual(WF_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_update_with_file_uri(self): self.requests_mock.put(self.TEST_URL + URL_TEMPLATE_SCOPE, json={'workflows': [WORKFLOW]}) # The contents of wf_v2.yaml must be identical to WF_DEF path = pkg.resource_filename( 'mistralclient', 'tests/unit/resources/wf_v2.yaml' ) path = os.path.abspath(path) # Convert the file path to file URI uri = parse.urljoin('file:', request.pathname2url(path)) wfs = self.workflows.update(uri) self.assertIsNotNone(wfs) self.assertEqual(WF_DEF, wfs[0].definition) last_request = self.requests_mock.last_request self.assertEqual(WF_DEF, last_request.text) self.assertEqual('text/plain', last_request.headers['content-type']) def test_list(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'workflows': [WORKFLOW]}) workflows_list = self.workflows.list() self.assertEqual(1, len(workflows_list)) wf = workflows_list[0] self.assertEqual( workflows.Workflow(self.workflows, WORKFLOW).to_dict(), wf.to_dict() ) def test_list_with_pagination(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'workflows': [WORKFLOW], 'next': '/workflows?fake'}) workflows_list = self.workflows.list( limit=1, sort_keys='created_at', sort_dirs='asc' ) self.assertEqual(1, len(workflows_list)) last_request = self.requests_mock.last_request # The url param order is unpredictable. self.assertEqual(['1'], last_request.qs['limit']) self.assertEqual(['created_at'], last_request.qs['sort_keys']) self.assertEqual(['asc'], last_request.qs['sort_dirs']) def test_list_with_no_limit(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'workflows': [WORKFLOW]}) workflows_list = self.workflows.list(limit=-1) self.assertEqual(1, len(workflows_list)) last_request = self.requests_mock.last_request self.assertNotIn('limit', last_request.qs) def test_get(self): url = self.TEST_URL + URL_TEMPLATE_NAME % 'wf' self.requests_mock.get(url, json=WORKFLOW) wf = self.workflows.get('wf') self.assertIsNotNone(wf) self.assertEqual( workflows.Workflow(self.workflows, WORKFLOW).to_dict(), wf.to_dict() ) def test_delete(self): url = self.TEST_URL + URL_TEMPLATE_NAME % 'wf' self.requests_mock.delete(url, status_code=204) self.workflows.delete('wf') python-mistralclient-3.7.0/mistralclient/tests/unit/v2/test_cli_members.py0000666000175000017500000000602313326122347027156 0ustar zuulzuul00000000000000# Copyright 2016 Catalyst IT Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import mock from mistralclient.api.v2 import members from mistralclient.commands.v2 import members as member_cmd from mistralclient.tests.unit import base MEMBER_DICT = { 'id': '123', 'resource_id': '456', 'resource_type': 'workflow', 'project_id': '1111', 'member_id': '2222', 'status': 'pending', 'created_at': '1', 'updated_at': '1' } MEMBER = members.Member(mock, MEMBER_DICT) class TestCLIWorkflowMembers(base.BaseCommandTest): def test_create(self): self.client.members.create.return_value = MEMBER result = self.call( member_cmd.Create, app_args=[MEMBER_DICT['resource_id'], MEMBER_DICT['resource_type'], MEMBER_DICT['member_id']] ) self.assertEqual( ('456', 'workflow', '1111', '2222', 'pending', '1', '1'), result[1] ) def test_update(self): self.client.members.update.return_value = MEMBER result = self.call( member_cmd.Update, app_args=[MEMBER_DICT['resource_id'], MEMBER_DICT['resource_type'], '-m', MEMBER_DICT['member_id']] ) self.assertEqual( ('456', 'workflow', '1111', '2222', 'pending', '1', '1'), result[1] ) def test_list(self): self.client.members.list.return_value = [MEMBER] result = self.call( member_cmd.List, app_args=[MEMBER_DICT['resource_id'], MEMBER_DICT['resource_type']] ) self.assertListEqual( [('456', 'workflow', '1111', '2222', 'pending', '1', '1')], result[1] ) def test_get(self): self.client.members.get.return_value = MEMBER result = self.call( member_cmd.Get, app_args=[MEMBER_DICT['resource_id'], MEMBER_DICT['resource_type'], '-m', MEMBER_DICT['member_id']] ) self.assertEqual( ('456', 'workflow', '1111', '2222', 'pending', '1', '1'), result[1] ) def test_delete(self): self.call( member_cmd.Delete, app_args=[MEMBER_DICT['resource_id'], MEMBER_DICT['resource_type'], MEMBER_DICT['member_id']] ) self.client.members.delete.assert_called_once_with( MEMBER_DICT['resource_id'], MEMBER_DICT['resource_type'], MEMBER_DICT['member_id'] ) python-mistralclient-3.7.0/mistralclient/tests/unit/v2/test_cli_tasks.py0000666000175000017500000000766513326122347026666 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, Inc. # All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import json import mock from mistralclient.api.v2 import tasks from mistralclient.commands.v2 import tasks as task_cmd from mistralclient.tests.unit import base TASK_DICT = { 'id': '123', 'name': 'some', 'workflow_name': 'thing', 'workflow_namespace': '', 'workflow_execution_id': '321', 'state': 'RUNNING', 'state_info': None, 'created_at': '1', 'updated_at': '1', } TASK_RESULT = {"test": "is", "passed": "successfully"} TASK_PUBLISHED = {"bar1": "val1", "var2": 2} TASK_WITH_RESULT_DICT = TASK_DICT.copy() TASK_WITH_RESULT_DICT.update({'result': json.dumps(TASK_RESULT)}) TASK_WITH_PUBLISHED_DICT = TASK_DICT.copy() TASK_WITH_PUBLISHED_DICT.update({'published': json.dumps(TASK_PUBLISHED)}) TASK = tasks.Task(mock, TASK_DICT) TASK_WITH_RESULT = tasks.Task(mock, TASK_WITH_RESULT_DICT) TASK_WITH_PUBLISHED = tasks.Task(mock, TASK_WITH_PUBLISHED_DICT) EXPECTED_TASK_RESULT = ( '123', 'some', 'thing', '', '321', 'RUNNING', None, '1', '1' ) class TestCLITasksV2(base.BaseCommandTest): def test_list(self): self.client.tasks.list.return_value = [TASK] result = self.call(task_cmd.List) self.assertEqual([EXPECTED_TASK_RESULT], result[1]) self.assertEqual( self.client.tasks.list.call_args[1]["fields"], task_cmd.TaskFormatter.COLUMN_FIELD_NAMES ) def test_list_with_workflow_execution(self): self.client.tasks.list.return_value = [TASK] result = self.call(task_cmd.List, app_args=['workflow_execution']) self.assertEqual([EXPECTED_TASK_RESULT], result[1]) def test_get(self): self.client.tasks.get.return_value = TASK result = self.call(task_cmd.Get, app_args=['id']) self.assertEqual(EXPECTED_TASK_RESULT, result[1]) def test_get_result(self): self.client.tasks.get.return_value = TASK_WITH_RESULT self.call(task_cmd.GetResult, app_args=['id']) self.assertDictEqual( TASK_RESULT, json.loads(self.app.stdout.write.call_args[0][0]) ) def test_get_published(self): self.client.tasks.get.return_value = TASK_WITH_PUBLISHED self.call(task_cmd.GetPublished, app_args=['id']) self.assertDictEqual( TASK_PUBLISHED, json.loads(self.app.stdout.write.call_args[0][0]) ) def test_rerun(self): self.client.tasks.rerun.return_value = TASK result = self.call(task_cmd.Rerun, app_args=['id']) self.assertEqual(EXPECTED_TASK_RESULT, result[1]) def test_rerun_no_reset(self): self.client.tasks.rerun.return_value = TASK result = self.call(task_cmd.Rerun, app_args=['id', '--resume']) self.assertEqual(EXPECTED_TASK_RESULT, result[1]) def test_rerun_update_env(self): self.client.tasks.rerun.return_value = TASK result = self.call( task_cmd.Rerun, app_args=['id', '--env', '{"k1": "foobar"}'] ) self.assertEqual(EXPECTED_TASK_RESULT, result[1]) def test_rerun_no_reset_update_env(self): self.client.tasks.rerun.return_value = TASK result = self.call( task_cmd.Rerun, app_args=['id', '--resume', '--env', '{"k1": "foobar"}'] ) self.assertEqual(EXPECTED_TASK_RESULT, result[1]) python-mistralclient-3.7.0/mistralclient/tests/unit/v2/test_cli_workflows.py0000666000175000017500000001460613326122347027567 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import mock import six from mistralclient.api.v2 import workflows from mistralclient.commands.v2 import base as cmd_base from mistralclient.commands.v2 import workflows as workflow_cmd from mistralclient.tests.unit import base WORKFLOW_DICT = { 'id': '1-2-3-4', 'name': 'a', 'namespace': '', 'project_id': '12345', 'tags': ['a', 'b'], 'input': 'param', 'scope': 'private', 'created_at': '1', 'updated_at': '1' } WF_DEF = """ version: '2.0' flow: tasks: task1: action: nova.servers_get server="1" """ WF_WITH_DEF_DICT = WORKFLOW_DICT.copy() WF_WITH_DEF_DICT.update({'definition': WF_DEF}) WORKFLOW = workflows.Workflow(mock, WORKFLOW_DICT) WORKFLOW_WITH_DEF = workflows.Workflow(mock, WF_WITH_DEF_DICT) class TestCLIWorkflowsV2(base.BaseCommandTest): @mock.patch('argparse.open', create=True) def test_create(self, mock_open): self.client.workflows.create.return_value = [WORKFLOW] result = self.call(workflow_cmd.Create, app_args=['1.txt']) self.assertEqual( [('1-2-3-4', 'a', '', '12345', 'a, b', 'param', 'private', '1', '1')], result[1] ) @mock.patch('argparse.open', create=True) def test_create_public(self, mock_open): wf_public_dict = WORKFLOW_DICT.copy() wf_public_dict['scope'] = 'public' workflow_public = workflows.Workflow(mock, wf_public_dict) self.client.workflows.create.return_value = [workflow_public] result = self.call( workflow_cmd.Create, app_args=['1.txt', '--public'] ) self.assertEqual( [('1-2-3-4', 'a', '', '12345', 'a, b', 'param', 'public', '1', '1')], result[1] ) self.assertEqual( 'public', self.client.workflows.create.call_args[1]['scope'] ) @mock.patch('argparse.open', create=True) def test_create_long_input(self, mock_open): wf_long_input_dict = WORKFLOW_DICT.copy() long_input = ', '.join( ['var%s' % i for i in six.moves.xrange(10)] ) wf_long_input_dict['input'] = long_input workflow_long_input = workflows.Workflow(mock, wf_long_input_dict) self.client.workflows.create.return_value = [workflow_long_input] result = self.call(workflow_cmd.Create, app_args=['1.txt']) self.assertEqual( [('1-2-3-4', 'a', '', '12345', 'a, b', cmd_base.cut(long_input), 'private', '1', '1')], result[1] ) @mock.patch('argparse.open', create=True) def test_update(self, mock_open): self.client.workflows.update.return_value = [WORKFLOW] result = self.call(workflow_cmd.Update, app_args=['1.txt']) self.assertEqual( [('1-2-3-4', 'a', '', '12345', 'a, b', 'param', 'private', '1', '1')], result[1] ) @mock.patch('argparse.open', create=True) def test_update_public(self, mock_open): self.client.workflows.update.return_value = [WORKFLOW] result = self.call( workflow_cmd.Update, app_args=['1.txt', '--public'] ) self.assertEqual( [('1-2-3-4', 'a', '', '12345', 'a, b', 'param', 'private', '1', '1')], result[1] ) self.assertEqual( 'public', self.client.workflows.update.call_args[1]['scope'] ) @mock.patch('argparse.open', create=True) def test_update_with_id(self, mock_open): self.client.workflows.update.return_value = WORKFLOW result = self.call( workflow_cmd.Update, app_args=['1.txt', '--id', '1-2-3-4'] ) self.assertEqual( [('1-2-3-4', 'a', '', '12345', 'a, b', 'param', 'private', '1', '1')], result[1] ) def test_list(self): self.client.workflows.list.return_value = [WORKFLOW] result = self.call(workflow_cmd.List) self.assertEqual( [('1-2-3-4', 'a', '', '12345', 'a, b', 'param', 'private', '1', '1')], result[1] ) def test_get(self): self.client.workflows.get.return_value = WORKFLOW result = self.call(workflow_cmd.Get, app_args=['name']) self.assertEqual( ('1-2-3-4', 'a', '', '12345', 'a, b', 'param', 'private', '1', '1'), result[1] ) def test_delete(self): self.call(workflow_cmd.Delete, app_args=['name']) self.client.workflows.delete.assert_called_once_with('name', None) def test_delete_with_multi_names(self): self.call(workflow_cmd.Delete, app_args=['name1', 'name2']) self.assertEqual(2, self.client.workflows.delete.call_count) self.assertEqual( [mock.call('name1', None), mock.call('name2', None)], self.client.workflows.delete.call_args_list ) def test_get_definition(self): self.client.workflows.get.return_value = WORKFLOW_WITH_DEF self.call(workflow_cmd.GetDefinition, app_args=['name']) self.app.stdout.write.assert_called_with(WF_DEF) @mock.patch('argparse.open', create=True) def test_validate(self, mock_open): self.client.workflows.validate.return_value = {'valid': True} result = self.call(workflow_cmd.Validate, app_args=['wf.yaml']) self.assertEqual((True, None), result[1]) @mock.patch('argparse.open', create=True) def test_validate_failed(self, mock_open): self.client.workflows.validate.return_value = { 'valid': False, 'error': 'Invalid DSL...' } result = self.call(workflow_cmd.Validate, app_args=['wf.yaml']) self.assertEqual((False, 'Invalid DSL...'), result[1]) python-mistralclient-3.7.0/mistralclient/tests/unit/v2/test_cli_services.py0000666000175000017500000000226213326122347027350 0ustar zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import mock from mistralclient.api.v2 import services from mistralclient.commands.v2 import services as service_cmd from mistralclient.tests.unit import base SERVICE_DICT = { 'name': 'service_name', 'type': 'service_type', } SERVICE = services.Service(mock, SERVICE_DICT) class TestCLIServicesV2(base.BaseCommandTest): def test_list(self): self.client.services.list.return_value = [SERVICE] expected = (SERVICE_DICT['name'], SERVICE_DICT['type'],) result = self.call(service_cmd.List) self.assertListEqual([expected], result[1]) python-mistralclient-3.7.0/mistralclient/tests/unit/v2/test_tasks.py0000666000175000017500000000767513326122347026040 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import json from mistralclient.api.v2 import tasks from mistralclient.tests.unit.v2 import base # TODO(everyone): later we need additional tests verifying all the errors etc. TASK = { 'id': "1", 'workflow_execution_id': '123', 'name': 'my_task', 'workflow_name': 'my_wf', 'state': 'RUNNING', 'tags': ['deployment', 'demo'], 'result': {'some': 'result'} } URL_TEMPLATE = '/tasks' URL_TEMPLATE_ID = '/tasks/%s' class TestTasksV2(base.BaseClientV2Test): def test_list(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'tasks': [TASK]}) task_list = self.tasks.list() self.assertEqual(1, len(task_list)) task = task_list[0] self.assertEqual( tasks.Task(self.tasks, TASK).to_dict(), task.to_dict() ) def test_list_with_fields(self): field_params = "?fields=id,name" self.requests_mock.get(self.TEST_URL + URL_TEMPLATE + field_params, json={'tasks': [TASK]}) self.tasks.list(fields=["id,name"]) self.assertTrue(self.requests_mock.called_once) def test_list_with_no_limit(self): self.requests_mock.get(self.TEST_URL + URL_TEMPLATE, json={'tasks': [TASK]}) task_list = self.tasks.list(limit=-1) self.assertEqual(1, len(task_list)) last_request = self.requests_mock.last_request self.assertNotIn('limit', last_request.qs) def test_get(self): url = self.TEST_URL + URL_TEMPLATE_ID % TASK['id'] self.requests_mock.get(url, json=TASK) task = self.tasks.get(TASK['id']) self.assertEqual( tasks.Task(self.tasks, TASK).to_dict(), task.to_dict() ) def test_rerun(self): url = self.TEST_URL + URL_TEMPLATE_ID % TASK['id'] self.requests_mock.put(url, json=TASK) task = self.tasks.rerun(TASK['id']) self.assertDictEqual( tasks.Task(self.tasks, TASK).to_dict(), task.to_dict() ) body = { 'reset': True, 'state': 'RUNNING', 'id': TASK['id'] } self.assertDictEqual(body, self.requests_mock.last_request.json()) def test_rerun_no_reset(self): url = self.TEST_URL + URL_TEMPLATE_ID % TASK['id'] self.requests_mock.put(url, json=TASK) task = self.tasks.rerun(TASK['id'], reset=False) self.assertDictEqual( tasks.Task(self.tasks, TASK).to_dict(), task.to_dict() ) body = { 'reset': False, 'state': 'RUNNING', 'id': TASK['id'] } self.assertDictEqual(body, self.requests_mock.last_request.json()) def test_rerun_update_env(self): url = self.TEST_URL + URL_TEMPLATE_ID % TASK['id'] self.requests_mock.put(url, json=TASK) task = self.tasks.rerun(TASK['id'], env={'k1': 'foobar'}) self.assertDictEqual( tasks.Task(self.tasks, TASK).to_dict(), task.to_dict() ) body = { 'reset': True, 'state': 'RUNNING', 'id': TASK['id'], 'env': json.dumps({'k1': 'foobar'}) } self.assertDictEqual(body, self.requests_mock.last_request.json()) python-mistralclient-3.7.0/mistralclient/tests/unit/v2/test_cli_environments.py0000666000175000017500000001121413326122347030251 0ustar zuulzuul00000000000000# Copyright 2015 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import copy import datetime import json import os import tempfile import mock import yaml from mistralclient.api.v2 import environments from mistralclient.commands.v2 import environments as environment_cmd from mistralclient.tests.unit import base ENVIRONMENT_DICT = { 'name': 'env1', 'description': 'Test Environment #1', 'scope': 'private', 'variables': { 'server': 'localhost', 'database': 'test', 'timeout': 600, 'verbose': True }, 'created_at': str(datetime.datetime.utcnow()), 'updated_at': str(datetime.datetime.utcnow()) } ENVIRONMENT = environments.Environment(mock, ENVIRONMENT_DICT) EXPECTED_RESULT = (ENVIRONMENT_DICT['name'], ENVIRONMENT_DICT['description'], json.dumps(ENVIRONMENT_DICT['variables'], indent=4), ENVIRONMENT_DICT['scope'], ENVIRONMENT_DICT['created_at'], ENVIRONMENT_DICT['updated_at']) EXPECTED_EXPORT_RESULT = (ENVIRONMENT_DICT['name'], ENVIRONMENT_DICT['description'], ENVIRONMENT_DICT['scope'], json.dumps(ENVIRONMENT_DICT['variables'])) 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_get_with_export(self): self.client.environments.get.return_value = ENVIRONMENT result = self.call(environment_cmd.Get, app_args=['--export', 'name']) self.assertEqual(EXPECTED_EXPORT_RESULT, result[1]) def test_delete(self): self.call(environment_cmd.Delete, app_args=['name']) self.client.environments.delete.assert_called_once_with('name') def test_delete_with_multi_names(self): self.call(environment_cmd.Delete, app_args=['name1', 'name2']) self.assertEqual(2, self.client.environments.delete.call_count) self.assertEqual( [mock.call('name1'), mock.call('name2')], self.client.environments.delete.call_args_list ) python-mistralclient-3.7.0/mistralclient/tests/unit/v2/test_cli_event_triggers.py0000666000175000017500000000623213326122347030555 0ustar zuulzuul00000000000000# Copyright 2014 Mirantis, Inc. # All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import mock from mistralclient.api.v2 import event_triggers from mistralclient.commands.v2 import event_triggers as event_triggers_cmd from mistralclient.tests.unit import base TRIGGER_DICT = { 'id': '456', 'name': 'my_trigger', 'workflow_id': '123e4567-e89b-12d3-a456-426655440000', 'workflow_input': {}, 'workflow_params': {}, 'exchange': 'dummy_exchange', 'topic': 'dummy_topic', 'event': 'event.dummy', 'created_at': '1', 'updated_at': '1' } TRIGGER = event_triggers.EventTrigger(mock, TRIGGER_DICT) class TestCLITriggersV2(base.BaseCommandTest): @mock.patch('argparse.open', create=True) def test_create(self, mock_open): self.client.event_triggers.create.return_value = TRIGGER mock_open.return_value = mock.MagicMock(spec=open) result = self.call( event_triggers_cmd.Create, app_args=['my_trigger', '123e4567-e89b-12d3-a456-426655440000', 'dummy_exchange', 'dummy_topic', 'event.dummy', '--params', '{}'] ) self.assertEqual( ( '456', 'my_trigger', '123e4567-e89b-12d3-a456-426655440000', {}, 'dummy_exchange', 'dummy_topic', 'event.dummy', '1', '1' ), result[1] ) def test_list(self): self.client.event_triggers.list.return_value = [TRIGGER] result = self.call(event_triggers_cmd.List) self.assertEqual( [( '456', 'my_trigger', '123e4567-e89b-12d3-a456-426655440000', {}, 'dummy_exchange', 'dummy_topic', 'event.dummy', '1', '1' )], result[1] ) def test_get(self): self.client.event_triggers.get.return_value = TRIGGER result = self.call(event_triggers_cmd.Get, app_args=['id']) self.assertEqual( ( '456', 'my_trigger', '123e4567-e89b-12d3-a456-426655440000', {}, 'dummy_exchange', 'dummy_topic', 'event.dummy', '1', '1' ), result[1] ) def test_delete(self): self.call(event_triggers_cmd.Delete, app_args=['id']) self.client.event_triggers.delete.assert_called_once_with('id') def test_delete_with_multi_names(self): self.call(event_triggers_cmd.Delete, app_args=['id1', 'id2']) self.assertEqual(2, self.client.event_triggers.delete.call_count) self.assertEqual( [mock.call('id1'), mock.call('id2')], self.client.event_triggers.delete.call_args_list ) python-mistralclient-3.7.0/mistralclient/tests/unit/v2/test_cli_actions.py0000666000175000017500000001362113326122347027166 0ustar zuulzuul00000000000000# Copyright 2014 Mirantis, Inc. # All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import mock import six from mistralclient.api.v2 import actions from mistralclient.commands.v2 import actions as action_cmd from mistralclient.commands.v2 import base as cmd_base from mistralclient.tests.unit import base ACTION_DICT = { 'id': '1234-4567-7894-7895', 'name': 'a', 'is_system': True, 'input': "param1", 'description': 'My cool action', 'tags': ['test'], 'created_at': '1', 'updated_at': '1' } ACTION_DEF = """ --- version: '2.0' base: std.echo base-parameters: output: "<% $.str1 %><% $.str2 %>" output: "<% $ %><% $ %>" """ ACTION_WITH_DEF_DICT = ACTION_DICT.copy() ACTION_WITH_DEF_DICT.update({'definition': ACTION_DEF}) ACTION = actions.Action(mock, ACTION_DICT) ACTION_WITH_DEF = actions.Action(mock, ACTION_WITH_DEF_DICT) class TestCLIActionsV2(base.BaseCommandTest): @mock.patch('argparse.open', create=True) def test_create(self, mock_open): self.client.actions.create.return_value = [ACTION] result = self.call(action_cmd.Create, app_args=['1.txt']) self.assertEqual( [('1234-4567-7894-7895', 'a', True, "param1", 'My cool action', 'test', '1', '1')], result[1] ) @mock.patch('argparse.open', create=True) def test_create_public(self, mock_open): self.client.actions.create.return_value = [ACTION] result = self.call( action_cmd.Create, app_args=['1.txt', '--public'] ) self.assertEqual( [('1234-4567-7894-7895', 'a', True, "param1", 'My cool action', 'test', '1', '1')], result[1] ) self.assertEqual( 'public', self.client.actions.create.call_args[1]['scope'] ) @mock.patch('argparse.open', create=True) def test_create_long_input(self, mock_open): action_long_input_dict = ACTION_DICT.copy() long_input = ', '.join( ['var%s' % i for i in six.moves.xrange(10)] ) action_long_input_dict['input'] = long_input workflow_long_input = actions.Action( mock.Mock(), action_long_input_dict ) self.client.actions.create.return_value = [workflow_long_input] result = self.call(action_cmd.Create, app_args=['1.txt']) self.assertEqual( [('1234-4567-7894-7895', 'a', True, cmd_base.cut(long_input), 'My cool action', 'test', '1', '1')], result[1] ) @mock.patch('argparse.open', create=True) def test_update(self, mock_open): self.client.actions.update.return_value = [ACTION] result = self.call(action_cmd.Update, app_args=['my_action.yaml']) self.assertEqual( [('1234-4567-7894-7895', 'a', True, "param1", 'My cool action', 'test', '1', '1')], result[1] ) @mock.patch('argparse.open', create=True) def test_update_public(self, mock_open): self.client.actions.update.return_value = [ACTION] result = self.call( action_cmd.Update, app_args=['my_action.yaml', '--public'] ) self.assertEqual( [('1234-4567-7894-7895', 'a', True, "param1", 'My cool action', 'test', '1', '1')], result[1] ) self.assertEqual( 'public', self.client.actions.update.call_args[1]['scope'] ) def test_list(self): self.client.actions.list.return_value = [ACTION] result = self.call(action_cmd.List) self.assertEqual( [('1234-4567-7894-7895', 'a', True, "param1", 'My cool action', 'test', '1', '1')], result[1] ) def test_get(self): self.client.actions.get.return_value = ACTION result = self.call(action_cmd.Get, app_args=['name']) self.assertEqual( ('1234-4567-7894-7895', 'a', True, "param1", 'My cool action', 'test', '1', '1'), result[1] ) def test_delete(self): self.call(action_cmd.Delete, app_args=['name']) self.client.actions.delete.assert_called_once_with('name') def test_delete_with_multi_names(self): self.call(action_cmd.Delete, app_args=['name1', 'name2']) self.assertEqual(2, self.client.actions.delete.call_count) self.assertEqual( [mock.call('name1'), mock.call('name2')], self.client.actions.delete.call_args_list ) def test_get_definition(self): self.client.actions.get.return_value = ACTION_WITH_DEF self.call(action_cmd.GetDefinition, app_args=['name']) self.app.stdout.write.assert_called_with(ACTION_DEF) @mock.patch('argparse.open', create=True) def test_validate(self, mock_open): self.client.actions.validate.return_value = {'valid': True} result = self.call(action_cmd.Validate, app_args=['action.yaml']) self.assertEqual((True, None), result[1]) @mock.patch('argparse.open', create=True) def test_validate_failed(self, mock_open): self.client.actions.validate.return_value = { 'valid': False, 'error': 'Invalid DSL...' } result = self.call(action_cmd.Validate, app_args=['action.yaml']) self.assertEqual((False, 'Invalid DSL...'), result[1]) python-mistralclient-3.7.0/mistralclient/tests/unit/v2/__init__.py0000666000175000017500000000000013326122347025362 0ustar zuulzuul00000000000000python-mistralclient-3.7.0/mistralclient/tests/unit/test_shell.py0000666000175000017500000003155413326122347025464 0ustar zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import mock import mistralclient.tests.unit.base_shell_test as base class TestShell(base.BaseShellTests): def test_help(self): """Test that client is not created for help and bash complete""" for command in ('-h', '--help', 'help', 'help workbook-list', 'bash-completion'): with mock.patch('mistralclient.api.client.client') as client_mock: self.shell(command) self.assertFalse(client_mock.called) @mock.patch('mistralclient.api.client.client') def test_command_no_mistral_url(self, client_mock): self.shell( 'workbook-list' ) self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('', params[1]['mistral_url']) @mock.patch('mistralclient.api.client.client') def test_command_with_mistral_url(self, client_mock): self.shell( '--os-mistral-url=http://localhost:8989/v2 workbook-list' ) self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('http://localhost:8989/v2', params[1]['mistral_url']) @mock.patch('mistralclient.api.client.determine_client_version') def test_mistral_version(self, client_mock): self.shell( '--os-mistral-version=v1 workbook-list' ) self.assertTrue(client_mock.called) mistral_version = client_mock.call_args self.assertEqual('v1', mistral_version[0][0]) @mock.patch('mistralclient.api.client.determine_client_version') def test_no_mistral_version(self, client_mock): self.shell('workbook-list') self.assertTrue(client_mock.called) mistral_version = client_mock.call_args self.assertEqual('v2', mistral_version[0][0]) @mock.patch('mistralclient.api.client.client') def test_service_type(self, client_mock): self.shell('--os-mistral-service-type=test workbook-list') self.assertTrue(client_mock.called) parmters = client_mock.call_args self.assertEqual('test', parmters[1]['service_type']) @mock.patch('mistralclient.api.client.client') def test_no_service_type(self, client_mock): self.shell('workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('workflowv2', params[1]['service_type']) @mock.patch('mistralclient.api.client.client') def test_endpoint_type(self, client_mock): self.shell('--os-mistral-endpoint-type=adminURL workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('adminURL', params[1]['endpoint_type']) @mock.patch('mistralclient.api.client.client') def test_no_endpoint_type(self, client_mock): self.shell('workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('publicURL', params[1]['endpoint_type']) @mock.patch('mistralclient.api.client.client') def test_auth_url(self, client_mock): self.shell( '--os-auth-url=https://127.0.0.1:35357/v3 ' '--os-username=admin ' '--os-password=1234 ' 'workbook-list' ) self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('https://127.0.0.1:35357/v3', params[1]['auth_url']) @mock.patch('mistralclient.api.client.client') def test_no_auth_url(self, client_mock): self.shell('workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('', params[1]['auth_url']) @mock.patch('mistralclient.api.client.client') def test_default_auth_url_with_os_password(self, client_mock): self.shell('--os-username=admin --os-password=1234 workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('http://localhost:35357/v3', params[1]['auth_url']) @mock.patch('mistralclient.api.client.client') def test_default_auth_url_with_os_auth_token(self, client_mock): self.shell( '--os-auth-token=abcd1234 ' 'workbook-list' ) self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('http://localhost:35357/v3', params[1]['auth_url']) @mock.patch('mistralclient.api.client.client') def test_profile(self, client_mock): self.shell('--profile=SECRET_HMAC_KEY workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('SECRET_HMAC_KEY', params[1]['profile']) @mock.patch('mistralclient.api.client.client') def test_region_name(self, client_mock): self.shell('--os-region-name=RegionOne workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('RegionOne', params[1]['region_name']) @mock.patch('mistralclient.api.client.client') def test_tenant_id_and_tenant_name(self, client_mock): self.shell( '--os-tenant-id=123tenant --os-tenant-name=fake_tenant' ' workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('fake_tenant', params[1]['project_name']) self.assertEqual('123tenant', params[1]['project_id']) @mock.patch('mistralclient.api.client.client') def test_project_id_and_project_name(self, client_mock): self.shell( '--os-project-name=fake_tenant --os-project-id=123tenant' ' workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('fake_tenant', params[1]['project_name']) self.assertEqual('123tenant', params[1]['project_id']) @mock.patch('mistralclient.api.client.client') def test_project_domain_name(self, client_mock): self.shell('--os-project-domain-name=default workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('default', params[1]['project_domain_name']) @mock.patch('mistralclient.api.client.client') def test_project_domain_id(self, client_mock): self.shell('--os-project-domain-id=default workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('default', params[1]['project_domain_id']) @mock.patch('mistralclient.api.client.client') def test_user_domain_name(self, client_mock): self.shell('--os-user-domain-name=default workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('default', params[1]['user_domain_name']) @mock.patch('mistralclient.api.client.client') def test_user_domain_id(self, client_mock): self.shell('--os-user-domain-id=default workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('default', params[1]['user_domain_id']) @mock.patch('mistralclient.api.client.client') def test_target_user_name_and_password(self, client_mock): self.shell( '--os-target-username=admin' ' --os-target-password=secret_pass workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('admin', params[1]['target_username']) self.assertEqual('secret_pass', params[1]['target_api_key']) @mock.patch('mistralclient.api.client.client') def test_target_tenant_name_and_id(self, client_mock): self.shell( '--os-target-tenant-id=123fake' ' --os-target-tenant-name=fake_target workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('123fake', params[1]['target_project_id']) self.assertEqual('fake_target', params[1]['target_project_name']) @mock.patch('mistralclient.api.client.client') def test_target_user_domain_id(self, client_mock): self.shell('--os-target-user-domain-id=default workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('default', params[1]['target_user_domain_id']) @mock.patch('mistralclient.api.client.client') def test_target_user_domain_name(self, client_mock): self.shell('--os-target-user-domain-name=default workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('default', params[1]['target_user_domain_name']) @mock.patch('mistralclient.api.client.client') def test_target_project_domain_id(self, client_mock): self.shell('--os-target-project-domain-id=default workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('default', params[1]['target_project_domain_id']) @mock.patch('mistralclient.api.client.client') def test_target_project_domain_name(self, client_mock): self.shell('--os-target-project-domain-name=default workbook-list') self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('default', params[1]['target_project_domain_name']) @mock.patch('mistralclient.api.client.client') def test_no_domains_keystone_v3(self, client_mock): self.shell( '--os-auth-url=https://127.0.0.1:35357/v3 ' '--os-username=admin ' '--os-password=1234 ' 'workbook-list' ) self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('https://127.0.0.1:35357/v3', params[1]['auth_url']) # For keystone v3 'default' values are automatically substituted for # project_domain_id and user_domain_id, if nothing was provided self.assertEqual('default', params[1]['project_domain_id']) self.assertEqual('default', params[1]['user_domain_id']) self.assertEqual('default', params[1]['target_project_domain_id']) self.assertEqual('default', params[1]['target_user_domain_id']) @mock.patch('mistralclient.api.client.client') def test_with_domain_names_keystone_v3(self, client_mock): self.shell( '--os-auth-url=https://127.0.0.1:35357/v3 ' '--os-username=admin ' '--os-password=1234 ' '--os-project-domain-name=fake_domain ' '--os-user-domain-name=fake_domain ' '--os-target-project-domain-name=fake_domain ' '--os-target-user-domain-name=fake_domain ' 'workbook-list' ) self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('https://127.0.0.1:35357/v3', params[1]['auth_url']) # No need to substitute values for project_domain_id and # user_domain_id if related domain names were provided self.assertEqual('', params[1]['project_domain_id']) self.assertEqual('', params[1]['user_domain_id']) self.assertEqual('fake_domain', params[1]['project_domain_name']) self.assertEqual('fake_domain', params[1]['user_domain_name']) self.assertEqual( 'fake_domain', params[1]['target_project_domain_name'] ) self.assertEqual('fake_domain', params[1]['target_user_domain_name']) @mock.patch('mistralclient.api.client.client') def test_no_domains_keystone_v2(self, client_mock): self.shell( '--os-auth-url=https://127.0.0.1:35357/v2.0 ' '--os-username=admin ' '--os-password=1234 ' 'workbook-list' ) self.assertTrue(client_mock.called) params = client_mock.call_args self.assertEqual('https://127.0.0.1:35357/v2.0', params[1]['auth_url']) # For keystone v2 nothing is substituted self.assertEqual('', params[1]['project_domain_id']) self.assertEqual('', params[1]['user_domain_id']) self.assertEqual('', params[1]['target_project_domain_id']) self.assertEqual('', params[1]['target_user_domain_id']) python-mistralclient-3.7.0/mistralclient/tests/unit/resources/0000775000175000017500000000000013326122642024742 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/mistralclient/tests/unit/resources/action_v2.yaml0000666000175000017500000000016313326122347027516 0ustar zuulzuul00000000000000 --- version: 2.0 my_action: base: std.echo base-input: output: 'Bye!' output: info: <% $.output %> python-mistralclient-3.7.0/mistralclient/tests/unit/resources/wb_v2.yaml0000666000175000017500000000046313326122347026654 0ustar zuulzuul00000000000000 --- version: 2.0 name: wb workflows: wf1: type: direct input: - param1 - param2 tasks: task1: action: std.http url="localhost:8989" on-success: - test_subsequent test_subsequent: action: std.http url="http://some_url" server_id=1 python-mistralclient-3.7.0/mistralclient/tests/unit/resources/env_v2.yaml0000666000175000017500000000016213326122347027030 0ustar zuulzuul00000000000000--- "name": "env1" "description": "Test Environment #1" "scope": "private" "variables": "server": "localhost"python-mistralclient-3.7.0/mistralclient/tests/unit/resources/wf_v2.yaml0000666000175000017500000000015313326122347026654 0ustar zuulzuul00000000000000 --- version: 2.0 my_wf: type: direct tasks: task1: action: std.echo output="hello, world" python-mistralclient-3.7.0/mistralclient/tests/unit/resources/ctx.json0000666000175000017500000000012413326122347026434 0ustar zuulzuul00000000000000{ "context": { "server": { "name": "name" } } } python-mistralclient-3.7.0/mistralclient/tests/unit/resources/env_v2.json0000666000175000017500000000022013326122347027032 0ustar zuulzuul00000000000000{ "name": "env1", "description": "Test Environment #1", "scope": "private", "variables": { "server": "localhost" } }python-mistralclient-3.7.0/mistralclient/tests/unit/__init__.py0000666000175000017500000000000013326122347025033 0ustar zuulzuul00000000000000python-mistralclient-3.7.0/mistralclient/tests/__init__.py0000666000175000017500000000000013326122347024054 0ustar zuulzuul00000000000000python-mistralclient-3.7.0/mistralclient/exceptions.py0000666000175000017500000000250313326122347023346 0ustar zuulzuul00000000000000# Copyright 2013 - Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. class MistralClientException(Exception): """Base Exception for Mistral client To correctly use this class, inherit from it and define a 'message' and 'code' properties. """ message = "An unknown exception occurred" code = "UNKNOWN_EXCEPTION" def __str__(self): return self.message def __init__(self, message=message): self.message = message super(MistralClientException, self).__init__( '%s: %s' % (self.code, self.message)) class IllegalArgumentException(MistralClientException): message = "IllegalArgumentException occurred" code = "ILLEGAL_ARGUMENT_EXCEPTION" def __init__(self, message=None): if message: self.message = message python-mistralclient-3.7.0/mistralclient/api/0000775000175000017500000000000013326122642021360 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/mistralclient/api/httpclient.py0000666000175000017500000001517613326122347024126 0ustar zuulzuul00000000000000# Copyright 2013 - Mirantis, Inc. # Copyright 2016 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import base64 import copy import logging import os from oslo_utils import importutils import requests AUTH_TOKEN = 'auth_token' SESSION = 'session' CACERT = 'cacert' CERT_FILE = 'cert' CERT_KEY = 'key' INSECURE = 'insecure' PROJECT_ID = 'project_id' USER_ID = 'user_id' REGION_NAME = 'region_name' TARGET_AUTH_TOKEN = 'target_auth_token' TARGET_SESSION = 'target_session' TARGET_AUTH_URI = 'target_auth_url' TARGET_PROJECT_ID = 'target_project_id' TARGET_USER_ID = 'target_user_id' TARGET_INSECURE = 'target_insecure' TARGET_SERVICE_CATALOG = 'target_service_catalog' TARGET_REGION_NAME = 'target_region_name' TARGET_USER_DOMAIN_NAME = 'target_user_domain_name' TARGET_PROJECT_DOMAIN_NAME = 'target_project_domain_name' osprofiler_web = importutils.try_import("osprofiler.web") LOG = logging.getLogger(__name__) def log_request(func): def decorator(self, *args, **kwargs): resp = func(self, *args, **kwargs) LOG.debug("HTTP %s %s %d", resp.request.method, resp.url, resp.status_code) return resp return decorator class HTTPClient(object): def __init__(self, base_url, **kwargs): self.base_url = base_url self.session = kwargs.get('session') if not self.session: self.session = requests.Session() self.auth_token = kwargs.get(AUTH_TOKEN) self.project_id = kwargs.get(PROJECT_ID) self.user_id = kwargs.get(USER_ID) self.cacert = kwargs.get(CACERT) self.insecure = kwargs.get(INSECURE, False) self.region_name = kwargs.get(REGION_NAME) self.ssl_options = {} self.target_session = kwargs.get(TARGET_SESSION) self.target_auth_token = kwargs.get(TARGET_AUTH_TOKEN) self.target_auth_uri = kwargs.get(TARGET_AUTH_URI) self.target_user_id = kwargs.get(TARGET_USER_ID) self.target_project_id = kwargs.get(TARGET_PROJECT_ID) self.target_service_catalog = kwargs.get(TARGET_SERVICE_CATALOG) self.target_region_name = kwargs.get(TARGET_REGION_NAME) self.target_insecure = kwargs.get(TARGET_INSECURE) self.target_user_domain_name = kwargs.get(TARGET_USER_DOMAIN_NAME) self.target_project_domain_name = kwargs.get( TARGET_PROJECT_DOMAIN_NAME ) if self.base_url.startswith('https'): if self.cacert and not os.path.exists(self.cacert): raise ValueError('Unable to locate cacert file ' 'at %s.' % self.cacert) if self.cacert and self.insecure: LOG.warning('Client is set to not verify even though ' 'cacert is provided.') if self.insecure: self.ssl_options['verify'] = False else: if self.cacert: self.ssl_options['verify'] = self.cacert else: self.ssl_options['verify'] = True self.ssl_options['cert'] = ( kwargs.get(CERT_FILE), kwargs.get(CERT_KEY) ) @log_request def get(self, url, headers=None): options = self._get_request_options('get', headers) return self.session.get(self.base_url + url, **options) @log_request def post(self, url, body, headers=None): options = self._get_request_options('post', headers) return self.session.post(self.base_url + url, data=body, **options) @log_request def put(self, url, body, headers=None): options = self._get_request_options('put', headers) return self.session.put(self.base_url + url, data=body, **options) @log_request def delete(self, url, headers=None): options = self._get_request_options('delete', headers) return self.session.delete(self.base_url + url, **options) def _get_request_options(self, method, headers): headers = self._update_headers(headers) if method in ['post', 'put']: content_type = headers.get('content-type', 'application/json') headers['content-type'] = content_type options = copy.deepcopy(self.ssl_options) options['headers'] = headers return options def _update_headers(self, headers): if not headers: headers = {} if isinstance(self.session, requests.Session): if self.auth_token: headers['X-Auth-Token'] = self.auth_token if self.project_id: headers['X-Project-Id'] = self.project_id if self.user_id: headers['X-User-Id'] = self.user_id if self.region_name: headers['X-Region-Name'] = self.region_name if self.target_auth_token: headers['X-Target-Auth-Token'] = self.target_auth_token if self.target_auth_uri: headers['X-Target-Auth-Uri'] = self.target_auth_uri if self.target_project_id: headers['X-Target-Project-Id'] = self.target_project_id if self.target_user_id: headers['X-Target-User-Id'] = self.target_user_id if self.target_insecure: # Note(akovi): due to changes in requests, this parameter # must be a string. Basically, it is a truthy value on # the server side. headers['X-Target-Insecure'] = str(self.target_insecure) if self.target_region_name: headers['X-Target-Region-Name'] = self.target_region_name if self.target_user_domain_name: headers['X-Target-User-Domain-Name'] = self.target_user_domain_name if self.target_project_domain_name: h_name = 'X-Target-Project-Domain-Name' headers[h_name] = self.target_project_domain_name if self.target_service_catalog: headers['X-Target-Service-Catalog'] = base64.b64encode( self.target_service_catalog.encode('utf-8') ) if osprofiler_web: # Add headers for osprofiler. headers.update(osprofiler_web.get_trace_id_headers()) return headers python-mistralclient-3.7.0/mistralclient/api/releasenotes/0000775000175000017500000000000013326122642024051 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/mistralclient/api/releasenotes/notes/0000775000175000017500000000000013326122642025201 5ustar zuulzuul00000000000000././@LongLink0000000000000000000000000000015500000000000011216 Lustar 00000000000000python-mistralclient-3.7.0/mistralclient/api/releasenotes/notes/fix-cli-error-messages-2f59329557a5734f.yamlpython-mistralclient-3.7.0/mistralclient/api/releasenotes/notes/fix-cli-error-messages-2f59329557a570000666000175000017500000000047413326122347033123 0ustar zuulzuul00000000000000--- fixes: - | Some cli mistral calls when failed would return unhelpful messages like error(400) that is not what the mistral api is returning. This changes those messages to the useful messages sent by the api. The affected are the run-action, the crud for actions, workflows, and workbooks. python-mistralclient-3.7.0/mistralclient/api/client.py0000666000175000017500000000165113326122347023217 0ustar zuulzuul00000000000000# Copyright 2013 - Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from mistralclient.api.v2 import client as client_v2 def client(auth_type='keystone', **kwargs): return client_v2.Client(auth_type=auth_type, **kwargs) def determine_client_version(mistral_version): if mistral_version.find("v2") != -1: return 2 raise RuntimeError("Cannot determine mistral API version") python-mistralclient-3.7.0/mistralclient/api/base.py0000666000175000017500000001277413326122347022663 0ustar zuulzuul00000000000000# Copyright 2013 - Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import copy import json from keystoneauth1 import exceptions class Resource(object): resource_name = 'Something' defaults = {} def __init__(self, manager, data): self.manager = manager self._data = data self._set_defaults() self._set_attributes() def _set_defaults(self): for k, v in self.defaults.items(): if k not in self._data: self._data[k] = v def _set_attributes(self): for k, v in self._data.items(): try: setattr(self, k, v) except AttributeError: # In this case we already defined the attribute on the class pass def to_dict(self): return copy.deepcopy(self._data) def __str__(self): vals = ", ".join(["%s='%s'" % (n, v) for n, v in self._data.items()]) return "%s [%s]" % (self.resource_name, vals) def _check_items(obj, searches): try: return all(getattr(obj, attr) == value for (attr, value) in searches) except AttributeError: return False def extract_json(response, response_key): if response_key is not None: return get_json(response)[response_key] else: return get_json(response) class ResourceManager(object): resource_class = None def __init__(self, http_client): self.http_client = http_client def find(self, **kwargs): return [i for i in self.list() if _check_items(i, kwargs.items())] def _ensure_not_empty(self, **kwargs): for name, value in kwargs.items(): if value is None or (isinstance(value, str) and len(value) == 0): raise APIException( 400, '%s is missing field "%s"' % (self.resource_class.__name__, name) ) def _copy_if_defined(self, data, **kwargs): for name, value in kwargs.items(): if value is not None: data[name] = value def _create(self, url, data, response_key=None, dump_json=True): if dump_json: data = json.dumps(data) try: resp = self.http_client.post(url, data) except exceptions.HttpError as ex: self._raise_api_exception(ex.response) if resp.status_code != 201: self._raise_api_exception(resp) return self.resource_class(self, extract_json(resp, response_key)) def _update(self, url, data, response_key=None, dump_json=True): if dump_json: data = json.dumps(data) try: resp = self.http_client.put(url, data) except exceptions.HttpError as ex: self._raise_api_exception(ex.response) if resp.status_code != 200: self._raise_api_exception(resp) return self.resource_class(self, extract_json(resp, response_key)) def _list(self, url, response_key=None): try: resp = self.http_client.get(url) except exceptions.HttpError as ex: self._raise_api_exception(ex.response) if resp.status_code != 200: self._raise_api_exception(resp) return [self.resource_class(self, resource_data) for resource_data in extract_json(resp, response_key)] def _get(self, url, response_key=None): try: resp = self.http_client.get(url) except exceptions.HttpError as ex: self._raise_api_exception(ex.response) if resp.status_code == 200: return self.resource_class(self, extract_json(resp, response_key)) else: self._raise_api_exception(resp) def _delete(self, url): try: resp = self.http_client.delete(url) except exceptions.HttpError as ex: self._raise_api_exception(ex.response) if resp.status_code != 204: self._raise_api_exception(resp) def _plurify_resource_name(self): return self.resource_class.resource_name + 's' def _raise_api_exception(self, resp): try: error_data = (resp.headers.get("Server-Error-Message", None) or get_json(resp).get("faultstring")) except ValueError: error_data = resp.content raise APIException(error_code=resp.status_code, error_message=error_data) def get_json(response): """Gets JSON representation of response. This method provided backward compatibility with old versions of requests library. """ json_field_or_function = getattr(response, 'json', None) if callable(json_field_or_function): return response.json() else: return json.loads(response.content) class APIException(Exception): def __init__(self, error_code=None, error_message=None): super(APIException, self).__init__(error_message) self.error_code = error_code self.error_message = error_message python-mistralclient-3.7.0/mistralclient/api/v2/0000775000175000017500000000000013326122642021707 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/mistralclient/api/v2/actions.py0000666000175000017500000001004213326122347023722 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import six from keystoneauth1 import exceptions 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) try: resp = self.http_client.post( '/actions?scope=%s' % scope, definition, headers={'content-type': 'text/plain'} ) except exceptions.HttpError as ex: self._raise_api_exception(ex.response) if resp.status_code != 201: self._raise_api_exception(resp) return [self.resource_class(self, 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) try: resp = self.http_client.put( '%s?scope=%s' % (url_pre, scope), definition, headers={'content-type': 'text/plain'} ) except exceptions.HttpError as ex: self._raise_api_exception(ex.response) if resp.status_code != 200: self._raise_api_exception(resp) return [self.resource_class(self, resource_data) for resource_data in base.extract_json(resp, 'actions')] def list(self, marker='', limit=None, sort_keys='', sort_dirs='', **filters): qparams = {} if marker: qparams['marker'] = marker if limit and limit > 0: qparams['limit'] = limit if sort_keys: qparams['sort_keys'] = sort_keys if sort_dirs: qparams['sort_dirs'] = sort_dirs for name, val in filters.items(): qparams[name] = val query_string = ("?%s" % urlparse.urlencode(list(qparams.items())) if qparams else "") return self._list( '/actions%s' % query_string, response_key='actions', ) def get(self, identifier): self._ensure_not_empty(identifier=identifier) return self._get('/actions/%s' % identifier) def delete(self, identifier): self._ensure_not_empty(identifier=identifier) self._delete('/actions/%s' % identifier) def validate(self, definition): self._ensure_not_empty(definition=definition) # If the specified definition is actually a file, read in the # definition file definition = utils.get_contents_if_file(definition) try: resp = self.http_client.post( '/actions/validate', definition, headers={'content-type': 'text/plain'} ) except exceptions.HttpError as ex: self._raise_api_exception(ex.response) if resp.status_code != 200: self._raise_api_exception(resp) return base.extract_json(resp, None) python-mistralclient-3.7.0/mistralclient/api/v2/client.py0000666000175000017500000000706413326122347023552 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import copy import six from oslo_utils import importutils from mistralclient.api import httpclient from mistralclient.api.v2 import action_executions from mistralclient.api.v2 import actions from mistralclient.api.v2 import cron_triggers from mistralclient.api.v2 import environments from mistralclient.api.v2 import event_triggers from mistralclient.api.v2 import executions from mistralclient.api.v2 import members from mistralclient.api.v2 import services from mistralclient.api.v2 import tasks from mistralclient.api.v2 import workbooks from mistralclient.api.v2 import workflows from mistralclient import auth osprofiler_profiler = importutils.try_import("osprofiler.profiler") _DEFAULT_MISTRAL_URL = "http://localhost:8989/v2" class Client(object): def __init__(self, auth_type='keystone', **kwargs): # We get the session at this point, as some instances of session # objects might have mutexes that can't be deep-copied. session = kwargs.pop('session', None) req = copy.deepcopy(kwargs) mistral_url = req.get('mistral_url') profile = req.get('profile') if mistral_url and not isinstance(mistral_url, six.string_types): raise RuntimeError('Mistral url should be a string.') # If auth url was provided then we perform an authentication, otherwise # just ignore this step if req.get('auth_url') or req.get('target_auth_url'): auth_handler = auth.get_auth_handler(auth_type) auth_response = auth_handler.authenticate(req, session=session) else: auth_response = {} if session is None: # If the session was None and we're using keystone auth, it will be # created by the auth_handler. session = auth_response.pop('session', None) req.update(auth_response) mistral_url = auth_response.get('mistral_url') or mistral_url if not mistral_url: mistral_url = _DEFAULT_MISTRAL_URL if profile: osprofiler_profiler.init(profile) http_client = httpclient.HTTPClient(mistral_url, session=session, **req) # Create all resource managers. self.workbooks = workbooks.WorkbookManager(http_client) self.executions = executions.ExecutionManager(http_client) self.tasks = tasks.TaskManager(http_client) self.actions = actions.ActionManager(http_client) self.workflows = workflows.WorkflowManager(http_client) self.cron_triggers = cron_triggers.CronTriggerManager(http_client) self.event_triggers = event_triggers.EventTriggerManager(http_client) self.environments = environments.EnvironmentManager(http_client) self.action_executions = action_executions.ActionExecutionManager( http_client) self.services = services.ServiceManager(http_client) self.members = members.MemberManager(http_client) python-mistralclient-3.7.0/mistralclient/api/v2/workflows.py0000666000175000017500000001101713326122347024322 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import six from keystoneauth1 import exceptions from mistralclient.api import base from mistralclient import utils urlparse = six.moves.urllib.parse class Workflow(base.Resource): resource_name = 'Workflow' class WorkflowManager(base.ResourceManager): resource_class = Workflow def create(self, definition, namespace='', scope='private'): self._ensure_not_empty(definition=definition) # If the specified definition is actually a file, read in the # definition file definition = utils.get_contents_if_file(definition) try: resp = self.http_client.post( '/workflows?scope=%s&namespace=%s' % (scope, namespace), definition, headers={'content-type': 'text/plain'} ) except exceptions.HttpError as ex: self._raise_api_exception(ex.response) if resp.status_code != 201: self._raise_api_exception(resp) return [self.resource_class(self, resource_data) for resource_data in base.extract_json(resp, 'workflows')] def update(self, definition, namespace='', scope='private', id=None): self._ensure_not_empty(definition=definition) url_pre = ('/workflows/%s' % id) if id else '/workflows' # If the specified definition is actually a file, read in the # definition file definition = utils.get_contents_if_file(definition) try: resp = self.http_client.put( '%s?namespace=%s&scope=%s' % (url_pre, namespace, scope), definition, headers={'content-type': 'text/plain'} ) except exceptions.HttpError as ex: self._raise_api_exception(ex.response) if resp.status_code != 200: self._raise_api_exception(resp) if id: return self.resource_class(self, base.extract_json(resp, None)) return [self.resource_class(self, resource_data) for resource_data in base.extract_json(resp, 'workflows')] def list(self, namespace='', marker='', limit=None, sort_keys='', sort_dirs='', **filters): qparams = {} if namespace: qparams['namespace'] = namespace if marker: qparams['marker'] = marker if limit and limit > 0: qparams['limit'] = limit if sort_keys: qparams['sort_keys'] = sort_keys if sort_dirs: qparams['sort_dirs'] = sort_dirs for name, val in filters.items(): qparams[name] = val query_string = ("?%s" % urlparse.urlencode(list(qparams.items())) if qparams else "") return self._list( '/workflows%s' % query_string, response_key='workflows', ) def get(self, identifier, namespace=''): self._ensure_not_empty(identifier=identifier) return self._get( '/workflows/%s?namespace=%s' % (identifier, namespace) ) def delete(self, identifier, namespace=None): self._ensure_not_empty(identifier=identifier) path = '/workflows/%s' % identifier if namespace: path = path + '?namespace=%s' % namespace self._delete(path) def validate(self, definition): self._ensure_not_empty(definition=definition) # If the specified definition is actually a file, read in the # definition file definition = utils.get_contents_if_file(definition) try: resp = self.http_client.post( '/workflows/validate', definition, headers={'content-type': 'text/plain'} ) except exceptions.HttpError as ex: self._raise_api_exception(ex.response) if resp.status_code != 200: self._raise_api_exception(resp) return base.extract_json(resp, None) python-mistralclient-3.7.0/mistralclient/api/v2/environments.py0000666000175000017500000000552213326122347025020 0ustar zuulzuul00000000000000# Copyright 2015 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import json import six from mistralclient.api import base from mistralclient import utils class Environment(base.Resource): resource_name = 'Environment' def _set_attributes(self): """Override loading of the "variables" attribute from text to dict.""" for k, v in self._data.items(): if k == 'variables' and isinstance(v, six.string_types): v = json.loads(v) try: setattr(self, k, v) except AttributeError: # In this case we already defined the attribute on the class pass class EnvironmentManager(base.ResourceManager): resource_class = Environment def create(self, **kwargs): # Check to see if the file name or URI is being passed in. If so, # read it's contents first. if 'file' in kwargs: file = kwargs['file'] kwargs = utils.load_content(utils.get_contents_if_file(file)) self._ensure_not_empty(name=kwargs.get('name', None), variables=kwargs.get('variables', None)) # Convert dict to text for the variables attribute. if isinstance(kwargs['variables'], dict): kwargs['variables'] = json.dumps(kwargs['variables']) return self._create('/environments', kwargs) def update(self, **kwargs): # Check to see if the file name or URI is being passed in. If so, # read it's contents first. if 'file' in kwargs: file = kwargs['file'] kwargs = utils.load_content(utils.get_contents_if_file(file)) name = kwargs.get('name', None) self._ensure_not_empty(name=name) # Convert dict to text for the variables attribute. if kwargs.get('variables') and isinstance(kwargs['variables'], dict): kwargs['variables'] = json.dumps(kwargs['variables']) return self._update('/environments', kwargs) def list(self): return self._list('/environments', response_key='environments') def get(self, name): self._ensure_not_empty(name=name) return self._get('/environments/%s' % name) def delete(self, name): self._ensure_not_empty(name=name) self._delete('/environments/%s' % name) python-mistralclient-3.7.0/mistralclient/api/v2/cron_triggers.py0000666000175000017500000000405513326122347025140 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import json from oslo_utils import uuidutils from mistralclient.api import base class CronTrigger(base.Resource): resource_name = 'CronTrigger' class CronTriggerManager(base.ResourceManager): resource_class = CronTrigger def create(self, name, workflow_identifier, workflow_input=None, workflow_params=None, pattern=None, first_time=None, count=None): self._ensure_not_empty( name=name, workflow_identifier=workflow_identifier ) data = { 'name': name, 'pattern': pattern, 'first_execution_time': first_time, 'remaining_executions': count } if uuidutils.is_uuid_like(workflow_identifier): data.update({'workflow_id': workflow_identifier}) else: data.update({'workflow_name': workflow_identifier}) if workflow_input: data.update({'workflow_input': json.dumps(workflow_input)}) if workflow_params: data.update({'workflow_params': json.dumps(workflow_params)}) return self._create('/cron_triggers', data) def list(self): return self._list('/cron_triggers', response_key='cron_triggers') def get(self, name): self._ensure_not_empty(name=name) return self._get('/cron_triggers/%s' % name) def delete(self, name): self._ensure_not_empty(name=name) self._delete('/cron_triggers/%s' % name) python-mistralclient-3.7.0/mistralclient/api/v2/services.py0000666000175000017500000000155613326122347024117 0ustar zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from mistralclient.api import base class Service(base.Resource): resource_name = 'Service' class ServiceManager(base.ResourceManager): resource_class = Service def list(self): return self._list('/services', response_key='services') python-mistralclient-3.7.0/mistralclient/api/v2/members.py0000666000175000017500000000443313326122347023723 0ustar zuulzuul00000000000000# Copyright 2016 - Catalyst IT Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from mistralclient.api import base class Member(base.Resource): resource_name = 'Member' class MemberManager(base.ResourceManager): resource_class = Member def create(self, resource_id, resource_type, member_id): self._ensure_not_empty( resource_id=resource_id, resource_type=resource_type, member_id=member_id ) data = { 'member_id': member_id, } url = '/%ss/%s/members' % (resource_type, resource_id) return self._create(url, data) def update(self, resource_id, resource_type, member_id='', status='accepted'): if not member_id: member_id = self.http_client.project_id url = '/%ss/%s/members/%s' % (resource_type, resource_id, member_id) return self._update(url, {'status': status}) def list(self, resource_id, resource_type): url = '/%ss/%s/members' % (resource_type, resource_id) return self._list(url, response_key='members') def get(self, resource_id, resource_type, member_id=None): self._ensure_not_empty( resource_id=resource_id, resource_type=resource_type, ) if not member_id: member_id = self.http_client.project_id url = '/%ss/%s/members/%s' % (resource_type, resource_id, member_id) return self._get(url) def delete(self, resource_id, resource_type, member_id): self._ensure_not_empty( resource_id=resource_id, resource_type=resource_type, member_id=member_id ) url = '/%ss/%s/members/%s' % (resource_type, resource_id, member_id) self._delete(url) python-mistralclient-3.7.0/mistralclient/api/v2/tasks.py0000666000175000017500000000420213326122347023410 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import json import six from mistralclient.api import base urlparse = six.moves.urllib.parse class Task(base.Resource): resource_name = 'Task' class TaskManager(base.ResourceManager): resource_class = Task def list(self, workflow_execution_id=None, marker='', limit=None, sort_keys='', sort_dirs='', fields=[], **filters): url = '/tasks' if workflow_execution_id: url = '/executions/%s/tasks' % workflow_execution_id url += '%s' qparams = {} if marker: qparams['marker'] = marker if limit and limit > 0: qparams['limit'] = limit if sort_keys: qparams['sort_keys'] = sort_keys if sort_dirs: qparams['sort_dirs'] = sort_dirs if fields: qparams['fields'] = ",".join(fields) for name, val in filters.items(): qparams[name] = val query_string = ("?%s" % urlparse.urlencode(list(qparams.items())) if qparams else "") return self._list(url % query_string, response_key='tasks') def get(self, id): self._ensure_not_empty(id=id) return self._get('/tasks/%s' % id) def rerun(self, task_ex_id, reset=True, env=None): url = '/tasks/%s' % task_ex_id body = { 'id': task_ex_id, 'state': 'RUNNING', 'reset': reset } if env: body['env'] = json.dumps(env) return self._update(url, body) python-mistralclient-3.7.0/mistralclient/api/v2/executions.py0000666000175000017500000000704113326122347024455 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import json from oslo_utils import uuidutils import six from mistralclient.api import base urlparse = six.moves.urllib.parse class Execution(base.Resource): resource_name = 'Execution' class ExecutionManager(base.ResourceManager): resource_class = Execution def create(self, workflow_identifier='', namespace='', workflow_input=None, description='', source_execution_id=None, **params): ident = workflow_identifier or source_execution_id self._ensure_not_empty(workflow_identifier=ident) data = { 'description': description, } if uuidutils.is_uuid_like(source_execution_id): data.update({'source_execution_id': source_execution_id}) if workflow_identifier: if uuidutils.is_uuid_like(workflow_identifier): data.update({'workflow_id': workflow_identifier}) else: data.update({'workflow_name': workflow_identifier}) if namespace: data.update({'workflow_namespace': namespace}) if workflow_input: if isinstance(workflow_input, six.string_types): data.update({'input': workflow_input}) else: data.update({'input': json.dumps(workflow_input)}) if params: data.update({'params': json.dumps(params)}) return self._create('/executions', data) def update(self, id, state, description=None, env=None): data = {} if state: data['state'] = state if description: data['description'] = description if env: data['params'] = {'env': env} return self._update('/executions/%s' % id, data) def list(self, task=None, marker='', limit=None, sort_keys='', sort_dirs='', **filters): qparams = {} if task: qparams['task_execution_id'] = task if marker: qparams['marker'] = marker if limit and limit > 0: qparams['limit'] = limit if sort_keys: qparams['sort_keys'] = sort_keys if sort_dirs: qparams['sort_dirs'] = sort_dirs for name, val in filters.items(): qparams[name] = val query_string = ("?%s" % urlparse.urlencode(list(qparams.items())) if qparams else "") return self._list( '/executions%s' % query_string, response_key='executions', ) def get(self, id): self._ensure_not_empty(id=id) return self._get('/executions/%s' % id) def delete(self, id, force=None): self._ensure_not_empty(id=id) qparams = {} if force: qparams['force'] = True query_string = ("?%s" % urlparse.urlencode(list(qparams.items())) if qparams else "") self._delete('/executions/%s%s' % (id, query_string)) python-mistralclient-3.7.0/mistralclient/api/v2/workbooks.py0000666000175000017500000000633513326122347024314 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from keystoneauth1 import exceptions 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, 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) try: resp = self.http_client.post( '/workbooks?scope=%s' % scope, definition, headers={'content-type': 'text/plain'} ) except exceptions.HttpError as ex: self._raise_api_exception(ex.response) if resp.status_code != 201: self._raise_api_exception(resp) return self.resource_class(self, base.extract_json(resp, None)) def update(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) try: resp = self.http_client.put( '/workbooks?scope=%s' % scope, definition, headers={'content-type': 'text/plain'} ) except exceptions.HttpError as ex: self._raise_api_exception(ex.response) if resp.status_code != 200: self._raise_api_exception(resp) return self.resource_class(self, 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) try: resp = self.http_client.post( '/workbooks/validate', definition, headers={'content-type': 'text/plain'} ) except exceptions.HttpError as ex: self._raise_api_exception(ex.response) if resp.status_code != 200: self._raise_api_exception(resp) return base.extract_json(resp, None) python-mistralclient-3.7.0/mistralclient/api/v2/action_executions.py0000666000175000017500000000462513326122347026017 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import json import six from mistralclient.api import base urlparse = six.moves.urllib.parse class ActionExecution(base.Resource): resource_name = 'ActionExecution' class ActionExecutionManager(base.ResourceManager): resource_class = ActionExecution def create(self, name, input=None, **params): self._ensure_not_empty(name=name) data = {'name': name} if input: data['input'] = json.dumps(input) if params: data['params'] = json.dumps(params) return self._create( '/action_executions', data, dump_json=True ) def update(self, id, state=None, output=None): self._ensure_not_empty(id=id) if not (state or output): raise base.APIException( 400, "Please provide either state or output for action execution." ) data = {} if state: data['state'] = state if output: data['output'] = output return self._update('/action_executions/%s' % id, data) def list(self, task_execution_id=None, limit=None): url = '/action_executions' if task_execution_id: url = '/tasks/%s/action_executions' % task_execution_id url += "%s" qparams = {} if limit and limit > 0: qparams['limit'] = limit query_string = ("?%s" % urlparse.urlencode(list(qparams.items())) if qparams else "") return self._list(url % query_string, response_key='action_executions') def get(self, id): self._ensure_not_empty(id=id) return self._get('/action_executions/%s' % id) def delete(self, id): self._ensure_not_empty(id=id) self._delete('/action_executions/%s' % id) python-mistralclient-3.7.0/mistralclient/api/v2/event_triggers.py0000666000175000017500000000342113326122347025314 0ustar zuulzuul00000000000000# Copyright 2017, OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import json from mistralclient.api import base class EventTrigger(base.Resource): resource_name = 'EventTrigger' class EventTriggerManager(base.ResourceManager): resource_class = EventTrigger def create(self, name, workflow_id, exchange, topic, event, workflow_input=None, workflow_params=None): self._ensure_not_empty( name=name, workflow_id=workflow_id ) data = { 'workflow_id': workflow_id, 'name': name, 'exchange': exchange, 'topic': topic, 'event': event } if workflow_input: data.update({'workflow_input': json.dumps(workflow_input)}) if workflow_params: data.update({'workflow_params': json.dumps(workflow_params)}) return self._create('/event_triggers', data) def list(self): return self._list('/event_triggers', response_key='event_triggers') def get(self, id): self._ensure_not_empty(id=id) return self._get('/event_triggers/%s' % id) def delete(self, id): self._ensure_not_empty(id=id) self._delete('/event_triggers/%s' % id) python-mistralclient-3.7.0/mistralclient/api/v2/__init__.py0000666000175000017500000000000013326122347024012 0ustar zuulzuul00000000000000python-mistralclient-3.7.0/mistralclient/api/__init__.py0000666000175000017500000000000013326122347023463 0ustar zuulzuul00000000000000python-mistralclient-3.7.0/mistralclient/auth/0000775000175000017500000000000013326122642021550 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/mistralclient/auth/auth_types.py0000666000175000017500000000141013326122347024307 0ustar zuulzuul00000000000000# Copyright 2016 - Nokia Networks # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from stevedore import extension # Valid authentication types. ALL = extension.ExtensionManager( namespace='mistralclient.auth', invoke_on_load=False ).names() python-mistralclient-3.7.0/mistralclient/auth/keystone.py0000666000175000017500000002142013326122347023766 0ustar zuulzuul00000000000000# Copyright 2016 - Nokia Networks # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import logging import keystoneauth1.access.service_catalog as sc import keystoneauth1.identity.generic as auth_plugin from keystoneauth1 import session as ks_session import mistralclient.api.httpclient as api from mistralclient import auth as mistral_auth from oslo_serialization import jsonutils LOG = logging.getLogger(__name__) class KeystoneAuthHandler(mistral_auth.AuthHandler): def authenticate(self, req, session=None): """Perform authentication via Keystone. :param req: Request dict containing the parameters required for Keystone authentication. """ reqs = self._separate_target_reqs(req) try: result = self._authenticate(reqs, session) except Exception as e: if "Cannot use v2 authentication with domain scope" in str(e): LOG.warning("Client tried to use v2 authentication with " "domain scope. Domain parameters are assumed " "to be erroneously set. Retrying " "authentication without them. " "Request parameters: %s" % str(reqs)) domainless_reqs = [reqs[0], self._remove_domain(reqs[1])] result = self._authenticate(domainless_reqs, session) else: raise return result @staticmethod def _separate_target_reqs(req): """Separates parameters into target and non-target ones. target_* parameters are rekeyed by removing the prefix. :param req: Request dict containing the parameters for Keystone authentication. :return: list of [non-target, target] request parameters """ r = {} target_r = {} target_prefix = "target_" for key in req: if key.startswith(target_prefix): target_r[key[len(target_prefix):]] = req[key] else: r[key] = req[key] return [r, target_r] @staticmethod def _remove_domain(req): """Remove all domain parameters from req. Keystoneauth with V2 does not accept domain parameters. This is an incompatible change from Keystoneclient but it would unnecessarily break clients of Mistral. It is safe to remove domain parameters if V2 auth is targeted. :param req: Request dict containing list of parameters required :return: Request dict without domains """ r = {} for key in req: if "domain" not in key: r[key] = req[key] return r @staticmethod def _get_auth(api_key=None, auth_token=None, auth_url=None, project_domain_id=None, project_domain_name=None, project_id=None, project_name=None, user_domain_id=None, user_domain_name=None, user_id=None, username=None, **kwargs): if project_name and project_id: raise RuntimeError( 'Only one of project_name or project_id should be set' ) if username and user_id: raise RuntimeError( 'Only one of username or user_id should be set' ) auth = {} if auth_token: auth = auth_plugin.Token( auth_url=auth_url, project_domain_id=project_domain_id, project_domain_name=project_domain_name, project_id=project_id, project_name=project_name, token=auth_token ) elif api_key and (username or user_id): auth = auth_plugin.Password( auth_url=auth_url, password=api_key, project_domain_id=project_domain_id, project_domain_name=project_domain_name, project_id=project_id, project_name=project_name, user_domain_id=user_domain_id, user_domain_name=user_domain_name, user_id=user_id, username=username ) return auth def _authenticate(self, reqs, session=None): """Performs authentication via Keystone. :param reqs: Request dict containing list of parameters required for Keystone authentication. :return: Auth response dict """ if not isinstance(reqs[0], dict): raise TypeError('The input "req" is not typeof dict.') if not isinstance(reqs[1], dict): raise TypeError('The input "req" is not typeof dict.') auth_response = {} req = reqs[0] cacert = req.get('cacert') endpoint_type = req.get('endpoint_type', 'publicURL') insecure = req.get('insecure') mistral_url = req.get('mistral_url') region_name = req.get('region_name') service_type = req.get('service_type', 'workflowv2') verify = self._verification_needed(cacert, insecure) if not session: auth = self._get_auth(**req) if auth: session = ks_session.Session(auth=auth, verify=verify) if session: if not mistral_url: try: mistral_url = session.get_endpoint( service_type=service_type, endpoint_type=endpoint_type, region_name=region_name ) except Exception: mistral_url = None auth_response['mistral_url'] = mistral_url auth_response['session'] = session target_req = reqs[1] if "auth_url" in target_req: target_auth = self._get_auth(**target_req) if target_auth: # target cacert and insecure cacert = target_req.get('cacert') insecure = target_req.get('insecure') verify = self._verification_needed(cacert, insecure) target_session = ks_session.Session( auth=target_auth, verify=verify ) target_auth_headers = target_session.get_auth_headers() or {} target_auth_token = target_auth_headers.get('X-Auth-Token') auth_response.update({ api.TARGET_AUTH_TOKEN: target_auth_token, api.TARGET_PROJECT_ID: target_session.get_project_id(), api.TARGET_USER_ID: target_session.get_user_id(), api.TARGET_AUTH_URI: target_auth._plugin.auth_url, }) access = target_auth.get_access(target_session) service_catalog = access.service_catalog if self._is_service_catalog_v2(service_catalog): access_data = access._data["access"] if not len(access_data['serviceCatalog']): LOG.warning( "Service Catalog empty, some authentication" "credentials may be missing. This can cause" "malfunction in the Mistral action executions.") sc_json = jsonutils.dumps(access_data) auth_response[api.TARGET_SERVICE_CATALOG] = sc_json if not auth_response: LOG.debug("No valid token or password + user provided. " "Continuing without authentication") return {} return auth_response @staticmethod def _verification_needed(cacert, insecure): """Return the verify parameter. The value of verify can be either True/False or a cacert. :param cacert None or path to CA cert file :param insecure truthy value to switch on SSL verification """ if insecure is False or insecure is None: verify = cacert or True else: verify = False return verify @staticmethod def _is_service_catalog_v2(catalog): """Check if the service catalog is of type ServiceCatalogV2 :param catalog: the service catalog :return: True if V2, False otherwise """ return type(catalog) is sc.ServiceCatalogV2 python-mistralclient-3.7.0/mistralclient/auth/keycloak.py0000666000175000017500000001511513326122347023733 0ustar zuulzuul00000000000000# Copyright 2016 - Nokia Networks # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import logging import os import pprint import requests from six.moves import urllib from mistralclient import auth LOG = logging.getLogger(__name__) class KeycloakAuthHandler(auth.AuthHandler): def authenticate(self, req, session=None): """Performs authentication using Keycloak OpenID Protocol. :param req: Request dict containing list of parameters required for Keycloak authentication. * auth_url: Base authentication url of KeyCloak server (e.g. "https://my.keycloak:8443/auth" * client_id: Client ID (according to OpenID Connect protocol). * client_secret: Client secret (according to OpenID Connect protocol). * project_name: KeyCloak realm name. * username: User name (Optional, if None then access_token must be provided). * api_key: Password (Optional). * access_token: Access token. If passed, username and password are not used and this method just validates the token and refreshes it if needed (Optional, if None then username must be provided). * cacert: SSL certificate file (Optional). * insecure: If True, SSL certificate is not verified (Optional). :param session: Keystone session object. Not used by this plugin. """ if not isinstance(req, dict): raise TypeError('The input "req" is not typeof dict.') auth_url = req.get('auth_url') client_id = req.get('client_id') client_secret = req.get('client_secret') realm_name = req.get('project_name') username = req.get('username') password = req.get('api_key') access_token = req.get('access_token') cacert = req.get('cacert') insecure = req.get('insecure', False) if not auth_url: raise ValueError('Base authentication url is not provided.') if not client_id: raise ValueError('Client ID is not provided.') if not realm_name: raise ValueError('Project(realm) name is not provided.') if username and access_token: raise ValueError( "User name and access token can't be " "provided at the same time." ) if not username and not access_token: raise ValueError( 'Either user name or access token must be provided.' ) if access_token: response = self._authenticate_with_token( auth_url, client_id, client_secret, access_token, cacert, insecure ) else: response = self._authenticate_with_password( auth_url, client_id, client_secret, realm_name, username, password, cacert, insecure ) return {'auth_token': response, 'project_id': realm_name} @staticmethod def _authenticate_with_token(auth_url, client_id, client_secret, auth_token, cacert=None, insecure=None): # TODO(rakhmerov): Implement. raise NotImplementedError @staticmethod def _authenticate_with_password(auth_url, client_id, client_secret, realm_name, username, password, cacert=None, insecure=None): access_token_endpoint = ( "%s/realms/%s/protocol/openid-connect/token" % (auth_url, realm_name) ) verify = None if urllib.parse.urlparse(access_token_endpoint).scheme == "https": verify = False if insecure else cacert if cacert else True body = { 'grant_type': 'password', 'username': username, 'password': password, 'client_id': client_id, 'scope': 'profile' } if client_secret: body['client_secret'] = client_secret, resp = requests.post( access_token_endpoint, data=body, verify=verify ) try: resp.raise_for_status() except Exception as e: raise Exception("Failed to get access token:\n %s" % str(e)) LOG.debug("HTTP response from OIDC provider: %s", pprint.pformat(resp.json())) return resp.json()['access_token'] def get_system_ca_file(): """Return path to system default CA file.""" # Standard CA file locations for Debian/Ubuntu, RedHat/Fedora, # Suse, FreeBSD/OpenBSD, MacOSX, and the bundled ca ca_path = ['/etc/ssl/certs/ca-certificates.crt', '/etc/pki/tls/certs/ca-bundle.crt', '/etc/ssl/ca-bundle.pem', '/etc/ssl/cert.pem', '/System/Library/OpenSSL/certs/cacert.pem', requests.certs.where()] for ca in ca_path: LOG.debug("Looking for ca file %s", ca) if os.path.exists(ca): LOG.debug("Using ca file %s", ca) return ca LOG.warning("System ca file could not be found.") # An example of working curl request to keycloak # curl -d "client_id=admin-cli" -d "client_secret=secret" # -d "username=admin" -d "password=qwerty" -d "grant_type=password" # "http://localhost:8080/auth/realms/master/protocol/openid-connect/token" # An example of using KeyCloak OpenID authentication. if __name__ == '__main__': print("Using username/password to get access token from KeyCloak...") auth_handler = KeycloakAuthHandler() a_token = auth_handler.authenticate( dict( "https://my.keycloak:8443/auth", client_id="mistral_client", client_secret="secret", project_name="mistral", username="user", api_key="secret", insecure=True ) )['auth_token'] print("Auth token: %s" % a_token) python-mistralclient-3.7.0/mistralclient/auth/__init__.py0000666000175000017500000000205613326122347023670 0ustar zuulzuul00000000000000# Copyright 2016 - Brocade Communications Systems, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import abc import six from stevedore import driver def get_auth_handler(auth_type): mgr = driver.DriverManager( 'mistralclient.auth', auth_type, invoke_on_load=True ) return mgr.driver @six.add_metaclass(abc.ABCMeta) class AuthHandler(object): """Abstract base class for an authentication plugin.""" @abc.abstractmethod def authenticate(self, req): raise NotImplementedError() python-mistralclient-3.7.0/mistralclient/utils.py0000666000175000017500000000507613326122347022335 0ustar zuulzuul00000000000000# Copyright 2015 - Huawei Technologies Co. Ltd # Copyright 2015 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import json import os import yaml from six.moves.urllib import parse from six.moves.urllib import request from mistralclient import exceptions def do_action_on_many(action, resources, success_msg, error_msg): """Helper to run an action on many resources.""" failure_flag = False for resource in resources: try: action(resource) print(success_msg % resource) except Exception as e: failure_flag = True print(e) if failure_flag: raise exceptions.MistralClientException(error_msg) def load_content(content): if content is None or content == '': return dict() try: data = yaml.safe_load(content) except Exception: data = json.loads(content) return data def load_file(path): with open(path, 'r') as f: return load_content(f.read()) def get_contents_if_file(contents_or_file_name): """Get the contents of a file. If the value passed in is a file name or file URI, return the contents. If not, or there is an error reading the file contents, return the value passed in as the contents. For example, a workflow definition will be returned if either the workflow definition file name, or file URI are passed in, or the actual workflow definition itself is passed in. """ try: if parse.urlparse(contents_or_file_name).scheme: definition_url = contents_or_file_name else: path = os.path.abspath(contents_or_file_name) definition_url = parse.urljoin( 'file:', request.pathname2url(path) ) return request.urlopen(definition_url).read().decode('utf8') except Exception: return contents_or_file_name def load_json(input_string): try: with open(input_string) as fh: return json.load(fh) except IOError: return json.loads(input_string) python-mistralclient-3.7.0/mistralclient/osc/0000775000175000017500000000000013326122642021373 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/mistralclient/osc/plugin.py0000666000175000017500000000354213326122347023253 0ustar zuulzuul00000000000000# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """OpenStackClient plugin for Workflow service.""" import logging from osc_lib import utils LOG = logging.getLogger(__name__) DEFAULT_WORKFLOW_API_VERSION = '2' API_VERSION_OPTION = 'os_workflow_api_version' API_NAME = 'workflow_engine' API_VERSIONS = { '2': 'mistralclient.api.v2.client.Client', } def make_client(instance): """Returns a workflow_engine service client.""" version = instance._api_version[API_NAME] workflow_client = utils.get_client_class( API_NAME, version, API_VERSIONS) LOG.debug('Instantiating workflow engine client: %s', workflow_client) mistral_url = instance.get_endpoint_for_service_type( 'workflowv2', region_name=instance.region_name, interface='publicURL' ) client = workflow_client(mistral_url=mistral_url, session=instance.session) return client def build_option_parser(parser): """Hook to add global options.""" parser.add_argument( '--os-workflow-api-version', metavar='', default=utils.env( 'OS_WORKFLOW_API_VERSION', default=DEFAULT_WORKFLOW_API_VERSION), help='Workflow API version, default=' + DEFAULT_WORKFLOW_API_VERSION + ' (Env: OS_WORKFLOW_API_VERSION)') return parser python-mistralclient-3.7.0/mistralclient/osc/__init__.py0000666000175000017500000000000013326122347023476 0ustar zuulzuul00000000000000python-mistralclient-3.7.0/mistralclient/commands/0000775000175000017500000000000013326122642022410 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/mistralclient/commands/v2/0000775000175000017500000000000013326122642022737 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/mistralclient/commands/v2/actions.py0000666000175000017500000001513713326122347024764 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import argparse from osc_lib.command import command from mistralclient.commands.v2 import base from mistralclient import utils def format_list(action=None): return format(action, lister=True) def format(action=None, lister=False): columns = ( 'ID', 'Name', 'Is system', 'Input', 'Description', 'Tags', 'Created at', 'Updated at' ) if action: tags = getattr(action, 'tags', None) or [] input = action.input if not lister else base.cut(action.input) desc = (action.description if not lister else base.cut(action.description)) data = ( action.id, action.name, action.is_system, input, desc, base.wrap(', '.join(tags)) or '', action.created_at, ) if hasattr(action, 'updated_at'): data += (action.updated_at,) else: data += (None,) else: data = (tuple('' for _ in range(len(columns))),) return columns, data class List(base.MistralLister): """List all actions.""" def _get_format_function(self): return format_list def get_parser(self, prog_name): parser = super(List, self).get_parser(prog_name) parser.add_argument( '--filter', dest='filters', action='append', help='Filters. Can be repeated.' ) return parser def _get_resources(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine return mistral_client.actions.list( **base.get_filters(parsed_args) ) class Get(command.ShowOne): """Show specific action.""" def get_parser(self, prog_name): parser = super(Get, self).get_parser(prog_name) parser.add_argument('action', help='Action (name or ID)') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine action = mistral_client.actions.get(parsed_args.action) return format(action) class Create(base.MistralLister): """Create new action.""" def get_parser(self, prog_name): parser = super(Create, self).get_parser(prog_name) parser.add_argument( 'definition', type=argparse.FileType('r'), help='Action definition file' ) parser.add_argument( '--public', action='store_true', help='With this flag action will be marked as "public".' ) return parser def _validate_parsed_args(self, parsed_args): if not parsed_args.definition: raise RuntimeError("Provide action definition file.") def _get_format_function(self): return format_list def _get_resources(self, parsed_args): scope = 'public' if parsed_args.public else 'private' mistral_client = self.app.client_manager.workflow_engine return mistral_client.actions.create( parsed_args.definition.read(), scope=scope ) class Delete(command.Command): """Delete action.""" def get_parser(self, prog_name): parser = super(Delete, self).get_parser(prog_name) parser.add_argument( 'action', nargs='+', help='Name or ID of action(s).' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine utils.do_action_on_many( lambda s: mistral_client.actions.delete(s), parsed_args.action, "Request to delete action %s has been accepted.", "Unable to delete the specified action(s)." ) class Update(base.MistralLister): """Update action.""" def get_parser(self, prog_name): parser = super(Update, self).get_parser(prog_name) parser.add_argument( 'definition', type=argparse.FileType('r'), help='Action definition file' ) parser.add_argument('--id', help='Action ID.') parser.add_argument( '--public', action='store_true', help='With this flag action will be marked as "public".' ) return parser def _get_format_function(self): return format_list def _get_resources(self, parsed_args): scope = 'public' if parsed_args.public else 'private' mistral_client = self.app.client_manager.workflow_engine return mistral_client.actions.update( parsed_args.definition.read(), scope=scope, id=parsed_args.id ) class GetDefinition(command.Command): """Show action definition.""" def get_parser(self, prog_name): parser = super(GetDefinition, self).get_parser(prog_name) parser.add_argument('name', help='Action name') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine definition = mistral_client.actions.get(parsed_args.name).definition self.app.stdout.write(definition or "\n") class Validate(command.ShowOne): """Validate action.""" def _format(self, result=None): columns = ('Valid', 'Error') if result: data = (result.get('valid'), result.get('error')) else: data = (tuple('' for _ in range(len(columns))),) return columns, data def get_parser(self, prog_name): parser = super(Validate, self).get_parser(prog_name) parser.add_argument( 'definition', type=argparse.FileType('r'), help='action definition file' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine result = mistral_client.actions.validate( parsed_args.definition.read() ) return self._format(result) python-mistralclient-3.7.0/mistralclient/commands/v2/workflows.py0000666000175000017500000001743513326122347025364 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import argparse from cliff import command from cliff import show from mistralclient.commands.v2 import base from mistralclient import utils def format_list(workflow=None): return format(workflow, lister=True) def format(workflow=None, lister=False): columns = ( 'ID', 'Name', 'Namespace', 'Project ID', 'Tags', 'Input', 'Scope', 'Created at', 'Updated at' ) if workflow: tags = getattr(workflow, 'tags', None) or [] data = ( workflow.id, workflow.name, workflow.namespace, workflow.project_id, base.wrap(', '.join(tags)) or '', workflow.input if not lister else base.cut(workflow.input), workflow.scope, workflow.created_at ) if hasattr(workflow, 'updated_at'): data += (workflow.updated_at,) else: data += (None,) else: data = (tuple('' for _ in range(len(columns))),) return columns, data class List(base.MistralLister): """List all workflows.""" def _get_format_function(self): return format_list def get_parser(self, prog_name): parser = super(List, self).get_parser(prog_name) parser.add_argument( '--filter', dest='filters', action='append', help='Filters. Can be repeated.' ) return parser def _get_resources(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine return mistral_client.workflows.list( **base.get_filters(parsed_args) ) class Get(show.ShowOne): """Show specific workflow.""" def get_parser(self, prog_name): parser = super(Get, self).get_parser(prog_name) parser.add_argument('workflow', help='Workflow ID or name.') parser.add_argument( '--namespace', nargs='?', default='', help="Namespace to get the workflow from.", ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine wf = mistral_client.workflows.get( parsed_args.workflow, parsed_args.namespace ) return format(wf) class Create(base.MistralLister): """Create new workflow.""" def get_parser(self, prog_name): parser = super(Create, self).get_parser(prog_name) parser.add_argument( 'definition', type=argparse.FileType('r'), help='Workflow definition file.' ) parser.add_argument( '--namespace', nargs='?', default='', help="Namespace to create the workflow within.", ) parser.add_argument( '--public', action='store_true', help='With this flag workflow will be marked as "public".' ) return parser def _get_format_function(self): return format_list def _validate_parsed_args(self, parsed_args): if not parsed_args.definition: raise RuntimeError("You must provide path to workflow " "definition file.") def _get_resources(self, parsed_args): scope = 'public' if parsed_args.public else 'private' mistral_client = self.app.client_manager.workflow_engine return mistral_client.workflows.create( parsed_args.definition.read(), namespace=parsed_args.namespace, scope=scope ) class Delete(command.Command): """Delete workflow.""" def get_parser(self, prog_name): parser = super(Delete, self).get_parser(prog_name) parser.add_argument( 'workflow', nargs='+', help='Name or ID of workflow(s).' ) parser.add_argument( '--namespace', nargs='?', default=None, help="Namespace to delete the workflow from.", ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine utils.do_action_on_many( lambda s: mistral_client.workflows.delete(s, parsed_args.namespace), parsed_args.workflow, "Request to delete workflow %s has been accepted.", "Unable to delete the specified workflow(s)." ) class Update(base.MistralLister): """Update workflow.""" def get_parser(self, prog_name): parser = super(Update, self).get_parser(prog_name) parser.add_argument( 'definition', type=argparse.FileType('r'), help='Workflow definition' ) parser.add_argument('--id', help='Workflow ID.') parser.add_argument( '--namespace', nargs='?', default='', help="Namespace of the workflow.", ) parser.add_argument( '--public', action='store_true', help='With this flag workflow will be marked as "public".' ) return parser def _get_format_function(self): return format_list def _get_resources(self, parsed_args): scope = 'public' if parsed_args.public else 'private' mistral_client = self.app.client_manager.workflow_engine return mistral_client.workflows.update( parsed_args.definition.read(), scope=scope, id=parsed_args.id, namespace=parsed_args.namespace ) class GetDefinition(command.Command): """Show workflow definition.""" def get_parser(self, prog_name): parser = super(GetDefinition, self).get_parser(prog_name) parser.add_argument('identifier', help='Workflow ID or name.') parser.add_argument( '--namespace', nargs='?', default='', help="Namespace to get the workflow from.", ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine wf = mistral_client.workflows.get( parsed_args.identifier, parsed_args.namespace ) self.app.stdout.write(wf.definition or "\n") class Validate(show.ShowOne): """Validate workflow.""" def _format(self, result=None): columns = ('Valid', 'Error') if result: data = (result.get('valid'), result.get('error'),) else: data = (tuple('' for _ in range(len(columns))),) return columns, data def get_parser(self, prog_name): parser = super(Validate, self).get_parser(prog_name) parser.add_argument( 'definition', type=argparse.FileType('r'), help='Workflow definition file' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine result = mistral_client.workflows.validate( parsed_args.definition.read() ) return self._format(result) python-mistralclient-3.7.0/mistralclient/commands/v2/environments.py0000666000175000017500000001244113326122347026046 0ustar zuulzuul00000000000000# Copyright 2015 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import argparse import json from osc_lib.command import command from mistralclient.commands.v2 import base from mistralclient import utils def format_list(environment=None): columns = ( 'Name', 'Description', 'Scope', 'Created at', 'Updated at' ) if environment: data = ( environment.name, environment.description, environment.scope, environment.created_at, ) if hasattr(environment, 'updated_at'): data += (environment.updated_at or '',) else: data += (None,) else: data = (tuple('' for _ in range(len(columns))),) return columns, data def format(environment=None): columns = ( 'Name', 'Description', 'Variables', 'Scope', 'Created at', 'Updated at' ) if environment: data = (environment.name,) if hasattr(environment, 'description'): data += (environment.description or '',) else: data += (None,) data += ( json.dumps(environment.variables, indent=4), environment.scope, environment.created_at, ) if hasattr(environment, 'updated_at'): data += (environment.updated_at or '',) else: data += (None,) else: data = (tuple('' for _ in range(len(columns))),) return columns, data class List(base.MistralLister): """List all environments.""" def _get_format_function(self): return format_list def _get_resources(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine return mistral_client.environments.list() class Get(command.ShowOne): """Show specific environment.""" def get_parser(self, prog_name): parser = super(Get, self).get_parser(prog_name) parser.add_argument( 'environment', help='Environment name' ) parser.add_argument( '--export', default=False, action='store_true', help='Export the environment suitable for import' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine environment = mistral_client.environments.get(parsed_args.environment) if parsed_args.export: columns = ('name', 'description', 'scope', 'variables') data = (environment.name, environment.description, environment.scope, json.dumps(environment.variables)) return columns, data return format(environment) class Create(command.ShowOne): """Create new environment.""" def get_parser(self, prog_name): parser = super(Create, self).get_parser(prog_name) parser.add_argument( 'file', type=argparse.FileType('r'), help='Environment configuration file in JSON or YAML' ) return parser def take_action(self, parsed_args): data = utils.load_content(parsed_args.file.read()) mistral_client = self.app.client_manager.workflow_engine environment = mistral_client.environments.create(**data) return format(environment) class Delete(command.Command): """Delete environment.""" def get_parser(self, prog_name): parser = super(Delete, self).get_parser(prog_name) parser.add_argument( 'environment', nargs='+', help='Name of environment(s).' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine utils.do_action_on_many( lambda s: mistral_client.environments.delete(s), parsed_args.environment, "Request to delete environment %s has been accepted.", "Unable to delete the specified environment(s)." ) class Update(command.ShowOne): """Update environment.""" def get_parser(self, prog_name): parser = super(Update, self).get_parser(prog_name) parser.add_argument( 'file', type=argparse.FileType('r'), help='Environment configuration file in JSON or YAML' ) return parser def take_action(self, parsed_args): data = utils.load_content(parsed_args.file.read()) mistral_client = self.app.client_manager.workflow_engine environment = mistral_client.environments.update(**data) return format(environment) python-mistralclient-3.7.0/mistralclient/commands/v2/cron_triggers.py0000666000175000017500000001416213326122347026170 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import datetime import time from osc_lib.command import command from mistralclient.commands.v2 import base from mistralclient import utils def format_list(trigger=None): return format(trigger, lister=True) def format(trigger=None, lister=False): columns = ( 'Name', 'Workflow', 'Params', 'Pattern', # TODO(rakhmerov): Uncomment when passwords are handled properly. # TODO(rakhmerov): Add 'Workflow input' column. 'Next execution time', 'Remaining executions', 'Created at', 'Updated at' ) if trigger: # TODO(rakhmerov): Add following here: # TODO(rakhmerov): wf_input = trigger.workflow_input if not lister # TODO(rakhmerov:): else base.cut(trigger.workflow_input) data = ( trigger.name, trigger.workflow_name, trigger.workflow_params, trigger.pattern, # TODO(rakhmerov): Uncomment when passwords are handled properly. # TODo(rakhmerov): Add 'wf_input' here. trigger.next_execution_time, trigger.remaining_executions, trigger.created_at, ) if hasattr(trigger, 'updated_at'): data += (trigger.updated_at,) else: data += (None,) else: data = (tuple('' for _ in range(len(columns))),) return columns, data class List(base.MistralLister): """List all cron triggers.""" def _get_format_function(self): return format_list def _get_resources(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine return mistral_client.cron_triggers.list() class Get(command.ShowOne): """Show specific cron trigger.""" def get_parser(self, prog_name): parser = super(Get, self).get_parser(prog_name) parser.add_argument('cron_trigger', help='Cron trigger name') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine return format(mistral_client.cron_triggers.get( parsed_args.cron_trigger )) class Create(command.ShowOne): """Create new trigger.""" def get_parser(self, prog_name): parser = super(Create, self).get_parser(prog_name) parser.add_argument('name', help='Cron trigger name') parser.add_argument('workflow_identifier', help='Workflow name or ID') parser.add_argument( 'workflow_input', nargs='?', help='Workflow input' ) parser.add_argument( '--params', help='Workflow params', ) parser.add_argument( '--pattern', type=str, help='Cron trigger pattern', metavar='<* * * * *>' ) parser.add_argument( '--first-time', type=str, default=None, help=("Date and time of the first execution. Time is treated as " "local time unless --utc is also specified"), metavar='' ) parser.add_argument( '--count', type=int, help="Number of wanted executions", metavar='' ) parser.add_argument( '--utc', action='store_true', help="All times specified should be treated as UTC" ) return parser @staticmethod def _get_file_content_or_dict(string): if string: return utils.load_json(string) else: return {} @staticmethod def _convert_time_string_to_utc(time_string): datetime_format = '%Y-%m-%d %H:%M' the_time = time_string if the_time: the_time = datetime.datetime.strptime( the_time, datetime_format) is_dst = time.daylight and time.localtime().tm_isdst > 0 utc_offset = - (time.altzone if is_dst else time.timezone) the_time = (the_time - datetime.timedelta( 0, utc_offset)).strftime(datetime_format) return the_time def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine wf_input = self._get_file_content_or_dict(parsed_args.workflow_input) wf_params = self._get_file_content_or_dict(parsed_args.params) first_time = parsed_args.first_time if not parsed_args.utc: first_time = self._convert_time_string_to_utc( parsed_args.first_time) trigger = mistral_client.cron_triggers.create( parsed_args.name, parsed_args.workflow_identifier, wf_input, wf_params, parsed_args.pattern, first_time, parsed_args.count ) return format(trigger) class Delete(command.Command): """Delete trigger.""" def get_parser(self, prog_name): parser = super(Delete, self).get_parser(prog_name) parser.add_argument( 'cron_trigger', nargs='+', help='Name of cron trigger(s).' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine utils.do_action_on_many( lambda s: mistral_client.cron_triggers.delete(s), parsed_args.cron_trigger, "Request to delete cron trigger %s has been accepted.", "Unable to delete the specified cron trigger(s)." ) python-mistralclient-3.7.0/mistralclient/commands/v2/services.py0000666000175000017500000000221413326122347025137 0ustar zuulzuul00000000000000# Copyright 2015 Huawei Technologies Co., Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from mistralclient.commands.v2 import base def format_list(service=None): columns = ('Name', 'Type') if service: data = (service.name, service.type) else: data = (tuple('' for _ in range(len(columns))),) return columns, data class List(base.MistralLister): """List all services.""" def _get_format_function(self): return format_list def _get_resources(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine return mistral_client.services.list() python-mistralclient-3.7.0/mistralclient/commands/v2/members.py0000666000175000017500000001436513326122347024760 0ustar zuulzuul00000000000000# Copyright 2016 - Catalyst IT Limited # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # from osc_lib.command import command from mistralclient.commands.v2 import base from mistralclient import exceptions def format_list(member=None): return format(member, lister=True) def format(member=None, lister=False): columns = ( 'Resource ID', 'Resource Type', 'Resource Owner', 'Member ID', 'Status', 'Created at', 'Updated at' ) if member: data = ( member.resource_id, member.resource_type, member.project_id, member.member_id, member.status, member.created_at, ) if hasattr(member, 'updated_at'): data += (member.updated_at,) else: data += (None,) else: data = (tuple('' for _ in range(len(columns))),) return columns, data class List(base.MistralLister): """List all members.""" def _get_format_function(self): return format_list def get_parser(self, parsed_args): parser = super(List, self).get_parser(parsed_args) parser.add_argument( 'resource_id', help='Resource id to be shared.' ) parser.add_argument( 'resource_type', help='Resource type.' ) return parser def _get_resources(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine return mistral_client.members.list( parsed_args.resource_id, parsed_args.resource_type ) class Get(command.ShowOne): """Show specific member information.""" def get_parser(self, prog_name): parser = super(Get, self).get_parser(prog_name) parser.add_argument( 'resource', help='Resource ID to be shared.' ) parser.add_argument( 'resource_type', help='Resource type.' ) parser.add_argument( '-m', '--member-id', default='', help='Project ID to whom the resource is shared to. No need to ' 'provide this param if you are the resource member.' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine member = mistral_client.members.get( parsed_args.resource, parsed_args.resource_type, parsed_args.member_id, ) return format(member) class Create(command.ShowOne): """Shares a resource to another tenant.""" def get_parser(self, prog_name): parser = super(Create, self).get_parser(prog_name) parser.add_argument( 'resource_id', help='Resource ID to be shared.' ) parser.add_argument( 'resource_type', help='Resource type.' ) parser.add_argument( 'member_id', help='Project ID to whom the resource is shared to.' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine member = mistral_client.members.create( parsed_args.resource_id, parsed_args.resource_type, parsed_args.member_id, ) return format(member) class Delete(command.Command): """Delete a resource sharing relationship.""" def get_parser(self, prog_name): parser = super(Delete, self).get_parser(prog_name) parser.add_argument( 'resource', help='Resource ID to be shared.' ) parser.add_argument( 'resource_type', help='Resource type.' ) parser.add_argument( 'member_id', help='Project ID to whom the resource is shared to.' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine try: mistral_client.members.delete( parsed_args.resource, parsed_args.resource_type, parsed_args.member_id, ) print( "Request to delete %s member %s has been accepted." % (parsed_args.resource_type, parsed_args.member_id) ) except Exception as e: print(e) error_msg = "Unable to delete the specified member." raise exceptions.MistralClientException(error_msg) class Update(command.ShowOne): """Update resource sharing status.""" def get_parser(self, prog_name): parser = super(Update, self).get_parser(prog_name) parser.add_argument( 'resource_id', help='Resource ID to be shared.' ) parser.add_argument( 'resource_type', help='Resource type.' ) parser.add_argument( '-m', '--member-id', default='', help='Project ID to whom the resource is shared to. No need to ' 'provide this param if you are the resource member.' ) parser.add_argument( '-s', '--status', default='accepted', choices=['pending', 'accepted', 'rejected'], help='status of the sharing.' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine member = mistral_client.members.update( parsed_args.resource_id, parsed_args.resource_type, parsed_args.member_id, status=parsed_args.status ) return format(member) python-mistralclient-3.7.0/mistralclient/commands/v2/base.py0000666000175000017500000000406613326122347024235 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import abc import textwrap from osc_lib.command import command import six DEFAULT_LIMIT = 100 @six.add_metaclass(abc.ABCMeta) class MistralLister(command.Lister): @abc.abstractmethod def _get_format_function(self): raise NotImplementedError @abc.abstractmethod def _get_resources(self, parsed_args): """Gets a list of API resources (e.g. using client).""" raise NotImplementedError def _validate_parsed_args(self, parsed_args): # No-op by default. pass def take_action(self, parsed_args): self._validate_parsed_args(parsed_args) f = self._get_format_function() ret = self._get_resources(parsed_args) if not isinstance(ret, list): ret = [ret] data = [f(r)[1] for r in ret] if data: return f()[0], data else: return f() def cut(string, length=25): if string and len(string) > length: return "%s..." % string[:length] else: return string def wrap(string, width=25): if string and len(string) > width: return textwrap.fill(string, width) else: return string def get_filters(parsed_args): filters = {} if parsed_args.filters: for f in parsed_args.filters: arr = f.split('=') if len(arr) != 2: raise ValueError('Invalid filter: %s' % f) filters[arr[0]] = arr[1] return filters python-mistralclient-3.7.0/mistralclient/commands/v2/tasks.py0000666000175000017500000001531213326122347024444 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, Inc. # All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import json import logging import os.path from osc_lib.command import command from mistralclient.commands.v2 import base from mistralclient import utils LOG = logging.getLogger(__name__) class TaskFormatter(object): COLUMNS = [ ('id', 'ID'), ('name', 'Name'), ('workflow_name', 'Workflow name'), ('workflow_namespace', 'Workflow namespace'), ('workflow_execution_id', 'Workflow Execution ID'), ('state', 'State'), ('state_info', 'State info'), ('created_at', 'Created at'), ('updated_at', 'Updated at'), ] COLUMN_FIELD_NAMES = list(zip(*COLUMNS))[0] COLUMN_HEADING_NAMES = list(zip(*COLUMNS))[1] @staticmethod def format_list(task=None): return TaskFormatter.format(task, lister=True) @staticmethod def format(task=None, lister=False): if task: state_info = (task.state_info if not lister else base.cut(task.state_info)) data = ( task.id, task.name, task.workflow_name, task.workflow_namespace, task.workflow_execution_id, task.state, state_info, task.created_at, task.updated_at or '' ) else: data = (tuple('' for _ in range(len(TaskFormatter.COLUMNS))),) return TaskFormatter.COLUMN_HEADING_NAMES, data class List(base.MistralLister): """List all tasks.""" def get_parser(self, prog_name): parser = super(List, self).get_parser(prog_name) parser.add_argument( 'workflow_execution', nargs='?', help='Workflow execution ID associated with list of Tasks.' ) parser.add_argument( '--filter', dest='filters', action='append', help='Filters. Can be repeated.' ) parser.add_argument( '--limit', type=int, help='Maximum number of tasks to return in a single result. ' 'limit is set to %s by default. Use --limit -1 to fetch the ' 'full result set.' % base.DEFAULT_LIMIT, nargs='?' ) return parser def _get_format_function(self): return TaskFormatter.format_list def _get_resources(self, parsed_args): if parsed_args.limit is None: parsed_args.limit = base.DEFAULT_LIMIT LOG.info( "limit is set to %s by default. Set " "the limit explicitly using \'--limit\', if required. " "Use \'--limit\' -1 to fetch the full result set.", base.DEFAULT_LIMIT ) mistral_client = self.app.client_manager.workflow_engine return mistral_client.tasks.list( parsed_args.workflow_execution, limit=parsed_args.limit, fields=TaskFormatter.COLUMN_FIELD_NAMES, **base.get_filters(parsed_args) ) class Get(command.ShowOne): """Show specific task.""" def get_parser(self, prog_name): parser = super(Get, self).get_parser(prog_name) parser.add_argument('task', help='Task identifier') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine execution = mistral_client.tasks.get(parsed_args.task) return TaskFormatter.format(execution) class GetResult(command.Command): """Show task output data.""" def get_parser(self, prog_name): parser = super(GetResult, self).get_parser(prog_name) parser.add_argument( 'id', help='Task ID') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine result = mistral_client.tasks.get(parsed_args.id).result try: result = json.loads(result) result = json.dumps(result, indent=4) + "\n" except Exception: LOG.debug("Task result is not JSON.") self.app.stdout.write(result or "\n") class GetPublished(command.Command): """Show task published variables.""" def get_parser(self, prog_name): parser = super(GetPublished, self).get_parser(prog_name) parser.add_argument( 'id', help='Task ID') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine result = mistral_client.tasks.get(parsed_args.id).published try: result = json.loads(result) result = json.dumps(result, indent=4) + "\n" except Exception: LOG.debug("Task result is not JSON.") self.app.stdout.write(result or "\n") class Rerun(command.ShowOne): """Rerun an existing task.""" def get_parser(self, prog_name): parser = super(Rerun, self).get_parser(prog_name) parser.add_argument( 'id', help='Task identifier' ) parser.add_argument( '--resume', action='store_true', dest='resume', default=False, help=('rerun only failed or unstarted action ' 'executions for with-items task') ) parser.add_argument( '-e', '--env', dest='env', help='Environment variables' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine env = ( utils.load_file(parsed_args.env) if parsed_args.env and os.path.isfile(parsed_args.env) else utils.load_content(parsed_args.env) ) execution = mistral_client.tasks.rerun( parsed_args.id, reset=(not parsed_args.resume), env=env ) return TaskFormatter.format(execution) python-mistralclient-3.7.0/mistralclient/commands/v2/executions.py0000666000175000017500000002513113326122347025505 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, Inc. # Copyright 2016 - Brocade Communications Systems, Inc. # # All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import json import logging import os.path from osc_lib.command import command from mistralclient.commands.v2 import base from mistralclient import utils LOG = logging.getLogger(__name__) def format_list(execution=None): return format(execution, lister=True) def format(execution=None, lister=False): columns = ( 'ID', 'Workflow ID', 'Workflow name', 'Workflow namespace', 'Description', 'Task Execution ID', 'Root Execution ID', 'State', 'State info', 'Created at', 'Updated at' ) # TODO(nmakhotkin) Add parent task id when it's implemented in API. if execution: state_info = (execution.state_info if not lister else base.cut(execution.state_info)) data = ( execution.id, execution.workflow_id, execution.workflow_name, execution.workflow_namespace, execution.description, execution.task_execution_id or '', execution.root_execution_id or '', execution.state, state_info, execution.created_at, execution.updated_at or '' ) else: data = (tuple('' for _ in range(len(columns))),) return columns, data class List(base.MistralLister): """List all executions.""" def _get_format_function(self): return format_list def get_parser(self, parsed_args): parser = super(List, self).get_parser(parsed_args) parser.add_argument( '--task', nargs='?', help="Parent task execution ID associated with workflow " "execution list.", ) parser.add_argument( '--marker', type=str, help='The last execution uuid of the previous page, displays list ' 'of executions after "marker".', default='', nargs='?' ) parser.add_argument( '--limit', type=int, help='Maximum number of executions to return in a single result. ' 'limit is set to %s by default. Use --limit -1 to fetch the ' 'full result set.' % base.DEFAULT_LIMIT, nargs='?' ) parser.add_argument( '--sort_keys', help='Comma-separated list of sort keys to sort results by. ' 'Default: created_at. ' 'Example: mistral execution-list --sort_keys=id,description', default='created_at', nargs='?' ) parser.add_argument( '--sort_dirs', help='Comma-separated list of sort directions. Default: asc. ' 'Example: mistral execution-list --sort_keys=id,description ' '--sort_dirs=asc,desc', default='asc', nargs='?' ) parser.add_argument( '--filter', dest='filters', action='append', help='Filters. Can be repeated.' ) return parser def _get_resources(self, parsed_args): if parsed_args.limit is None: parsed_args.limit = base.DEFAULT_LIMIT LOG.info( "limit is set to %s by default. Set " "the limit explicitly using \'--limit\', if required. " "Use \'--limit\' -1 to fetch the full result set.", base.DEFAULT_LIMIT ) mistral_client = self.app.client_manager.workflow_engine return mistral_client.executions.list( task=parsed_args.task, marker=parsed_args.marker, limit=parsed_args.limit, sort_keys=parsed_args.sort_keys, sort_dirs=parsed_args.sort_dirs, **base.get_filters(parsed_args) ) class Get(command.ShowOne): """Show specific execution.""" def get_parser(self, prog_name): parser = super(Get, self).get_parser(prog_name) parser.add_argument('execution', help='Execution identifier') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine execution = mistral_client.executions.get(parsed_args.execution) return format(execution) class Create(command.ShowOne): """Create new execution.""" def get_parser(self, prog_name): parser = super(Create, self).get_parser(prog_name) parser.add_argument( 'workflow_identifier', nargs='?', help='Workflow ID or name. Workflow name will be deprecated since ' 'Mitaka.' ) parser.add_argument( '--namespace', nargs='?', default='', help="Workflow namespace." ) parser.add_argument( 'workflow_input', nargs='?', help='Workflow input' ) parser.add_argument( 'params', nargs='?', help='Workflow additional parameters' ) parser.add_argument( '-d', '--description', dest='description', default='', help='Execution description' ) parser.add_argument( '-s', dest='source_execution_id', nargs='?', help="Workflow Execution id which will allow operators to create " "a new workflow execution based on the previously successful " "executed workflow. Example: mistral execution-create -s " "123e4567-e89b-12d3-a456-426655440000") return parser def take_action(self, parsed_args): if parsed_args.workflow_input: wf_input = utils.load_json(parsed_args.workflow_input) else: wf_input = {} if parsed_args.params: params = utils.load_json(parsed_args.params) else: params = {} mistral_client = self.app.client_manager.workflow_engine execution = mistral_client.executions.create( parsed_args.workflow_identifier, parsed_args.namespace, wf_input, parsed_args.description, parsed_args.source_execution_id, **params ) return format(execution) class Delete(command.Command): """Delete execution.""" def get_parser(self, prog_name): parser = super(Delete, self).get_parser(prog_name) parser.add_argument( 'execution', nargs='+', help='Id of execution identifier(s).' ) parser.add_argument( '--force', default=False, action='store_true', help='Force the deletion of an execution. Might cause a cascade ' ' of errors if used for running executions.' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine force = parsed_args.force utils.do_action_on_many( lambda s: mistral_client.executions.delete(s, force=force), parsed_args.execution, "Request to delete execution %s has been accepted.", "Unable to delete the specified execution(s)." ) class Update(command.ShowOne): """Update execution.""" def get_parser(self, prog_name): parser = super(Update, self).get_parser(prog_name) parser.add_argument( 'id', help='Execution identifier' ) parser.add_argument( '-s', '--state', dest='state', choices=['RUNNING', 'PAUSED', 'SUCCESS', 'ERROR', 'CANCELLED'], help='Execution state' ) parser.add_argument( '-e', '--env', dest='env', help='Environment variables' ) parser.add_argument( '-d', '--description', dest='description', help='Execution description' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine env = ( utils.load_file(parsed_args.env) if parsed_args.env and os.path.isfile(parsed_args.env) else utils.load_content(parsed_args.env) ) execution = mistral_client.executions.update( parsed_args.id, parsed_args.state, description=parsed_args.description, env=env ) return format(execution) class GetInput(command.Command): """Show execution input data.""" def get_parser(self, prog_name): parser = super(GetInput, self).get_parser(prog_name) parser.add_argument('id', help='Execution ID') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine ex_input = mistral_client.executions.get(parsed_args.id).input try: ex_input = json.loads(ex_input) ex_input = json.dumps(ex_input, indent=4) + "\n" except Exception: LOG.debug("Execution input is not JSON.") self.app.stdout.write(ex_input or "\n") class GetOutput(command.Command): """Show execution output data.""" def get_parser(self, prog_name): parser = super(GetOutput, self).get_parser(prog_name) parser.add_argument('id', help='Execution ID') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine output = mistral_client.executions.get(parsed_args.id).output try: output = json.loads(output) output = json.dumps(output, indent=4) + "\n" except Exception: LOG.debug("Execution output is not JSON.") self.app.stdout.write(output or "\n") python-mistralclient-3.7.0/mistralclient/commands/v2/workbooks.py0000666000175000017500000001315613326122347025343 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2015 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import argparse from osc_lib.command import command from mistralclient.commands.v2 import base from mistralclient import utils def format(workbook=None): columns = ( 'Name', 'Tags', 'Scope', 'Created at', 'Updated at' ) if workbook: data = ( workbook.name, base.wrap(', '.join(workbook.tags or '')) or '', workbook.scope, workbook.created_at, ) if hasattr(workbook, 'updated_at'): data += (workbook.updated_at,) else: data += (None,) else: data = (tuple('' for _ in range(len(columns))),) return columns, data class List(base.MistralLister): """List all workbooks.""" def _get_format_function(self): return format def _get_resources(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine return mistral_client.workbooks.list() class Get(command.ShowOne): """Show specific workbook.""" def get_parser(self, prog_name): parser = super(Get, self).get_parser(prog_name) parser.add_argument( 'workbook', help='Workbook name' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine workbook = mistral_client.workbooks.get(parsed_args.workbook) return format(workbook) class Create(command.ShowOne): """Create new workbook.""" def get_parser(self, prog_name): parser = super(Create, self).get_parser(prog_name) parser.add_argument( 'definition', type=argparse.FileType('r'), help='Workbook definition file' ) parser.add_argument( '--public', action='store_true', help='With this flag workbook will be marked as "public".' ) return parser def take_action(self, parsed_args): scope = 'public' if parsed_args.public else 'private' mistral_client = self.app.client_manager.workflow_engine workbook = mistral_client.workbooks.create( parsed_args.definition.read(), scope=scope ) 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' ) parser.add_argument( '--public', action='store_true', help='With this flag workbook will be marked as "public".' ) return parser def take_action(self, parsed_args): scope = 'public' if parsed_args.public else 'private' mistral_client = self.app.client_manager.workflow_engine workbook = mistral_client.workbooks.update( parsed_args.definition.read(), scope=scope ) return format(workbook) class GetDefinition(command.Command): """Show workbook definition.""" def get_parser(self, prog_name): parser = super(GetDefinition, self).get_parser(prog_name) parser.add_argument('name', help='Workbook name') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine definition = mistral_client.workbooks.get(parsed_args.name).definition self.app.stdout.write(definition or "\n") class Validate(command.ShowOne): """Validate workbook.""" def _format(self, result=None): columns = ('Valid', 'Error') if result: data = (result.get('valid'), result.get('error'),) else: data = (tuple('' for _ in range(len(columns))),) return columns, data def get_parser(self, prog_name): parser = super(Validate, self).get_parser(prog_name) parser.add_argument( 'definition', type=argparse.FileType('r'), help='Workbook definition file' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine result = mistral_client.workbooks.validate( parsed_args.definition.read() ) return self._format(result) python-mistralclient-3.7.0/mistralclient/commands/v2/action_executions.py0000666000175000017500000002257213326122347027050 0ustar zuulzuul00000000000000# Copyright 2014 - Mirantis, Inc. # Copyright 2016 - Brocade Communications Systems, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import json import logging from osc_lib.command import command from mistralclient.commands.v2 import base from mistralclient import utils LOG = logging.getLogger(__name__) def format_list(action_ex=None): columns = ( 'ID', 'Name', 'Workflow name', 'Workflow namespace', 'Task name', 'Task ID', 'State', 'Accepted', 'Created at', 'Updated at' ) if action_ex: data = ( action_ex.id, action_ex.name, action_ex.workflow_name, action_ex.workflow_namespace, action_ex.task_name if hasattr(action_ex, 'task_name') else None, action_ex.task_execution_id, action_ex.state, action_ex.accepted, action_ex.created_at, action_ex.updated_at or '' ) else: data = (tuple('' for _ in range(len(columns))),) return columns, data def format(action_ex=None): columns = ( 'ID', 'Name', 'Workflow name', 'Workflow namespace', 'Task name', 'Task ID', 'State', 'State info', 'Accepted', 'Created at', 'Updated at', ) if action_ex: data = ( action_ex.id, action_ex.name, action_ex.workflow_name, action_ex.workflow_namespace, action_ex.task_name if hasattr(action_ex, 'task_name') else None, action_ex.task_execution_id, action_ex.state, action_ex.state_info, action_ex.accepted, action_ex.created_at, action_ex.updated_at or '' ) else: data = (tuple('' for _ in range(len(columns))),) return columns, data class Create(command.ShowOne): """Create new Action execution or just run specific action.""" def produce_output(self, parsed_args, column_names, data): if not column_names: return 0 return super(Create, self).produce_output( parsed_args, column_names, data ) def get_parser(self, prog_name): parser = super(Create, self).get_parser(prog_name) parser.add_argument( 'name', help='Action name to execute.' ) parser.add_argument( dest='input', nargs='?', help='Action input.' ) parser.add_argument( '-s', '--save-result', dest='save_result', action='store_true', help='Save the result into DB.' ) parser.add_argument( '--run-sync', dest='run_sync', action='store_true', help='Run the action synchronously.' ) parser.add_argument( '-t', '--target', dest='target', help='Action will be executed on executor.' ) return parser def take_action(self, parsed_args): params = {} if parsed_args.save_result: params['save_result'] = parsed_args.save_result if parsed_args.run_sync: params['run_sync'] = parsed_args.run_sync if parsed_args.target: params['target'] = parsed_args.target action_input = None if parsed_args.input: action_input = utils.load_json(parsed_args.input) mistral_client = self.app.client_manager.workflow_engine action_ex = mistral_client.action_executions.create( parsed_args.name, action_input, **params ) if not parsed_args.run_sync and parsed_args.save_result: return format(action_ex) else: self.app.stdout.write("%s\n" % action_ex.output) return None, None class List(base.MistralLister): """List all Action executions.""" def _get_format_function(self): return format_list def get_parser(self, prog_name): parser = super(List, self).get_parser(prog_name) parser.add_argument( 'task_execution_id', nargs='?', help='Task execution ID.' ) parser.add_argument( '--limit', type=int, help='Maximum number of action-executions to return in a single ' 'result. limit is set to %s by default. Use --limit -1 to ' 'fetch the full result set.' % base.DEFAULT_LIMIT, nargs='?' ) return parser def _get_resources(self, parsed_args): if parsed_args.limit is None: parsed_args.limit = base.DEFAULT_LIMIT LOG.info( "limit is set to %s by default. Set " "the limit explicitly using \'--limit\', if required. " "Use \'--limit\' -1 to fetch the full result set.", base.DEFAULT_LIMIT ) mistral_client = self.app.client_manager.workflow_engine return mistral_client.action_executions.list( parsed_args.task_execution_id, limit=parsed_args.limit, ) class Get(command.ShowOne): """Show specific Action execution.""" def get_parser(self, prog_name): parser = super(Get, self).get_parser(prog_name) parser.add_argument('action_execution', help='Action execution ID.') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine execution = mistral_client.action_executions.get( parsed_args.action_execution ) return format(execution) class Update(command.ShowOne): """Update specific Action execution.""" def get_parser(self, prog_name): parser = super(Update, self).get_parser(prog_name) parser.add_argument( 'id', help='Action execution ID.') parser.add_argument( '--state', dest='state', choices=['IDLE', 'RUNNING', 'SUCCESS', 'ERROR', 'CANCELLED'], help='Action execution state') parser.add_argument( '--output', dest='output', help='Action execution output') return parser def take_action(self, parsed_args): output = None if parsed_args.output: output = utils.load_json(parsed_args.output) mistral_client = self.app.client_manager.workflow_engine execution = mistral_client.action_executions.update( parsed_args.id, parsed_args.state, output ) return format(execution) class GetOutput(command.Command): """Show Action execution output data.""" def get_parser(self, prog_name): parser = super(GetOutput, self).get_parser(prog_name) parser.add_argument( 'id', help='Action execution ID.') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine output = mistral_client.action_executions.get(parsed_args.id).output try: output = json.loads(output) output = json.dumps(output, indent=4) + "\n" except Exception: LOG.debug("Task result is not JSON.") self.app.stdout.write(output or "\n") class GetInput(command.Command): """Show Action execution input data.""" def get_parser(self, prog_name): parser = super(GetInput, self).get_parser(prog_name) parser.add_argument( 'id', help='Action execution ID.' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine result = mistral_client.action_executions.get(parsed_args.id).input try: result = json.loads(result) result = json.dumps(result, indent=4) + "\n" except Exception: LOG.debug("Task result is not JSON.") self.app.stdout.write(result or "\n") class Delete(command.Command): """Delete action execution.""" def get_parser(self, prog_name): parser = super(Delete, self).get_parser(prog_name) parser.add_argument( 'action_execution', nargs='+', help='Id of action execution identifier(s).' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine utils.do_action_on_many( lambda s: mistral_client.action_executions.delete(s), parsed_args.action_execution, "Request to delete action execution %s has been accepted.", "Unable to delete the specified action execution(s)." ) python-mistralclient-3.7.0/mistralclient/commands/v2/event_triggers.py0000666000175000017500000001106113326122347026343 0ustar zuulzuul00000000000000# Copyright 2017, OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # from osc_lib.command import command from mistralclient.commands.v2 import base from mistralclient import utils def format_list(trigger=None): return format(trigger, lister=True) def format(trigger=None, lister=False): columns = ( 'ID', 'Name', 'Workflow ID', 'Params', 'Exchange', 'Topic', 'Event', 'Created at', 'Updated at' ) if trigger: data = ( trigger.id, trigger.name, trigger.workflow_id, trigger.workflow_params, trigger.exchange, trigger.topic, trigger.event, trigger.created_at, ) if hasattr(trigger, 'updated_at'): data += (trigger.updated_at,) else: data += (None,) else: data = (tuple('' for _ in range(len(columns))),) return columns, data class List(base.MistralLister): """List all event triggers.""" def _get_format_function(self): return format_list def _get_resources(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine return mistral_client.event_triggers.list() class Get(command.ShowOne): """Show specific event trigger.""" def get_parser(self, prog_name): parser = super(Get, self).get_parser(prog_name) parser.add_argument('event_trigger', help='Event trigger ID') return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine return format(mistral_client.event_triggers.get( parsed_args.event_trigger )) class Create(command.ShowOne): """Create new trigger.""" def get_parser(self, prog_name): parser = super(Create, self).get_parser(prog_name) parser.add_argument('name', help='Event trigger name') parser.add_argument('workflow_id', help='Workflow ID') parser.add_argument('exchange', type=str, help='Event trigger exchange') parser.add_argument('topic', type=str, help='Event trigger topic') parser.add_argument('event', type=str, help='Event trigger event name') parser.add_argument('workflow_input', nargs='?', help='Workflow input') parser.add_argument('--params', help='Workflow params') return parser @staticmethod def _get_json_string_or_dict(string): if string: return utils.load_json(string) else: return {} def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine wf_input = self._get_json_string_or_dict(parsed_args.workflow_input) wf_params = self._get_json_string_or_dict(parsed_args.params) trigger = mistral_client.event_triggers.create( parsed_args.name, parsed_args.workflow_id, parsed_args.exchange, parsed_args.topic, parsed_args.event, wf_input, wf_params, ) return format(trigger) class Delete(command.Command): """Delete trigger.""" def get_parser(self, prog_name): parser = super(Delete, self).get_parser(prog_name) parser.add_argument( 'event_trigger_id', nargs='+', help='ID of event trigger(s).' ) return parser def take_action(self, parsed_args): mistral_client = self.app.client_manager.workflow_engine utils.do_action_on_many( lambda s: mistral_client.event_triggers.delete(s), parsed_args.event_trigger_id, "Request to delete event trigger %s has been accepted.", "Unable to delete the specified event trigger(s)." ) python-mistralclient-3.7.0/mistralclient/commands/v2/__init__.py0000666000175000017500000000000013326122347025042 0ustar zuulzuul00000000000000python-mistralclient-3.7.0/mistralclient/commands/__init__.py0000666000175000017500000000000013326122347024513 0ustar zuulzuul00000000000000python-mistralclient-3.7.0/mistralclient/i18n.py0000666000175000017500000000152313326122347021745 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """oslo.i18n integration module. See https://docs.openstack.org/oslo.i18n/latest/user/usage.html """ import oslo_i18n _translators = oslo_i18n.TranslatorFactory(domain='mistralclient') # The primary translation function using the well-known name "_" _ = _translators.primary python-mistralclient-3.7.0/mistralclient/shell.py0000666000175000017500000006760113326122347022306 0ustar zuulzuul00000000000000# Copyright 2015 - StackStorm, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Command-line interface to the Mistral APIs """ import argparse import logging import os import sys from cliff import app from cliff import commandmanager from osc_lib.command import command from mistralclient.api import client from mistralclient.auth import auth_types import mistralclient.commands.v2.action_executions import mistralclient.commands.v2.actions import mistralclient.commands.v2.cron_triggers import mistralclient.commands.v2.environments import mistralclient.commands.v2.event_triggers import mistralclient.commands.v2.executions import mistralclient.commands.v2.members import mistralclient.commands.v2.services import mistralclient.commands.v2.tasks import mistralclient.commands.v2.workbooks import mistralclient.commands.v2.workflows from mistralclient import exceptions as exe def env(*args, **kwargs): """Returns the first environment variable set. If all are empty, defaults to '' or keyword arg `default`. """ for arg in args: value = os.environ.get(arg) if value: return value return kwargs.get('default', '') class OpenStackHelpFormatter(argparse.HelpFormatter): def __init__(self, prog, indent_increment=2, max_help_position=32, width=None): super(OpenStackHelpFormatter, self).__init__( prog, indent_increment, max_help_position, width ) def start_section(self, heading): # Title-case the headings. heading = '%s%s' % (heading[0].upper(), heading[1:]) super(OpenStackHelpFormatter, self).start_section(heading) class HelpAction(argparse.Action): """Custom help action. Provide a custom action so the -h and --help options to the main app will print a list of the commands. The commands are determined by checking the CommandManager instance, passed in as the "default" value for the action. """ def __call__(self, parser, namespace, values, option_string=None): outputs = [] max_len = 0 app = self.default parser.print_help(app.stdout) app.stdout.write('\nCommands for API v2 :\n') for name, ep in sorted(app.command_manager): factory = ep.load() cmd = factory(self, None) one_liner = cmd.get_description().split('\n')[0] outputs.append((name, one_liner)) max_len = max(len(name), max_len) for (name, one_liner) in outputs: app.stdout.write(' %s %s\n' % (name.ljust(max_len), one_liner)) sys.exit(0) class BashCompletionCommand(command.Command): """Prints all of the commands and options for bash-completion.""" def take_action(self, parsed_args): commands = set() options = set() for option, _action in self.app.parser._option_string_actions.items(): options.add(option) for command_name, _cmd in self.app.command_manager: commands.add(command_name) print(' '.join(commands | options)) class MistralShell(app.App): def __init__(self): super(MistralShell, self).__init__( description=__doc__.strip(), version=mistralclient.__version__, command_manager=commandmanager.CommandManager('mistral.cli'), ) # Set v2 commands by default self._set_shell_commands(self._get_commands_v2()) def configure_logging(self): log_lvl = logging.DEBUG if self.options.debug else logging.WARNING logging.basicConfig( format="%(levelname)s (%(module)s) %(message)s", level=log_lvl ) logging.getLogger('iso8601').setLevel(logging.WARNING) if self.options.verbose_level <= 1: logging.getLogger('requests').setLevel(logging.WARNING) def build_option_parser(self, description, version, argparse_kwargs=None): """Return an argparse option parser for this application. Subclasses may override this method to extend the parser with more global options. :param description: full description of the application :paramtype description: str :param version: version number for the application :paramtype version: str :param argparse_kwargs: extra keyword argument passed to the ArgumentParser constructor :paramtype extra_kwargs: dict """ argparse_kwargs = argparse_kwargs or {} parser = argparse.ArgumentParser( description=description, add_help=False, formatter_class=OpenStackHelpFormatter, **argparse_kwargs ) parser.add_argument( '--version', action='version', version='%(prog)s {0}'.format(version), help='Show program\'s version number and exit.' ) parser.add_argument( '-v', '--verbose', action='count', dest='verbose_level', default=self.DEFAULT_VERBOSE_LEVEL, help='Increase verbosity of output. Can be repeated.', ) parser.add_argument( '--log-file', action='store', default=None, help='Specify a file to log output. Disabled by default.', ) parser.add_argument( '-q', '--quiet', action='store_const', dest='verbose_level', const=0, help='Suppress output except warnings and errors.', ) parser.add_argument( '-h', '--help', action=HelpAction, nargs=0, default=self, # tricky help="Show this help message and exit.", ) parser.add_argument( '--debug', default=False, action='store_true', help='Show tracebacks on errors.', ) parser.add_argument( '--os-mistral-url', action='store', dest='mistral_url', default=env('OS_MISTRAL_URL'), help='Mistral API host (Env: OS_MISTRAL_URL)' ) parser.add_argument( '--os-mistral-version', action='store', dest='mistral_version', default=env('OS_MISTRAL_VERSION', default='v2'), help='Mistral API version (default = v2) (Env: ' 'OS_MISTRAL_VERSION)' ) parser.add_argument( '--os-mistral-service-type', action='store', dest='service_type', default=env('OS_MISTRAL_SERVICE_TYPE', default='workflowv2'), help='Mistral service-type (should be the same name as in ' 'keystone-endpoint) (default = workflowv2) (Env: ' 'OS_MISTRAL_SERVICE_TYPE)' ) parser.add_argument( '--os-mistral-endpoint-type', action='store', dest='endpoint_type', default=env('OS_MISTRAL_ENDPOINT_TYPE', default='publicURL'), help='Mistral endpoint-type (should be the same name as in ' 'keystone-endpoint) (default = publicURL) (Env: ' 'OS_MISTRAL_ENDPOINT_TYPE)' ) parser.add_argument( '--os-username', action='store', dest='username', default=env('OS_USERNAME'), help='Authentication username (Env: OS_USERNAME)' ) parser.add_argument( '--os-password', action='store', dest='password', default=env('OS_PASSWORD'), help='Authentication password (Env: OS_PASSWORD)' ) parser.add_argument( '--os-tenant-id', action='store', dest='tenant_id', default=env('OS_TENANT_ID', 'OS_PROJECT_ID'), help='Authentication tenant identifier (Env: OS_TENANT_ID' ' or OS_PROJECT_ID)' ) parser.add_argument( '--os-project-id', action='store', dest='project_id', default=env('OS_TENANT_ID', 'OS_PROJECT_ID'), help='Authentication project identifier (Env: OS_TENANT_ID' ' or OS_PROJECT_ID), will use tenant_id if both tenant_id' ' and project_id are set' ) parser.add_argument( '--os-tenant-name', action='store', dest='tenant_name', default=env('OS_TENANT_NAME', 'OS_PROJECT_NAME'), help='Authentication tenant name (Env: OS_TENANT_NAME' ' or OS_PROJECT_NAME)' ) parser.add_argument( '--os-project-name', action='store', dest='project_name', default=env('OS_TENANT_NAME', 'OS_PROJECT_NAME'), help='Authentication project name (Env: OS_TENANT_NAME' ' or OS_PROJECT_NAME), will use tenant_name if both' ' tenant_name and project_name are set' ) parser.add_argument( '--os-auth-token', action='store', dest='token', default=env('OS_AUTH_TOKEN'), help='Authentication token (Env: OS_AUTH_TOKEN)' ) parser.add_argument( '--os-project-domain-name', action='store', dest='project_domain_name', default=env('OS_PROJECT_DOMAIN_NAME'), help='Authentication project domain name or ID' ' (Env: OS_PROJECT_DOMAIN_NAME or OS_PROJECT_DOMAIN_NAME)' ) parser.add_argument( '--os-project-domain-id', action='store', dest='project_domain_id', default=env('OS_PROJECT_DOMAIN_ID'), help='Authentication project domain ID' ' (Env: OS_PROJECT_DOMAIN_ID)' ) parser.add_argument( '--os-user-domain-name', action='store', dest='user_domain_name', default=env('OS_USER_DOMAIN_NAME'), help='Authentication user domain name' ' (Env: OS_USER_DOMAIN_NAME)' ) parser.add_argument( '--os-user-domain-id', action='store', dest='user_domain_id', default=env('OS_USER_DOMAIN_ID'), help='Authentication user domain name' ' (Env: OS_USER_DOMAIN_ID)' ) parser.add_argument( '--os-auth-url', action='store', dest='auth_url', default=env('OS_AUTH_URL'), help='Authentication URL (Env: OS_AUTH_URL)' ) parser.add_argument( '--os-cert', action='store', dest='os_cert', default=env('OS_CERT'), help='Client Certificate (Env: OS_CERT)' ) parser.add_argument( '--os-key', action='store', dest='os_key', default=env('OS_KEY'), help='Client Key (Env: OS_KEY)' ) parser.add_argument( '--os-cacert', action='store', dest='os_cacert', default=env('OS_CACERT'), help='Authentication CA Certificate (Env: OS_CACERT)' ) parser.add_argument( '--os-region-name', action='store', dest='region_name', default=env('OS_REGION_NAME'), help='Region name (Env: OS_REGION_NAME)' ) parser.add_argument( '--insecure', action='store_true', dest='insecure', default=env('MISTRALCLIENT_INSECURE', default=False), help='Disables SSL/TLS certificate verification ' '(Env: MISTRALCLIENT_INSECURE)' ) parser.add_argument( '--auth-type', action='store', dest='auth_type', default=env('MISTRAL_AUTH_TYPE', default='keystone'), help='Authentication type. Valid options are: %s.' ' (Env: MISTRAL_AUTH_TYPE)' % ', '.join(auth_types.ALL) ) parser.add_argument( '--openid-client-id', action='store', dest='client_id', default=env('OPENID_CLIENT_ID'), help='Client ID (according to OpenID Connect).' ' (Env: OPENID_CLIENT_ID)' ) parser.add_argument( '--openid-client-secret', action='store', dest='client_secret', default=env('OPENID_CLIENT_SECRET'), help='Client secret (according to OpenID Connect)' ' (Env: OPENID_CLIENT_SECRET)' ) parser.add_argument( '--os-target-username', action='store', dest='target_username', default=env('OS_TARGET_USERNAME', default='admin'), help='Authentication username for target cloud' ' (Env: OS_TARGET_USERNAME)' ) parser.add_argument( '--os-target-password', action='store', dest='target_password', default=env('OS_TARGET_PASSWORD'), help='Authentication password for target cloud' ' (Env: OS_TARGET_PASSWORD)' ) parser.add_argument( '--os-target-tenant-id', action='store', dest='target_tenant_id', default=env('OS_TARGET_TENANT_ID'), help='Authentication tenant identifier for target cloud' ' (Env: OS_TARGET_TENANT_ID)' ) parser.add_argument( '--os-target-tenant-name', action='store', dest='target_tenant_name', default=env('OS_TARGET_TENANT_NAME'), help='Authentication tenant name for target cloud' ' (Env: OS_TARGET_TENANT_NAME)' ) parser.add_argument( '--os-target-auth-token', action='store', dest='target_token', default=env('OS_TARGET_AUTH_TOKEN'), help='Authentication token for target cloud' ' (Env: OS_TARGET_AUTH_TOKEN)' ) parser.add_argument( '--os-target-auth-url', action='store', dest='target_auth_url', default=env('OS_TARGET_AUTH_URL'), help='Authentication URL for target cloud' ' (Env: OS_TARGET_AUTH_URL)' ) parser.add_argument( '--os-target_cacert', action='store', dest='target_cacert', default=env('OS_TARGET_CACERT'), help='Authentication CA Certificate for target cloud' ' (Env: OS_TARGET_CACERT)' ) parser.add_argument( '--os-target-region-name', action='store', dest='target_region_name', default=env('OS_TARGET_REGION_NAME'), help='Region name for target cloud' '(Env: OS_TARGET_REGION_NAME)' ) parser.add_argument( '--os-target-user-domain-name', action='store', dest='target_user_domain_name', default=env('OS_TARGET_USER_DOMAIN_NAME'), help='User domain name for target cloud' '(Env: OS_TARGET_USER_DOMAIN_NAME)' ) parser.add_argument( '--os-target-user-domain-id', action='store', dest='target_user_domain_id', default=env('OS_TARGET_USER_DOMAIN_ID'), help='User domain ID for target cloud' '(Env: OS_TARGET_USER_DOMAIN_ID)' ) parser.add_argument( '--os-target-project-domain-name', action='store', dest='target_project_domain_name', default=env('OS_TARGET_PROJECT_DOMAIN_NAME'), help='Project domain name for target cloud' '(Env: OS_TARGET_PROJECT_DOMAIN_NAME)' ) parser.add_argument( '--os-target-project-domain-id', action='store', dest='target_project_domain_id', default=env('OS_TARGET_PROJECT_DOMAIN_ID'), help='Project domain ID for target cloud' '(Env: OS_TARGET_PROJECT_DOMAIN_ID)' ) parser.add_argument( '--target_insecure', action='store_true', dest='target_insecure', default=env('TARGET_MISTRALCLIENT_INSECURE', default=False), help='Disables SSL/TLS certificate verification for target cloud ' '(Env: TARGET_MISTRALCLIENT_INSECURE)' ) parser.add_argument( '--profile', dest='profile', metavar='HMAC_KEY', default=env('OS_PROFILE'), help='HMAC key to use for encrypting context data for performance ' 'profiling of operation. This key should be one of the ' 'values configured for the osprofiler middleware in mistral, ' 'it is specified in the profiler section of the mistral ' 'configuration (i.e. /etc/mistral/mistral.conf). Without the ' 'key, profiling will not be triggered even if osprofiler is ' 'enabled on the server side.' ) return parser def initialize_app(self, argv): self._clear_shell_commands() ver = client.determine_client_version(self.options.mistral_version) self._set_shell_commands(self._get_commands(ver)) # bash-completion and help messages should not require client creation need_client = not ( ('bash-completion' in argv) or ('help' in argv) or ('-h' in argv) or ('--help' in argv) or not argv) # Set default for auth_url if not supplied. The default is not # set at the parser to support use cases where auth is not enabled. # An example use case would be a developer's environment. if not self.options.auth_url: if self.options.password or self.options.token: self.options.auth_url = 'http://localhost:35357/v3' if (self.options.auth_type == 'keystone' and not self.options.auth_url.endswith("/v2.0")): # Assume that keystone V3 is used and try to be more user-friendly, # i.e provide default values for domains if (not self.options.project_domain_id and not self.options.project_domain_name): self.options.project_domain_id = "default" if (not self.options.user_domain_id and not self.options.user_domain_name): self.options.user_domain_id = "default" if (not self.options.target_project_domain_id and not self.options.target_project_domain_name): self.options.target_project_domain_id = "default" if (not self.options.target_user_domain_id and not self.options.target_user_domain_name): self.options.target_user_domain_id = "default" if self.options.auth_url and not self.options.token: if not self.options.username: raise exe.IllegalArgumentException( ("You must provide a username " "via --os-username env[OS_USERNAME]") ) if not self.options.password: raise exe.IllegalArgumentException( ("You must provide a password " "via --os-password env[OS_PASSWORD]") ) self.client = self._create_client() if need_client else None # Adding client_manager variable to make mistral client work with # unified OpenStack client. ClientManager = type( 'ClientManager', (object,), dict(workflow_engine=self.client) ) self.client_manager = ClientManager() def _create_client(self): kwargs = { 'cert': self.options.os_cert, 'key': self.options.os_key, 'user_domain_name': self.options.user_domain_name, 'user_domain_id': self.options.user_domain_id, 'project_domain_name': self.options.project_domain_name, 'project_domain_id': self.options.project_domain_id, 'target_project_domain_name': self.options.target_project_domain_name, 'target_project_domain_id': self.options.target_project_domain_id, 'target_user_domain_name': self.options.target_user_domain_name, 'target_user_domain_id': self.options.target_user_domain_id } return client.client( mistral_url=self.options.mistral_url, username=self.options.username, api_key=self.options.password, project_name=self.options.tenant_name or self.options.project_name, auth_url=self.options.auth_url, project_id=self.options.tenant_id or self.options.project_id, endpoint_type=self.options.endpoint_type, service_type=self.options.service_type, region_name=self.options.region_name, auth_token=self.options.token, cacert=self.options.os_cacert, insecure=self.options.insecure, profile=self.options.profile, auth_type=self.options.auth_type, client_id=self.options.client_id, client_secret=self.options.client_secret, target_username=self.options.target_username, target_api_key=self.options.target_password, target_project_name=self.options.target_tenant_name, target_auth_url=self.options.target_auth_url, target_project_id=self.options.target_tenant_id, target_auth_token=self.options.target_token, target_cacert=self.options.target_cacert, target_region_name=self.options.target_region_name, target_insecure=self.options.target_insecure, **kwargs ) def _set_shell_commands(self, cmds_dict): for k, v in cmds_dict.items(): self.command_manager.add_command(k, v) def _clear_shell_commands(self): exclude_cmds = ['help', 'complete'] cmds = self.command_manager.commands.copy() for k, v in cmds.items(): if k not in exclude_cmds: self.command_manager.commands.pop(k) def _get_commands(self, version): if version == 2: return self._get_commands_v2() return {} @staticmethod def _get_commands_v2(): return { 'bash-completion': BashCompletionCommand, 'workbook-list': mistralclient.commands.v2.workbooks.List, 'workbook-get': mistralclient.commands.v2.workbooks.Get, 'workbook-create': mistralclient.commands.v2.workbooks.Create, 'workbook-delete': mistralclient.commands.v2.workbooks.Delete, 'workbook-update': mistralclient.commands.v2.workbooks.Update, 'workbook-get-definition': mistralclient.commands.v2.workbooks.GetDefinition, 'workbook-validate': mistralclient.commands.v2.workbooks.Validate, 'workflow-list': mistralclient.commands.v2.workflows.List, 'workflow-get': mistralclient.commands.v2.workflows.Get, 'workflow-create': mistralclient.commands.v2.workflows.Create, 'workflow-delete': mistralclient.commands.v2.workflows.Delete, 'workflow-update': mistralclient.commands.v2.workflows.Update, 'workflow-get-definition': mistralclient.commands.v2.workflows.GetDefinition, 'workflow-validate': mistralclient.commands.v2.workflows.Validate, 'environment-create': mistralclient.commands.v2.environments.Create, 'environment-delete': mistralclient.commands.v2.environments.Delete, 'environment-update': mistralclient.commands.v2.environments.Update, 'environment-list': mistralclient.commands.v2.environments.List, 'environment-get': mistralclient.commands.v2.environments.Get, 'run-action': mistralclient.commands.v2.action_executions.Create, 'action-execution-list': mistralclient.commands.v2.action_executions.List, 'action-execution-get': mistralclient.commands.v2.action_executions.Get, 'action-execution-get-input': mistralclient.commands.v2.action_executions.GetInput, 'action-execution-get-output': mistralclient.commands.v2.action_executions.GetOutput, 'action-execution-update': mistralclient.commands.v2.action_executions.Update, 'action-execution-delete': mistralclient.commands.v2.action_executions.Delete, 'execution-create': mistralclient.commands.v2.executions.Create, 'execution-delete': mistralclient.commands.v2.executions.Delete, 'execution-update': mistralclient.commands.v2.executions.Update, 'execution-list': mistralclient.commands.v2.executions.List, 'execution-get': mistralclient.commands.v2.executions.Get, 'execution-get-input': mistralclient.commands.v2.executions.GetInput, 'execution-get-output': mistralclient.commands.v2.executions.GetOutput, 'task-list': mistralclient.commands.v2.tasks.List, 'task-get': mistralclient.commands.v2.tasks.Get, 'task-get-published': mistralclient.commands.v2.tasks.GetPublished, 'task-get-result': mistralclient.commands.v2.tasks.GetResult, 'task-rerun': mistralclient.commands.v2.tasks.Rerun, 'action-list': mistralclient.commands.v2.actions.List, 'action-get': mistralclient.commands.v2.actions.Get, 'action-create': mistralclient.commands.v2.actions.Create, 'action-delete': mistralclient.commands.v2.actions.Delete, 'action-update': mistralclient.commands.v2.actions.Update, 'action-get-definition': mistralclient.commands.v2.actions.GetDefinition, 'action-validate': mistralclient.commands.v2.actions.Validate, 'cron-trigger-list': mistralclient.commands.v2.cron_triggers.List, 'cron-trigger-get': mistralclient.commands.v2.cron_triggers.Get, 'cron-trigger-create': mistralclient.commands.v2.cron_triggers.Create, 'cron-trigger-delete': mistralclient.commands.v2.cron_triggers.Delete, 'event-trigger-list': mistralclient.commands.v2.event_triggers.List, 'event-trigger-get': mistralclient.commands.v2.event_triggers.Get, 'event-trigger-create': mistralclient.commands.v2.event_triggers.Create, 'event-trigger-delete': mistralclient.commands.v2.event_triggers.Delete, 'service-list': mistralclient.commands.v2.services.List, 'member-create': mistralclient.commands.v2.members.Create, 'member-delete': mistralclient.commands.v2.members.Delete, 'member-update': mistralclient.commands.v2.members.Update, 'member-list': mistralclient.commands.v2.members.List, 'member-get': mistralclient.commands.v2.members.Get, } def main(argv=sys.argv[1:]): return MistralShell().run(argv) if __name__ == '__main__': sys.exit(main(sys.argv[1:])) python-mistralclient-3.7.0/mistralclient/__init__.py0000666000175000017500000000125613326122347022730 0ustar zuulzuul00000000000000# Copyright 2014 Rackspace Hosting # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import pbr.version __version__ = pbr.version.VersionInfo( 'python-mistralclient').version_string() python-mistralclient-3.7.0/test-requirements.txt0000666000175000017500000000106713326122347022206 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 python-openstackclient>=3.12.0 # Apache-2.0 sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD mock>=2.0.0 # BSD oslotest>=3.2.0 # Apache-2.0 requests-mock>=1.2.0 # Apache-2.0 tempest>=17.1.0 # Apache-2.0 osprofiler>=1.4.0 # Apache-2.0 reno>=2.5.0 # Apache-2.0 stestr>=1.0.0 # Apache-2.0 openstackdocstheme>=1.18.1 # Apache-2.0 python-mistralclient-3.7.0/requirements.txt0000666000175000017500000000104413326122347021224 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. cliff!=2.9.0,>=2.8.0 # Apache-2.0 osc-lib>=1.8.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 pbr!=2.1.0,>=2.0.0 # Apache-2.0 keystoneauth1>=3.4.0 # Apache-2.0 PyYAML>=3.12 # MIT requests>=2.14.2 # Apache-2.0 six>=1.10.0 # MIT stevedore>=1.20.0 # Apache-2.0 python-mistralclient-3.7.0/AUTHORS0000664000175000017500000000716513326122641017015 0ustar zuulzuul00000000000000Adriano Petrich Anastasia Kuznetsova Andras Kovi Andreas Jaeger Angus Salkeld Bob Haddleton Boris Bobrov Brad P. Crochet Cao Xuan Hoang Chen Christian Berendt Corey Bryant Daryl Mowrer David C Kennedy David Vallee Delisle Dawid Deja Dina Belova Dmitri Zimine Doug Hellmann Dougal Matthews Ed Cranford Flavio Percoco Goutham Pratapa GouthamPratapa Hangdong Zhang Hardik Parekh Istvan Imre Jamie Lennox Jeremy Liu Jeremy Stanley Ji zhaoxuan Juan Antonio Osorio Robles KATO Tomoyuki Kevin Pouget Kevin_Zheng Kirill Izotov Kupai József Leandro I. Costantino Limor Stotland Lingxian Kong LingxianKong LingxianKong Lucky samadhiya Marcos Fermin Lobo Michal Gershenzon Michal Gershenzon Mike Fedosin Nguyen Hung Phuong Nikolay Mahotkin OpenStack Release Bot Pierre Gaxatte Pierre-Arthur MATHIEU Prince Katiyar Quentin GROLLEAU Renat Akhmerov Renat Akhmerov Robert Collins Sagi Shnaidman Sharat Sharma Sirushti Murugesan Stephen Finucane Steve Martinelli Tang Chen Tetiana Lashchova Theodoros Tsioutsias Thomas Goirand Thomas Herve TimurNurlygayanov Tony Xu Toure Dunnon Tovin Seven W Chan Winson Chan Zhenguo Niu Zuul fengchaoyang gengchc2 hardik houweichao hparekh pawnesh.kumar pengdake <19921207pq@gmail.com> rakhmerov reedip ricolin ricolin shu-mutou venkatamahesh wangzhenyu wu.chunyang xianming mao yfzhao zhangguoqing python-mistralclient-3.7.0/doc/0000775000175000017500000000000013326122642016502 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/doc/source/0000775000175000017500000000000013326122642020002 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/doc/source/cli/0000775000175000017500000000000013326122642020551 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/doc/source/cli/cli_usage_with_keycloak.rst0000666000175000017500000000010613326122347026154 0ustar zuulzuul00000000000000Using Mistral with KeyCloak Server ================================== python-mistralclient-3.7.0/doc/source/cli/cli_usage_targeting_workflows.rst0000666000175000017500000000405713326122347027431 0ustar zuulzuul00000000000000Using Mistral to execute Workflows on an arbitrary cloud ======================================================== It is possible to execute a workflow on any arbitrary cloud without additional configuration on the Mistral server side. 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 Note on the --os-target_cacert parameter ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The `--os-target_cacert` parameter can be used to set a CA certificate for SSL communication with the target cloud's Keystone service. The CA certificate file is **NOT** transferred to the Mistral server. It is the responsibility of the user to ensure that the SSL Certificate is accessible for the Mistral Executor and SSL communication is possible with the target cloud. For testing purposes it is suggested to use the `--target_insecure` parameter. python-mistralclient-3.7.0/doc/source/cli/cli_usage_source_execution.rst0000666000175000017500000000506013326122347026706 0ustar zuulzuul00000000000000Replicating Workflows with Mistral ================================== The new command line switch '-s' will allow the operator to replicate / clone an existing workflow execution based on its ID. Once id is given mistral will create a new workflow execution based on the parameters of the first, which will provide a simple approach to spawning a number of workflow executions without having to specify inputs or parameters. Otherwise you can override some of the parameters (e.g. some of the input variables) Basic Usage ----------- From the command line the operator will issue the following. The first step would be to list the current executions, which is done with "execution-list". The following step is to take the listed execution id and pass it to the source execution switch "-s". .. code-block:: shell mistral execution-list mistral execution-create -s Once the workflow execution is selected and the replicate command used you should see a newly created workflow execution based on an existing one with a new execution id. .. code-block:: shell mistral execution-create -s 123e4567-e89b-12d3-a456-426655440000 +--------------------+---------------------------------------+ | Field | Value | +--------------------+---------------------------------------+ | ID | 123e4567-e89b-12d3-a456-77046883182e | | | | | Workflow ID | 123e4567-e89b-12d3-a456-45411dfa33af | | | | | Workflow name | some.workflow.name.goes.here | | | | | Workflow namespace | | | | | | Description | | | | | | Task Execution ID | | | | | | State | RUNNING | | | | | State info | None | | | | | Created at | 2018-01-25 18:41:07 | | | | | Updated at | 2018-01-25 18:41:07 | +--------------------+---------------------------------------+ python-mistralclient-3.7.0/doc/source/cli/cli_usage_with_openstack.rst0000666000175000017500000000233013326122347026342 0ustar zuulzuul00000000000000Using Mistral with OpenStack ============================ The **mistral** shell utility interacts with OpenStack Mistral API from the command-line. It supports the features in the OpenStack Mistral API. Basic Usage ----------- In order to use the CLI, you must provide your OpenStack credentials (for both user and project), and auth endpoint. Use the corresponding configuration options (``--os-username``, ``--os-password``, ``--os-project-name``, ``--os-user-domain-id``, ``os-project-domain-id``, and ``--os-auth-url``), but it is easier to set them in environment variables. .. code-block:: shell $ export OS_AUTH_URL=http://:5000/v2.0 $ export OS_USERNAME=admin $ export OS_TENANT_NAME=tenant $ export OS_PASSWORD=secret $ export OS_MISTRAL_URL=http://:8989/v2 When authenticating against keystone over https: .. code-block:: shell $ export OS_CACERT= Once you've configured your authentication parameters, you can run **mistral** commands. All commands take the form of:: mistral [arguments...] Run **mistral --help** to get a full list of all possible commands, and run **mistral help ** to get detailed help for that command. python-mistralclient-3.7.0/doc/source/class_reference.rst0000666000175000017500000000017613326122347023667 0ustar zuulzuul00000000000000Python Mistral bindings class refrence ====================================== .. toctree:: :maxdepth: 1 api/autoindex python-mistralclient-3.7.0/doc/source/conf.py0000666000175000017500000000713013326122347021306 0ustar zuulzuul00000000000000# Mistral documentation build configuration file import os import pbr.version import sys on_rtd = os.environ.get('READTHEDOCS', None) == 'True' # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('../../')) sys.path.insert(0, os.path.abspath('../')) sys.path.insert(0, os.path.abspath('./')) # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', 'openstackdocstheme', ] # Add any paths that contain templates here, relative to this directory. # templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Mistral Client' copyright = u'2016, Mistral Contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. version_info = pbr.version.VersionInfo('python-mistralclient') release = version_info.release_string() version = version_info.version_string() # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. modindex_common_prefix = ['mistralclient.'] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'openstackdocs' # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # Must set this variable to include year, month, day, hours, and minutes. html_last_updated_fmt = '%Y-%m-%d %H:%M' # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". html_title = 'MistralClient' # Custom sidebar templates, maps document names to template names. html_sidebars = { 'index': [ 'sidebarlinks.html', 'localtoc.html', 'searchbox.html', 'sourcelink.html' ], '**': [ 'localtoc.html', 'relations.html', 'searchbox.html', 'sourcelink.html' ] } # Output file base name for HTML help builder. htmlhelp_basename = 'Mistraldoc' # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'mistral_client', u'Mistral Client Documentation', [u'Mistral Contributors'], 1) ] # -- Options for openstackdocstheme ------------------------------------------- repository_name = 'openstack/python-mistralclient' bug_project = 'python-mistralclient' bug_tag = '' python-mistralclient-3.7.0/doc/source/index.rst0000666000175000017500000000234313326122347021651 0ustar zuulzuul00000000000000Python bindings to the OpenStack Workflow API ============================================= This is a client for OpenStack Mistral API. There's a Python API (the :mod:`mistralclient` module), and a command-line script (installed as :program:`mistral`). Using mistralclient ------------------- .. toctree:: :maxdepth: 2 cli/cli_usage_with_openstack cli/cli_usage_with_keycloak cli/cli_usage_targeting_workflows cli/cli_usage_source_execution class_reference For information about using the mistral command-line client, see `Workflow service command-line client`_. .. _Workflow service command-line client: https://docs.openstack.org/mistral/latest/cli/index.html Python API Reference -------------------- * `REST API Specification`_ .. _REST API Specification: https://docs.openstack.org/mistral/latest/api/v2.html Contributing ------------ Code is hosted `on GitHub`_. Submit bugs to the python-mistralclient project on `Launchpad`_. Submit code to the openstack/python-mistralclient project using `Gerrit`_. .. _on GitHub: https://github.com/openstack/python-mistralclient .. _Launchpad: https://launchpad.net/python-mistralclient .. _Gerrit: https://docs.openstack.org/infra/manual/developers.html#development-workflow python-mistralclient-3.7.0/functional_creds.conf.sample0000666000175000017500000000026513326122347023415 0ustar zuulzuul00000000000000# Credentials for functional testing [auth] uri = http://10.42.0.50:5000/v2.0 [admin] user = admin tenant = admin pass = secrete [demo] user = demo tenant = demo pass = demo_secretpython-mistralclient-3.7.0/RELEASENOTES.rst0000664000175000017500000000213013326122642020354 0ustar zuulzuul00000000000000==================== python-mistralclient ==================== .. _python-mistralclient_3.6.0: 3.6.0 ===== .. _python-mistralclient_3.6.0_Bug Fixes: Bug Fixes --------- .. releasenotes/notes/fix-regression-with-execution-force-delete-af8d1968cb2673ef.yaml @ b'146d1c17e24f936f8bc365c69bf9f72a084e62ae' - mistralclient 3.5.0 introduced a new --force option to delete executions that are still running. However, this had the unintended impact of passing force=false when it wasn't provided. This is incompatible with previous releases of the Mistral API which reject requests as they don't recognise "force". .. _python-mistralclient_3.5.0: 3.5.0 ===== .. _python-mistralclient_3.5.0_New Features: New Features ------------ .. releasenotes/notes/force-delete-executions-d08ce88a5deb3291.yaml @ b'e400bed6b0888247eafc90ff338165cfe01e037f' - Adding a --force optional parameter to delete excetutions. Without it only finished executions can be deleted. If --force is passed the execution will be deleted but mistral will generate some errors as expected objects in memory no longer exist python-mistralclient-3.7.0/tox.ini0000666000175000017500000000344513326122347017262 0ustar zuulzuul00000000000000[tox] envlist = py35,py27,pep8 minversion = 1.6 skipsdist = True [testenv] usedevelop = True install_command = pip install {opts} {packages} setenv = VIRTUAL_ENV={envdir} NOSE_WITH_OPENSTACK=1 NOSE_OPENSTACK_COLOR=1 NOSE_OPENSTACK_RED=0.05 NOSE_OPENSTACK_YELLOW=0.025 NOSE_OPENSTACK_SHOW_ELAPSED=1 NOSE_OPENSTACK_STDOUT=1 NOSE_XUNIT=1 DISCOVER_DIRECTORY=mistralclient/tests/unit passenv = http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = find . -type f -name "*.pyc" -delete stestr run --concurrency 1 {posargs} whitelist_externals = find rm [testenv:functional] setenv = OS_TEST_PATH = ./mistralclient/tests/functional commands = {posargs} [testenv:pep8] basepython = python3 commands = python setup.py check --restructuredtext --strict flake8 {posargs} [testenv:venv] basepython = python3 commands = {posargs} [testenv:docs] basepython = python3 commands = rm -rf doc/html doc/build rm -rf doc/source/apidoc doc/source/api python setup.py build_sphinx [flake8] # H106: Don’t put vim configuration in source files # H203: Use assertIs(Not)None to check for None enable-extensions=H106,H203 show-source = true builtins = _ exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,tools [testenv:releasenotes] basepython = python3 commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:lower-constraints] basepython = python3 deps = -c{toxinidir}/lower-constraints.txt -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt python-mistralclient-3.7.0/PKG-INFO0000664000175000017500000001120013326122642017024 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: python-mistralclient Version: 3.7.0 Summary: Mistral Client Library Home-page: https://docs.openstack.org/python-mistralclient/latest/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: Apache Software License Description: ======================== 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.org/project/python-mistralclient/ :alt: Latest Version Mistral is a workflow service. Most business processes consist of multiple distinct interconnected steps that need to be executed in a particular order in a distributed environment. A user can describe such a process as a set of tasks and their transitions. After that, it is possible to upload such a description to Mistral, which will take care of state management, correct execution order, parallelism, synchronization and high availability. Mistral also provides flexible task scheduling so that it can run a process according to a specified schedule (for example, every Sunday at 4.00pm) instead of running it immediately. In Mistral terminology such a set of tasks and relations between them is called a workflow. Mistral client ============== Python client for Mistral REST API. Includes python library for Mistral API and Command Line Interface (CLI) library. Installation ------------ First of all, clone the repo and go to the repo directory:: $ git clone git://git.openstack.org/openstack/python-mistralclient.git $ cd python-mistralclient Then just run:: $ pip install -e . or:: $ pip install -r requirements.txt $ python setup.py install Running Mistral client ---------------------- If Mistral authentication is enabled, provide the information about OpenStack auth to environment variables. Type:: $ export OS_AUTH_URL=http://:5000/v2.0 $ export OS_USERNAME=admin $ export OS_TENANT_NAME=tenant $ export OS_PASSWORD=secret $ export OS_MISTRAL_URL=http://:8989/v2 (optional, by default URL=http://localhost:8989/v2) and in the case that you are authenticating against keystone over https: $ export OS_CACERT= .. note:: In client, we can use both Keystone auth versions - v2.0 and v3. But server supports only v3.* To make sure Mistral client works, type:: $ mistral workbook-list You can see the list of available commands typing:: $ mistral --help Useful Links ============ * `PyPi`_ - package installation * `Launchpad project`_ - release management * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ * `Specs`_ * `How to Contribute`_ .. _PyPi: https://pypi.org/project/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/ .. _Release Notes: https://docs.openstack.org/releasenotes/python-mistralclient Platform: UNKNOWN Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux python-mistralclient-3.7.0/functionaltests/0000775000175000017500000000000013326122642021162 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/functionaltests/run_tests.sh0000777000175000017500000000373113326122347023557 0ustar zuulzuul00000000000000#!/bin/bash # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # How many seconds to wait for the API to be responding before giving up API_RESPONDING_TIMEOUT=20 if ! timeout ${API_RESPONDING_TIMEOUT} sh -c "until curl --output /dev/null --silent --head --fail http://localhost:8989; do sleep 1; done"; then echo "Mistral API failed to respond within ${API_RESPONDING_TIMEOUT} seconds" exit 1 fi echo "Successfully contacted Mistral API" export BASE=/opt/stack export MISTRALCLIENT_DIR="$BASE/new/python-mistralclient" # Get demo credentials. cd ${BASE}/new/devstack source openrc alt_demo alt_demo export OS_ALT_USERNAME=${OS_USERNAME} export OS_ALT_TENANT_NAME=${OS_TENANT_NAME} export OS_ALT_USER_DOMAIN_NAME=${OS_USER_DOMAIN_NAME} export OS_ALT_PROJECT_DOMAIN_NAME=${OS_PROJECT_DOMAIN_NAME} export OS_ALT_PASSWORD=${OS_PASSWORD} # Get admin credentials. source openrc admin admin # Store these credentials into the config file. CREDS_FILE=${MISTRALCLIENT_DIR}/functional_creds.conf cat < ${CREDS_FILE} # Credentials for functional testing [auth] uri = $OS_AUTH_URL [admin] user = $OS_USERNAME tenant = $OS_TENANT_NAME pass = $OS_PASSWORD user_domain = $OS_USER_DOMAIN_NAME project_domain = $OS_PROJECT_DOMAIN_NAME [demo] user = $OS_ALT_USERNAME tenant = $OS_ALT_TENANT_NAME pass = $OS_ALT_PASSWORD user_domain = $OS_ALT_USER_DOMAIN_NAME project_domain = $OS_ALT_PROJECT_DOMAIN_NAME EOF cd $MISTRALCLIENT_DIR # Run tests tox -efunctional -- nosetests -sv mistralclient/tests/functional python-mistralclient-3.7.0/functionaltests/post_test_hook.sh0000777000175000017500000000146613326122347024600 0ustar zuulzuul00000000000000#!/bin/bash # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This script is executed inside post_test_hook function in devstack gate. RETVAL=0 sudo chmod -R a+rw /opt/stack/new/ cd /opt/stack/new/python-mistralclient echo "Running CLI python-mistralclient tests" ./functionaltests/run_tests.sh RETVAL=$? exit $RETVAL python-mistralclient-3.7.0/functionaltests/resources/0000775000175000017500000000000013326122642023174 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/functionaltests/resources/v2/0000775000175000017500000000000013326122642023523 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/functionaltests/resources/v2/wf_delay_v2.yaml0000666000175000017500000000051013326122347026610 0ustar zuulzuul00000000000000--- version: '2.0' wf: type: direct tags: [tag] tasks: hello: action: std.echo output="Hello" publish: result: <% task(hello).result %> wait-after: 1 on-success: - task2 task2: action: std.echo output="Task 2" publish: task2: <% task(task2).result %> python-mistralclient-3.7.0/functionaltests/resources/v2/action_v2.yaml0000666000175000017500000000044413326122347026301 0ustar zuulzuul00000000000000--- version: "2.0" greeting: description: "This action says 'Hello'" base: std.echo base-input: output: 'Hello, <% $.name %>' input: - name output: string: <% $.output %> farewell: base: std.echo base-input: output: 'Bye!' output: info: <% $.output %> python-mistralclient-3.7.0/functionaltests/resources/v2/wb_v2.yaml0000666000175000017500000000045113326122347025432 0ustar zuulzuul00000000000000--- version: '2.0' name: wb actions: ac1: input: - str1 - str2 base: std.echo output="<% $.str1 %><% $.str2 %>" workflows: wf1: type: direct tasks: hello: action: std.echo output="Hello" publish: result: <% task(hello).result %> python-mistralclient-3.7.0/functionaltests/resources/v2/wf_wrapping_wf_v2.yaml0000666000175000017500000000012713326122347030041 0ustar zuulzuul00000000000000--- version: '2.0' wrapping_wf: type: direct tasks: hello: workflow: wf python-mistralclient-3.7.0/functionaltests/resources/v2/wf_single_v2.yaml0000666000175000017500000000024113326122347026774 0ustar zuulzuul00000000000000--- version: '2.0' wf_single: type: direct tasks: hello: action: std.echo output="Hello" publish: result: <% task(hello).result %> python-mistralclient-3.7.0/functionaltests/resources/v2/async.yaml0000666000175000017500000000014313326122347025526 0ustar zuulzuul00000000000000--- version: '2.0' async_wf: type: direct tasks: async_task: action: std.async_noop python-mistralclient-3.7.0/functionaltests/resources/v2/for_namespaces/0000775000175000017500000000000013326122642026510 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/functionaltests/resources/v2/for_namespaces/top_level_wf.yaml0000666000175000017500000000013713326122347032066 0ustar zuulzuul00000000000000--- version: '2.0' top_level_wf: type: direct tasks: hello: workflow: middle_wf python-mistralclient-3.7.0/functionaltests/resources/v2/for_namespaces/middle_wf.yaml0000666000175000017500000000014213326122347031327 0ustar zuulzuul00000000000000--- version: '2.0' middle_wf: type: direct tasks: hello: workflow: lowest_level_wf python-mistralclient-3.7.0/functionaltests/resources/v2/for_namespaces/lowest_level_wf.yaml0000666000175000017500000000013713326122347032601 0ustar zuulzuul00000000000000--- version: '2.0' lowest_level_wf: type: direct tasks: hello: action: std.noop python-mistralclient-3.7.0/functionaltests/resources/v2/action_v2_tags.yaml0000666000175000017500000000032713326122347027317 0ustar zuulzuul00000000000000--- version: "2.0" greeting: description: "This action says 'Hello'" tags: [tag, tag1] base: std.echo base-input: output: 'Hello, <% $.name %>' input: - name output: string: <% $.output %> python-mistralclient-3.7.0/functionaltests/resources/v2/wf_v2.yaml0000666000175000017500000000112113326122347025431 0ustar zuulzuul00000000000000--- version: '2.0' wf: type: direct tasks: hello: action: std.echo output="Hello" wait-before: 1 publish: result: <% task().result %> on-success: bye bye: action: std.echo output="Bye" publish: result: <% $.result + ', ' + task().result %> wf1: type: reverse tags: [tag] input: - farewell tasks: addressee: action: std.echo output="John" publish: name: <% task(addressee).result %> goodbye: action: std.echo output="<% $.farewell %>, <% $.name %>" requires: [addressee] python-mistralclient-3.7.0/functionaltests/resources/v2/wb_with_tags_v2.yaml0000666000175000017500000000031213326122347027477 0ustar zuulzuul00000000000000--- version: '2.0' name: wb tags: [tag] workflows: wf1: type: direct tasks: hello: action: std.echo output="Hello" publish: result: <% task(hello).result %> python-mistralclient-3.7.0/ChangeLog0000664000175000017500000004547713326122641017527 0ustar zuulzuul00000000000000CHANGES ======= 3.7.0 ----- * Clarify details about the target cacert parameter * Add missing \`--public\` option to workbook api * Add the root execution ID to the CLI output * Add missing oslo.serialization requirement 3.6.1 ----- * Fixing region filter for getting workflow endpoint * Add release note link in README * Add the restructuredtext check to the flake8 job 3.6.0 ----- * Remove PyPI downloads * Fix force parameter being always added to URL in execution delete * Add --export option to environment-get * Revert "Adding environment-get-definition for easy environment-update" * fix tox python3 overrides * Switch to using stestr * Update task columns info * Add namespace parameter to workflow-get and workflow-get-definition * Adding environment-get-definition for easy environment-update 3.5.0 ----- * Trivial: Update pypi url to new url * Do not let keystoneauth mask the errors * Add the force parameter to delete executions 3.4.0 ----- * add lower-constraints job * Updated from global requirements * Updated from global requirements * Updated from global requirements * Add the scope attribute in workflow list * Updated from global requirements * Updated from global requirements * Clean imports in code * Updated from global requirements * Add TripleO jobs for mistral client * Don't override session during auth * Fix a race condition with execution creation * Remove broken tox-cover job * Update reno for stable/queens * Running new workflow based on existing execution 3.2.0 ----- * Clean up keystone authentication * Restore devstack job for python-mistralclient * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Avoid tox\_install.sh for constraints support * Fix limit handling to not send value of -1 * Remove setting of version/release from releasenotes * Updated from global requirements * Updated from global requirements * Support no\_auth mode in mistral client * Fix several problems in keycloak auth module * Don't create client for help and bash completion * Remove "insecure" in token-based authentication * Updated from global requirements * Fix a typo in env variable name * Add default values for domain related options * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Enable ssl support for keycloak auth module * Support for Project\_domain\_id and user\_domain\_id * Updated from global requirements * Updated from global requirements * Namespaces release note * Create and run a workflow within a namespace * Updated from global requirements * Task list now only queries the displayed fields * Use keystoneauth plugins and session instead of keystoneclient * Fix how "--limit" is passed to the server for action executions * mistral execution-list -f value should be empty * Add test for target parameters and fix requests lib error * Update reno for stable/pike * Updated from global requirements * Make README better 3.1.1 ----- * String interpolation should be delayed * Give better tox output * Apply Pike document structure * Updated from global requirements * Update and optimize documentation links * Enable warning-is-error in doc build * Updated from global requirements * Add CLI for event trigger operations * Set the default value of --limit parameter * Switch from oslosphinx to openstackdocstheme * Updated from global requirements * Enable some off-by-default checks * Make --profile load from environment variables * Updated from global requirements * Updated from global requirements * Replace request mocking in test\_httpclient with requests-mock * Updated from global requirements * Updated from global requirements * Change service type to workflowv2 * Updated from global requirements * Updated from global requirements * Updated from global requirements * Add a missing space to the help message for execution-create * fix release note formatting * Explicitly set 'builders' option * doc: Remove cruft from conf.py * Fix doc generation for python 3 3.1.0 ----- * Updated from global requirements * Optimize the link address * Add release note for region name bugfix * Take DST into account when converting to UTC * Fix region support in mistralclient * Update the gitingore * Remove log translations * Use session from OSC plugin * Accept keystone session in client * Use generic keystone client instead of versioned * Add --utc flag to cron trigger create * Updated from global requirements * Update test requirement * Updated from global requirements * Remove support for py34 * Updated from global requirements * Updated from global requirements * Add project\_id and project\_name options * Read project\_id and project\_name in env * Update reno for stable/ocata * Format the list of auth types in the Mistral client help * Added link for modindex * Fix doc build if git is absent * Changed the README.rst 3.0.0 ----- * Pass target insecure flag to msitral service * Updated from global requirements * Add user and project domain name parameters for target cloud * Update .gitignore * Removes unnecessary utf-8 encoding * Initial commit for mistral-i18n support * Add tests for filters in CLI list commands * Cosmetic changes in CLI tests * Add '--filter' parameter to list commands in CLI * Add filters to client Python APIs * Use assertGreater() or assertLess() * Keystone v3 needs extra parameters * Initial commit for python-mistralclient document * Remove commented-out Apache 2 classifier from setup.cfg * Make python mistralclient readme better * Filter workflow executions by creating task execution id * Updated from global requirements * Fix for failing dsvm gate * Move json.loads() method to utils.py, and use "with" to deal with file objects * Make python mistralclient readme better * Show team and repo badges on README * Updated tox.ini to pick up requirements from upper constraints * Region name related command line arguments are added * Replace uuid4() with generate\_uuid() from oslo\_utils * Removing deprecation warnings to pass py35 * Removed the extra space from tox.ini * Updated from global requirements * Remove unused pylint * Added the reno for stable/mitaka stable/newton and stable/liberty * Add cancelled state to action executions * Remove unused scripts in tools * Updated from global requirements * remove apiclient from mistralclient * remove openstack/common/cliutils.py * Updated from global requirements * Remove unused openstack/common/apiclient/client * Removed openstack/common/importutils.py * Updated from global requirements * Fix python35 job failures * Adding files to .gitignore * Enable DeprecationWarning in test environments * Add Python 3.5 classifier and venv * Updated from global requirements * Fixed errors while generating releasenotes * Replaced savanna word with mistralclient * Updated from global requirements * Fix warning when running tox -e docs * Remove white space between print () in cliutils.py and lintstack.py * Add plug-in summary for osc doc * Updated from global requirements * Fix PEP8 issues and incorrect version/release details * Enable release notes translation * Updated from global requirements * Expose the --run-sync Action Execution parameter on the CLI * Added the # -\*- coding: utf-8 -\*- to the conf.py * Updated from global requirements * Updated from global requirements * Send access info to server * Maintain releasenotes for python-mistralclient * Refactor common parts of client tests * Updated from global requirements * Avoid use xx=[] for parameter to initialize it's value * Trivial: Remove vim header from source files * Remove clashes of openstackclient command in mistral * Abstract authentication function * Add 'created\_at' and 'updated\_at' to action-execution-get and action-execution-list command Closes-bug: 1618767 Change-Id: I422fdcdfa66d6b7a781542c7acc458f8c46edb18 * Added sphinix config to setup.cfg * Updated from global requirements * Add 'created\_at' and 'updated\_at' to task-get command * Pass httpclient to managers * Use requests-mock for testing * Use oslotest instead of testtools/unittest * Fixing auth for keystone v2.0 * Fix wrong error message for environment operation * Make osprofiler dependency really "soft" * Fixing getting mistral\_url from keystone catalog 2.1.1 ----- * Make osprofiler dependency "soft" * Clean imports in code * TrivialFix: Remove logging import unused * Updated from global requirements 2.1.0 ----- * Updated from global requirements * Updated from global requirements * Added ID option to update Action Definition * Add error message when OS\_USERNAME or OS\_PASSWORD not provided * Add target-\* parameters to python-mistralclient * Different formatters for "action-execution-get" and "action-execution-list" * Add "Task ID" field for "action-execution-get" command output * Remove usage of private '\_url' property from OSC * Add cancelled state to executions * Added 'pip install -r requirements.txt' instruction * Updated from global requirements * Fixed ssl options for httpclient * Changed argument names as per other python clients * Change action-get help to get action info by ID * Updated from global requirements * Add KeyCloak OpenID Connect authentication * Updated from global requirements * Use osc\_lib instead of cliff * Use osc-lib instead of openstackclient * Updated from global requirements * Removed use of tempest\_lib and used tempest instead * Updated from global requirements * Remove .mailmap since it's no longer needed * Updated from global requirements * Add osprofiler option to trace operations * Remove AUTHORS file from git tracking * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Added CONTRIBUTING.rst file * Validate ad-hoc action via cli * Updated from global requirements * Updated from global requirements * Updated from global requirements * Fix task result syntax in workflows used for functional tests * Updated from global requirements * Fix cacert and insecure options on HTTP requests * Change the mistralclient for Mistral action pack 2.0.0 ----- * Functional tests for workflow sharing * Fix show member error * Updated from global requirements * Support resource sharing CLI * Updated from global requirements * Support workflow id for execution CLI * Updated from global requirements * Support ID for workflow operations in CLI * Support workflow id for cron trigger creation * Add task\_execution\_id to workflow execution in CLI * Updated from global requirements * Updated from global requirements * Fix db error when running python34 unit tests * Add env option to CLI for executions and tasks update * Updated from global requirements * Updated from global requirements * Drop py33 support * Updated from global requirements * Updated from global requirements * Pass environment variables of proxy to tox * Updated from global requirements * Delete python bytecode before every test run * Remove py26 support 1.2.0 ----- * Updated from global requirements * Introduce openstackclient plugin * Show project id when retrieving workflow(s) * Show workflow\_id when getting workflow(s) * Add post\_test\_hook script that will run tests on the gate * Fix all H201 pep8 errors * Adding --insecure flag * Make help message of execution pagination query more readable * Create pagination for the mistral client * Wrapped long lines in the "Tags" column * Truncate 'input' data in the wf/action update command output * Changing attribute of run\_tests script * Added home-page link in setup.cfg file * Fix dict ordering issue in action executions CLI test * Updated from global requirements * Updated from global requirements * Adding correct version info to mistralclient * Fixing dict comparison in task CLI tests * Updated from global requirements * Show exact error message when authentication falied instead of HTML body * Fix support of dev use case with no auth * Add state info to tasks list * Add .mailmap for pbr AUTHORS generation * Update AUTHORS file * Updated from global requirements * Fix order of arguments in assertEqual * Corrected output when env description is not provided * Output format is corrected for wf-validate and wb-validate * Adding pagination support to mistral python client * Added some tests for environment * Default auth\_url is set, if it is not provided * Updated from global requirements 1.1.0 ----- * Fixing test failing on workflow-delete * Updated from global requirements * Provide an ability to make action/workflow public * Removed unused functional test * Fix order of arguments in assertEqual 1.0.2 ----- * Remove invalid test case of mistralclient * Updated from global requirements * Fix failure of run\_action CLI command * Support action\_execution deletion on client side * Fixing dict ordering issue in task tests * Update README.md for project namespace and repo change * Add service API support in client side 1.0.1 ----- * Add CLI for rerunning failed task execution * Add python 3 classifiers * Fix PY3 compatibility * Adding to\_dict() method to Resource class * Changing check of mistral availability in functional tests * User 'alt\_demo' user instead of 'demo' for cli tests * Fixing launching mistralclient tests * Configure logging with debug flag 1.0.0 ----- * Update requirements to unwedge solum * Append newlines to two new test files * Implementing run-action command in client * fix mistral-client use does not use endpoint keystone: * Add description param for execution create/update * Update .gitreview file for project rename * Remove unnecessary validation in update environment form the client * Make create commands cut long output * Mistral bash completion script optimization * Add bash-completion command support * Fix cli integration tests * Update requirements for master branch * Delete bash completion script auto placement * Adding '--params' to cron-trigger CLI * Support resource deletion in batches * Update cli integration test due to last changes * Modified help display format to present a more user-friendly display * Update commands bash completion script * Adding 'Task name' in Action Execution CLI * Fixing state\_info column in CLI * Delete requirement on unneeded arguments 2015.1.0rc1 ----------- * Remove "policies" keyword from test resources * Fixing a type in execution-create command help * Removing v1 stuff (API methods, commands, tests) * Add workbook and workflow validate commands * Updating AUTHORS * Add functional CLI tests for 'action-execution' endpoint * Fixing task CLI * Added tempest-lib to test-requirements.txt * Fixing tasks operations in mistralclient * Add action execution client lib and CLI * Fix failing after refactoring CLI test for execution * Trigger remaining-executions and first-exec-date * Rename execution\_id to workflow\_execution\_id * add bash completion script 2015.1.0b3 ---------- * Refactor task output: "wf\_name" -> "workflow\_name" * Deleting command 'get-task-output' from CLI * Adjust all test examples in mistralclient * Import 'decorators' from tempest\_lib instead of 'test' from tempest * Add support for auth against keystone on https * Fix tempest gate, add tempest import * Add tests which check env isolation * Add CLI tests for environments 2015.1.0b2 ---------- * Extend cli integration tests for update * Add error code to APIException * Fix import in functional tests * Implement commands for execution environment * Fix client initialization in the integration tests 2015.1.0b1 ---------- * Add mistralclient documentation * Add new functional CLI tests * Use YAML text instead of JSON in HTTP body (client side) * Add CLI multi tenancy tests * Fix pep8 issues * Fix execution-update CLI test * Refactor CLI tests * Add CLI tests for actions * Updating AUTHORS file 0.1.1 ----- * Fix CLI cron-trigger tests * Fix passing workflow input via UI * Fix execution state choices and commenting workflow input in triggers * Add CLI tests for triggers * Adding cron triggers * Provide workflow input * Adjust action commands (CLI) * Add script to run functional CLI and client tests locally * Fix 'is\_system' property in action commands * Refactoring CLI commands returning lists * Adding 'tags' to actions * Update requirements according to global requirements (master) 0.1 --- * Fix CLI v2 tests for workflows * Fix CLI v2 test for workbooks and executions * Getting rid of 'name' and 'tags' for workbook create/update * Removing 'name' and 'tags' from action API and CLI * Removing 'name' and 'tags' from workflow API and CLI * Add 'name' to test workbook * Renaming 'parameters' to 'input' * Fix client tests due to changes in code from 'mistral' repository * Add task-get-parameters command * Minor changes in tests files * Fix workflow-update command * Support naive filtering in python API * Fixed execution create method * Add actions API and CLI commands * Add CLI integration tests for v2 * Add hacking to the flake8 tests * Add unit tests on client CLI v2 * Add CLI for client v2 * Add unit tests on v2 client * Implement python-mistralclient v2 * Work toward Python 3.4 support and testing * Move integration tests under mistralclient/tests folder * Add negative tests for CLI * Fix failing 'get\_task' test * Add CLI tests for workbook, execution and task * Add simple tests for Mistral CLI * Remove unneeded definitions of Python Source Code Encoding * Fix typo in base.py * Fix path to integrational tests * Support v2 Keystone API in client * Override configure\_logging to quieten iso8601 and requests * Add integrational tests for executions and tasks * Add integration tests (actions with workbooks) * Update requirements due to global-requirements 0.0.4 ----- * Modify API to make use of /executions endpoint * Removing horizon plugin code base from client * Correct the name that the client is installed as * Remove the log module and all dependencies * Update openstack/common code * Don't use oslo logging in the client * change assertEquals to assertEqual * "target\_task" -> "task" in tests * cleaning up index.rst file * Handle error from upload definition command * Rename Target to Task in execution format * Print status code of requests * Return response as error if not valid json object 0.0.2 ----- * Fixed issues with tarballs 0.0.1 ----- * Replace Task text field with drop-down list * Add list of tasks for an execution * Add authentication to dashboard * Temporarily harbouring Horizon Dashboard * Add upload workbook definition * Add uploading context as a file * Update oslo-incubator dependencies * Add script to allow update dependencies in all envs * Fix context serialization in Execution.create * Modify API tests to check the calls * Fix a bug with empty context * Made keystone authentication optional * Add command-line interface * Add context parameter in execution creation * Rename "target\_task" to "task" * Adding changes related to Data Flow * Fix installation with requirements * Moving Mistral Client code from main Mistral repository * Adding .gitignore file * Initial commit python-mistralclient-3.7.0/CONTRIBUTING.rst0000666000175000017500000000105213326122347020400 0ustar zuulzuul00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps documented at: https://docs.openstack.org/infra/manual/developers.html Once those steps have been completed, changes to OpenStack should be submitted for review via the Gerrit tool, following the workflow documented at: https://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. Bugs should be filed on Launchpad, not GitHub: https://bugs.launchpad.net/python-mistralclient python-mistralclient-3.7.0/setup.py0000666000175000017500000000200613326122347017451 0ustar zuulzuul00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools # In python < 2.7.4, a lazy loading of package `pbr` will break # setuptools if some other modules registered functions in `atexit`. # solution from: http://bugs.python.org/issue15881#msg170215 try: import multiprocessing # noqa except ImportError: pass setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) python-mistralclient-3.7.0/.stestr.conf0000666000175000017500000000007213326122347020211 0ustar zuulzuul00000000000000[DEFAULT] test_path=./mistralclient/tests/unit top_dir=./ python-mistralclient-3.7.0/LICENSE0000666000175000017500000002363613326122347016760 0ustar zuulzuul00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. python-mistralclient-3.7.0/README.rst0000666000175000017500000000613013326122347017430 0ustar zuulzuul00000000000000======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/badges/python-mistralclient.svg :target: https://governance.openstack.org/reference/tags/index.html Mistral ======= .. image:: https://img.shields.io/pypi/v/python-mistralclient.svg :target: https://pypi.org/project/python-mistralclient/ :alt: Latest Version Mistral is a workflow service. Most business processes consist of multiple distinct interconnected steps that need to be executed in a particular order in a distributed environment. A user can describe such a process as a set of tasks and their transitions. After that, it is possible to upload such a description to Mistral, which will take care of state management, correct execution order, parallelism, synchronization and high availability. Mistral also provides flexible task scheduling so that it can run a process according to a specified schedule (for example, every Sunday at 4.00pm) instead of running it immediately. In Mistral terminology such a set of tasks and relations between them is called a workflow. Mistral client ============== Python client for Mistral REST API. Includes python library for Mistral API and Command Line Interface (CLI) library. Installation ------------ First of all, clone the repo and go to the repo directory:: $ git clone git://git.openstack.org/openstack/python-mistralclient.git $ cd python-mistralclient Then just run:: $ pip install -e . or:: $ pip install -r requirements.txt $ python setup.py install Running Mistral client ---------------------- If Mistral authentication is enabled, provide the information about OpenStack auth to environment variables. Type:: $ export OS_AUTH_URL=http://:5000/v2.0 $ export OS_USERNAME=admin $ export OS_TENANT_NAME=tenant $ export OS_PASSWORD=secret $ export OS_MISTRAL_URL=http://:8989/v2 (optional, by default URL=http://localhost:8989/v2) and in the case that you are authenticating against keystone over https: $ export OS_CACERT= .. note:: In client, we can use both Keystone auth versions - v2.0 and v3. But server supports only v3.* To make sure Mistral client works, type:: $ mistral workbook-list You can see the list of available commands typing:: $ mistral --help Useful Links ============ * `PyPi`_ - package installation * `Launchpad project`_ - release management * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ * `Specs`_ * `How to Contribute`_ .. _PyPi: https://pypi.org/project/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/ .. _Release Notes: https://docs.openstack.org/releasenotes/python-mistralclient python-mistralclient-3.7.0/lower-constraints.txt0000666000175000017500000000402313326122347022176 0ustar zuulzuul00000000000000alabaster==0.7.10 amqp==2.1.1 appdirs==1.3.0 asn1crypto==0.23.0 Babel==2.3.4 cachetools==2.0.0 cffi==1.7.0 cliff==2.8.0 cmd2==0.8.0 contextlib2==0.4.0 cryptography==2.1 debtcollector==1.2.0 decorator==3.4.0 deprecation==1.0 docutils==0.11 dogpile.cache==0.6.2 dulwich==0.15.0 eventlet==0.18.2 extras==1.0.0 fasteners==0.7.0 fixtures==3.0.0 flake8==2.5.5 future==0.16.0 futurist==1.2.0 greenlet==0.4.10 hacking==0.12.0 idna==2.6 imagesize==0.7.1 iso8601==0.1.11 Jinja2==2.10 jmespath==0.9.0 jsonpatch==1.16 jsonpointer==1.13 jsonschema==2.6.0 keystoneauth1==3.4.0 kombu==4.0.0 linecache2==1.0.0 MarkupSafe==1.0 mccabe==0.2.1 mock==2.0.0 monotonic==0.6 mox3==0.20.0 msgpack-python==0.4.0 munch==2.1.0 netaddr==0.7.18 netifaces==0.10.4 nose==1.3.7 openstackdocstheme==1.18.1 openstacksdk==0.11.2 os-client-config==1.28.0 os-service-types==1.2.0 os-testr==1.0.0 osc-lib==1.8.0 oslo.concurrency==3.25.0 oslo.config==5.2.0 oslo.context==2.19.2 oslo.i18n==3.15.3 oslo.log==3.36.0 oslo.messaging==5.29.0 oslo.middleware==3.31.0 oslo.serialization==2.18.0 oslo.service==1.24.0 oslo.utils==3.33.0 oslotest==3.2.0 osprofiler==1.4.0 paramiko==2.0.0 Paste==2.0.2 PasteDeploy==1.5.0 pbr==2.0.0 pep8==1.5.7 pika-pool==0.1.3 pika==0.10.0 positional==1.2.1 prettytable==0.7.2 pyasn1==0.1.8 pycparser==2.18 pyflakes==0.8.1 Pygments==2.2.0 pyinotify==0.9.6 pyOpenSSL==17.1.0 pyparsing==2.1.0 pyperclip==1.5.27 python-cinderclient==3.3.0 python-dateutil==2.5.3 python-glanceclient==2.8.0 python-keystoneclient==3.8.0 python-mimeparse==1.6.0 python-novaclient==9.1.0 python-openstackclient==3.12.0 python-subunit==1.0.0 pytz==2013.6 PyYAML==3.12 reno==2.5.0 repoze.lru==0.7 requests-mock==1.2.0 requests==2.14.2 requestsexceptions==1.2.0 rfc3986==0.3.1 Routes==2.3.1 simplejson==3.5.1 six==1.10.0 snowballstemmer==1.2.1 Sphinx==1.6.2 sphinxcontrib-websupport==1.0.1 statsd==3.2.1 stestr==1.0.0 stevedore==1.20.0 tempest==17.1.0 tenacity==3.2.1 testrepository==0.0.18 testtools==2.2.0 traceback2==1.4.0 unittest2==1.1.0 urllib3==1.21.1 vine==1.1.4 warlock==1.2.0 WebOb==1.7.1 wrapt==1.7.0 python-mistralclient-3.7.0/python_mistralclient.egg-info/0000775000175000017500000000000013326122642023702 5ustar zuulzuul00000000000000python-mistralclient-3.7.0/python_mistralclient.egg-info/entry_points.txt0000664000175000017500000001021113326122641027172 0ustar zuulzuul00000000000000[console_scripts] mistral = mistralclient.shell:main [mistralclient.auth] keycloak-oidc = mistralclient.auth.keycloak:KeycloakAuthHandler keystone = mistralclient.auth.keystone:KeystoneAuthHandler [openstack.cli.extension] workflow_engine = mistralclient.osc.plugin [openstack.workflow_engine.v2] action_definition_create = mistralclient.commands.v2.actions:Create action_definition_definition_show = mistralclient.commands.v2.actions:GetDefinition action_definition_delete = mistralclient.commands.v2.actions:Delete action_definition_list = mistralclient.commands.v2.actions:List action_definition_show = mistralclient.commands.v2.actions:Get action_definition_update = mistralclient.commands.v2.actions:Update action_execution_delete = mistralclient.commands.v2.action_executions:Delete action_execution_input_show = mistralclient.commands.v2.action_executions:GetInput action_execution_list = mistralclient.commands.v2.action_executions:List action_execution_output_show = mistralclient.commands.v2.action_executions:GetOutput action_execution_run = mistralclient.commands.v2.action_executions:Create action_execution_show = mistralclient.commands.v2.action_executions:Get action_execution_update = mistralclient.commands.v2.action_executions:Update cron_trigger_create = mistralclient.commands.v2.cron_triggers:Create cron_trigger_delete = mistralclient.commands.v2.cron_triggers:Delete cron_trigger_list = mistralclient.commands.v2.cron_triggers:List cron_trigger_show = mistralclient.commands.v2.cron_triggers:Get event_trigger_create = mistralclient.commands.v2.event_triggers:Create event_trigger_delete = mistralclient.commands.v2.event_triggers:Delete event_trigger_list = mistralclient.commands.v2.event_triggers:List event_trigger_show = mistralclient.commands.v2.event_triggers:Get resource_member_create = mistralclient.commands.v2.members:Create resource_member_delete = mistralclient.commands.v2.members:Delete resource_member_list = mistralclient.commands.v2.members:List resource_member_show = mistralclient.commands.v2.members:Get resource_member_update = mistralclient.commands.v2.members:Update task_execution_list = mistralclient.commands.v2.tasks:List task_execution_published_show = mistralclient.commands.v2.tasks:GetPublished task_execution_rerun = mistralclient.commands.v2.tasks:Rerun task_execution_result_show = mistralclient.commands.v2.tasks:GetResult task_execution_show = mistralclient.commands.v2.tasks:Get workbook_create = mistralclient.commands.v2.workbooks:Create workbook_definition_show = mistralclient.commands.v2.workbooks:GetDefinition workbook_delete = mistralclient.commands.v2.workbooks:Delete workbook_list = mistralclient.commands.v2.workbooks:List workbook_show = mistralclient.commands.v2.workbooks:Get workbook_update = mistralclient.commands.v2.workbooks:Update workbook_validate = mistralclient.commands.v2.workbooks:Validate workflow_create = mistralclient.commands.v2.workflows:Create workflow_definition_show = mistralclient.commands.v2.workflows:GetDefinition workflow_delete = mistralclient.commands.v2.workflows:Delete workflow_engine_service_list = mistralclient.commands.v2.services:List workflow_env_create = mistralclient.commands.v2.environments:Create workflow_env_delete = mistralclient.commands.v2.environments:Delete workflow_env_list = mistralclient.commands.v2.environments:List workflow_env_show = mistralclient.commands.v2.environments:Get workflow_env_update = mistralclient.commands.v2.environments:Update workflow_execution_create = mistralclient.commands.v2.executions:Create workflow_execution_delete = mistralclient.commands.v2.executions:Delete workflow_execution_input_show = mistralclient.commands.v2.executions:GetInput workflow_execution_list = mistralclient.commands.v2.executions:List workflow_execution_output_show = mistralclient.commands.v2.executions:GetOutput workflow_execution_show = mistralclient.commands.v2.executions:Get workflow_execution_update = mistralclient.commands.v2.executions:Update workflow_list = mistralclient.commands.v2.workflows:List workflow_show = mistralclient.commands.v2.workflows:Get workflow_update = mistralclient.commands.v2.workflows:Update workflow_validate = mistralclient.commands.v2.workflows:Validate python-mistralclient-3.7.0/python_mistralclient.egg-info/top_level.txt0000664000175000017500000000001613326122641026430 0ustar zuulzuul00000000000000mistralclient python-mistralclient-3.7.0/python_mistralclient.egg-info/requires.txt0000664000175000017500000000032113326122641026275 0ustar zuulzuul00000000000000cliff!=2.9.0,>=2.8.0 osc-lib>=1.8.0 oslo.utils>=3.33.0 oslo.i18n>=3.15.3 oslo.serialization!=2.19.1,>=2.18.0 pbr!=2.1.0,>=2.0.0 keystoneauth1>=3.4.0 PyYAML>=3.12 requests>=2.14.2 six>=1.10.0 stevedore>=1.20.0 python-mistralclient-3.7.0/python_mistralclient.egg-info/pbr.json0000664000175000017500000000005613326122641025360 0ustar zuulzuul00000000000000{"git_version": "f0ee48f", "is_release": true}python-mistralclient-3.7.0/python_mistralclient.egg-info/dependency_links.txt0000664000175000017500000000000113326122641027747 0ustar zuulzuul00000000000000 python-mistralclient-3.7.0/python_mistralclient.egg-info/PKG-INFO0000664000175000017500000001120013326122641024770 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: python-mistralclient Version: 3.7.0 Summary: Mistral Client Library Home-page: https://docs.openstack.org/python-mistralclient/latest/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: Apache Software License Description: ======================== 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.org/project/python-mistralclient/ :alt: Latest Version Mistral is a workflow service. Most business processes consist of multiple distinct interconnected steps that need to be executed in a particular order in a distributed environment. A user can describe such a process as a set of tasks and their transitions. After that, it is possible to upload such a description to Mistral, which will take care of state management, correct execution order, parallelism, synchronization and high availability. Mistral also provides flexible task scheduling so that it can run a process according to a specified schedule (for example, every Sunday at 4.00pm) instead of running it immediately. In Mistral terminology such a set of tasks and relations between them is called a workflow. Mistral client ============== Python client for Mistral REST API. Includes python library for Mistral API and Command Line Interface (CLI) library. Installation ------------ First of all, clone the repo and go to the repo directory:: $ git clone git://git.openstack.org/openstack/python-mistralclient.git $ cd python-mistralclient Then just run:: $ pip install -e . or:: $ pip install -r requirements.txt $ python setup.py install Running Mistral client ---------------------- If Mistral authentication is enabled, provide the information about OpenStack auth to environment variables. Type:: $ export OS_AUTH_URL=http://:5000/v2.0 $ export OS_USERNAME=admin $ export OS_TENANT_NAME=tenant $ export OS_PASSWORD=secret $ export OS_MISTRAL_URL=http://:8989/v2 (optional, by default URL=http://localhost:8989/v2) and in the case that you are authenticating against keystone over https: $ export OS_CACERT= .. note:: In client, we can use both Keystone auth versions - v2.0 and v3. But server supports only v3.* To make sure Mistral client works, type:: $ mistral workbook-list You can see the list of available commands typing:: $ mistral --help Useful Links ============ * `PyPi`_ - package installation * `Launchpad project`_ - release management * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ * `Specs`_ * `How to Contribute`_ .. _PyPi: https://pypi.org/project/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/ .. _Release Notes: https://docs.openstack.org/releasenotes/python-mistralclient Platform: UNKNOWN Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux python-mistralclient-3.7.0/python_mistralclient.egg-info/SOURCES.txt0000664000175000017500000001371413326122641025573 0ustar zuulzuul00000000000000.stestr.conf .zuul.yaml AUTHORS CONTRIBUTING.rst ChangeLog LICENSE README.rst functional_creds.conf.sample lower-constraints.txt requirements.txt run_functional_tests.sh setup.cfg setup.py test-requirements.txt tox.ini doc/source/class_reference.rst doc/source/conf.py doc/source/index.rst doc/source/cli/cli_usage_source_execution.rst doc/source/cli/cli_usage_targeting_workflows.rst doc/source/cli/cli_usage_with_keycloak.rst doc/source/cli/cli_usage_with_openstack.rst functionaltests/post_test_hook.sh functionaltests/run_tests.sh functionaltests/resources/v2/action_v2.yaml functionaltests/resources/v2/action_v2_tags.yaml functionaltests/resources/v2/async.yaml functionaltests/resources/v2/wb_v2.yaml functionaltests/resources/v2/wb_with_tags_v2.yaml functionaltests/resources/v2/wf_delay_v2.yaml functionaltests/resources/v2/wf_single_v2.yaml functionaltests/resources/v2/wf_v2.yaml functionaltests/resources/v2/wf_wrapping_wf_v2.yaml functionaltests/resources/v2/for_namespaces/lowest_level_wf.yaml functionaltests/resources/v2/for_namespaces/middle_wf.yaml functionaltests/resources/v2/for_namespaces/top_level_wf.yaml mistralclient/__init__.py mistralclient/exceptions.py mistralclient/i18n.py mistralclient/shell.py mistralclient/utils.py mistralclient/api/__init__.py mistralclient/api/base.py mistralclient/api/client.py mistralclient/api/httpclient.py mistralclient/api/releasenotes/notes/fix-cli-error-messages-2f59329557a5734f.yaml mistralclient/api/v2/__init__.py mistralclient/api/v2/action_executions.py mistralclient/api/v2/actions.py mistralclient/api/v2/client.py mistralclient/api/v2/cron_triggers.py mistralclient/api/v2/environments.py mistralclient/api/v2/event_triggers.py mistralclient/api/v2/executions.py mistralclient/api/v2/members.py mistralclient/api/v2/services.py mistralclient/api/v2/tasks.py mistralclient/api/v2/workbooks.py mistralclient/api/v2/workflows.py mistralclient/auth/__init__.py mistralclient/auth/auth_types.py mistralclient/auth/keycloak.py mistralclient/auth/keystone.py mistralclient/commands/__init__.py mistralclient/commands/v2/__init__.py mistralclient/commands/v2/action_executions.py mistralclient/commands/v2/actions.py mistralclient/commands/v2/base.py mistralclient/commands/v2/cron_triggers.py mistralclient/commands/v2/environments.py mistralclient/commands/v2/event_triggers.py mistralclient/commands/v2/executions.py mistralclient/commands/v2/members.py mistralclient/commands/v2/services.py mistralclient/commands/v2/tasks.py mistralclient/commands/v2/workbooks.py mistralclient/commands/v2/workflows.py mistralclient/osc/__init__.py mistralclient/osc/plugin.py mistralclient/tests/__init__.py mistralclient/tests/functional/__init__.py mistralclient/tests/functional/cli/__init__.py mistralclient/tests/functional/cli/base.py mistralclient/tests/functional/cli/v2/__init__.py mistralclient/tests/functional/cli/v2/base_v2.py mistralclient/tests/functional/cli/v2/cli_multi_tenancy_tests.py mistralclient/tests/functional/cli/v2/cli_tests_v2.py mistralclient/tests/unit/__init__.py mistralclient/tests/unit/base.py mistralclient/tests/unit/base_shell_test.py mistralclient/tests/unit/test_client.py mistralclient/tests/unit/test_httpclient.py mistralclient/tests/unit/test_shell.py mistralclient/tests/unit/test_utils.py mistralclient/tests/unit/resources/action_v2.yaml mistralclient/tests/unit/resources/ctx.json mistralclient/tests/unit/resources/env_v2.json mistralclient/tests/unit/resources/env_v2.yaml mistralclient/tests/unit/resources/wb_v2.yaml mistralclient/tests/unit/resources/wf_v2.yaml mistralclient/tests/unit/v2/__init__.py mistralclient/tests/unit/v2/base.py mistralclient/tests/unit/v2/test_action_executions.py mistralclient/tests/unit/v2/test_actions.py mistralclient/tests/unit/v2/test_cli_action_execs.py mistralclient/tests/unit/v2/test_cli_actions.py mistralclient/tests/unit/v2/test_cli_bash_completion.py mistralclient/tests/unit/v2/test_cli_cron_triggers.py mistralclient/tests/unit/v2/test_cli_environments.py mistralclient/tests/unit/v2/test_cli_event_triggers.py mistralclient/tests/unit/v2/test_cli_executions.py mistralclient/tests/unit/v2/test_cli_members.py mistralclient/tests/unit/v2/test_cli_services.py mistralclient/tests/unit/v2/test_cli_tasks.py mistralclient/tests/unit/v2/test_cli_workbooks.py mistralclient/tests/unit/v2/test_cli_workflows.py mistralclient/tests/unit/v2/test_environments.py mistralclient/tests/unit/v2/test_executions.py mistralclient/tests/unit/v2/test_keystone.py mistralclient/tests/unit/v2/test_members.py mistralclient/tests/unit/v2/test_services.py mistralclient/tests/unit/v2/test_tasks.py mistralclient/tests/unit/v2/test_workbooks.py mistralclient/tests/unit/v2/test_workflows.py playbooks/legacy/python-mistralclient-devstack-dsvm/post.yaml playbooks/legacy/python-mistralclient-devstack-dsvm/run.yaml python_mistralclient.egg-info/PKG-INFO python_mistralclient.egg-info/SOURCES.txt python_mistralclient.egg-info/dependency_links.txt python_mistralclient.egg-info/entry_points.txt python_mistralclient.egg-info/not-zip-safe python_mistralclient.egg-info/pbr.json python_mistralclient.egg-info/requires.txt python_mistralclient.egg-info/top_level.txt releasenotes/notes/.placeholder releasenotes/notes/add-namespaces-to-cli-9d20e7fcb07c223f.yaml releasenotes/notes/fix-region-name-2031ff4b83b6308e.yaml releasenotes/notes/fix-regression-with-execution-force-delete-af8d1968cb2673ef.yaml releasenotes/notes/force-delete-executions-d08ce88a5deb3291.yaml releasenotes/notes/remove-keystoneclient-dependency-f2981f29e6673f71.yaml releasenotes/notes/set-default-limit-value-7e293d843d6d85ac.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/liberty.rst releasenotes/source/mitaka.rst releasenotes/source/newton.rst releasenotes/source/ocata.rst releasenotes/source/pike.rst releasenotes/source/queens.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder tools/mistral.bash_completion tools/run_pep8 tools/update_env_deps tools/config/generate_sample.shpython-mistralclient-3.7.0/python_mistralclient.egg-info/not-zip-safe0000664000175000017500000000000113326122624026130 0ustar zuulzuul00000000000000