Add env option to CLI for executions and tasks update

Add optional env argument on execution-update and task-update. The
env argument accepts a string or file in JSON or YAML.

Partially implements: blueprint mistral-rerun-update-env

Change-Id: Icc036dc3b995d2539e0916d161f1b0f5f0099556
This commit is contained in:
Winson Chan 2015-12-22 01:25:38 +00:00
parent 2c816bf238
commit 91535df1bf
11 changed files with 311 additions and 59 deletions

View File

@ -1,4 +1,5 @@
# 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.
@ -58,12 +59,17 @@ class ExecutionManager(base.ResourceManager):
def create_direct_workflow(self, workflow_name, workflow_input, **params):
return self.create(workflow_name, workflow_input, **params)
def update(self, id, state, description=None):
def update(self, id, state, description=None, env=None):
data = {}
if state:
data = {'state': state}
data['state'] = state
if description:
data = ({'description': description})
data['description'] = description
if env:
data['params'] = {'env': env}
return self._update('/executions/%s' % id, data)

View File

@ -13,6 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from mistralclient.api import base
@ -36,7 +38,7 @@ class TaskManager(base.ResourceManager):
return self._get('/tasks/%s' % id)
def rerun(self, task_ex_id, reset=True):
def rerun(self, task_ex_id, reset=True, env=None):
url = '/tasks/%s' % task_ex_id
body = {
@ -45,4 +47,7 @@ class TaskManager(base.ResourceManager):
'reset': reset
}
if env:
body['env'] = json.dumps(env)
return self._update(url, body)

View File

@ -18,7 +18,6 @@ import logging
from cliff import command
from cliff import show
import yaml
from mistralclient.commands.v2 import base
from mistralclient import utils
@ -89,17 +88,6 @@ def format(environment=None):
return columns, data
def load_file_content(f):
content = f.read()
try:
data = yaml.safe_load(content)
except Exception:
data = json.loads(content)
return data
class List(base.MistralLister):
"""List all environments."""
@ -146,7 +134,7 @@ class Create(show.ShowOne):
return parser
def take_action(self, parsed_args):
data = load_file_content(parsed_args.file)
data = utils.load_content(parsed_args.file.read())
mistral_client = self.app.client_manager.workflow_engine
environment = mistral_client.environments.create(**data)
@ -190,7 +178,7 @@ class Update(show.ShowOne):
return parser
def take_action(self, parsed_args):
data = load_file_content(parsed_args.file)
data = utils.load_content(parsed_args.file.read())
mistral_client = self.app.client_manager.workflow_engine
environment = mistral_client.environments.update(**data)

View File

@ -1,4 +1,5 @@
# Copyright 2014 - Mirantis, Inc.
# Copyright 2015 - StackStorm, Inc.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -16,6 +17,7 @@
import json
import logging
import os.path
from cliff import command
from cliff import show
@ -225,15 +227,22 @@ class Update(show.ShowOne):
help='Execution identifier'
)
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
parser.add_argument(
'-s',
'--state',
dest='state',
choices=['RUNNING', 'PAUSED', 'SUCCESS', 'ERROR'],
help='Execution state'
)
group.add_argument(
parser.add_argument(
'-e',
'--env',
dest='env',
help='Environment variables'
)
parser.add_argument(
'-d',
'--description',
dest='description',
@ -245,10 +254,18 @@ class Update(show.ShowOne):
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,
parsed_args.description)
description=parsed_args.description,
env=env
)
return format(execution)

View File

@ -17,11 +17,13 @@
import json
import logging
import os.path
from cliff import command
from cliff import show
from mistralclient.commands.v2 import base
from mistralclient import utils
LOG = logging.getLogger(__name__)
@ -163,13 +165,28 @@ class Rerun(show.ShowOne):
'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)
reset=(not parsed_args.resume),
env=env
)
return format(execution)

View File

@ -0,0 +1,57 @@
# 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 testtools
import yaml
from mistralclient import utils
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(testtools.TestCase):
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))

View File

@ -1,4 +1,5 @@
# Copyright 2014 Mirantis, Inc.
# Copyright 2014 - Mirantis, Inc.
# Copyright 2015 - StackStorm, Inc.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -36,47 +37,95 @@ class TestCLIExecutionsV2(base.BaseCommandTest):
def test_create_wf_input_string(self):
self.client.executions.create.return_value = EXECUTION
result = self.call(execution_cmd.Create,
app_args=['id', '{ "context": true }'])
result = self.call(
execution_cmd.Create,
app_args=['id', '{ "context": true }']
)
self.assertEqual(('123', 'some', '', 'RUNNING', None,
'1', '1'), result[1])
self.assertEqual(
('123', 'some', '', 'RUNNING', None, '1', '1'),
result[1]
)
def test_create_wf_input_file(self):
self.client.executions.create.return_value = EXECUTION
path = pkg.resource_filename('mistralclient',
'tests/unit/resources/ctx.json')
result = self.call(execution_cmd.Create,
app_args=['id', path])
self.assertEqual(('123', 'some', '', 'RUNNING', None,
'1', '1'), result[1])
path = pkg.resource_filename(
'mistralclient',
'tests/unit/resources/ctx.json'
)
result = self.call(
execution_cmd.Create,
app_args=['id', path]
)
self.assertEqual(
('123', 'some', '', 'RUNNING', None, '1', '1'),
result[1]
)
def test_create_with_description(self):
self.client.executions.create.return_value = EXECUTION
result = self.call(execution_cmd.Create,
app_args=['id', '{ "context": true }', '-d', ''])
result = self.call(
execution_cmd.Create,
app_args=['id', '{ "context": true }', '-d', '']
)
self.assertEqual(('123', 'some', '', 'RUNNING', None,
'1', '1'), result[1])
self.assertEqual(
('123', 'some', '', 'RUNNING', None, '1', '1'),
result[1]
)
def test_update(self):
def test_update_state(self):
self.client.executions.update.return_value = EXECUTION
result = self.call(execution_cmd.Update,
app_args=['id', '-s', 'SUCCESS'])
result = self.call(
execution_cmd.Update,
app_args=['id', '-s', 'SUCCESS']
)
self.assertEqual(('123', 'some', '', 'RUNNING', None,
'1', '1'), result[1])
self.assertEqual(
('123', 'some', '', 'RUNNING', None, '1', '1'),
result[1]
)
def test_resume_update_env(self):
self.client.executions.update.return_value = EXECUTION
result = self.call(
execution_cmd.Update,
app_args=['id', '-s', 'RUNNING', '--env', '{"k1": "foobar"}']
)
self.assertEqual(
('123', 'some', '', 'RUNNING', None, '1', '1'),
result[1]
)
def test_update_description(self):
self.client.executions.update.return_value = EXECUTION
result = self.call(
execution_cmd.Update,
app_args=['id', '-d', 'foobar']
)
self.assertEqual(
('123', 'some', '', 'RUNNING', None, '1', '1'),
result[1]
)
def test_list(self):
self.client.executions.list.return_value = (EXECUTION,)
result = self.call(execution_cmd.List)
self.assertEqual([('123', 'some', '', 'RUNNING', None,
'1', '1')], result[1])
self.assertEqual(
[('123', 'some', '', 'RUNNING', None, '1', '1')],
result[1]
)
def test_list_with_pagination(self):
self.client.executions.list.return_value = (EXECUTION,)
@ -111,8 +160,10 @@ class TestCLIExecutionsV2(base.BaseCommandTest):
result = self.call(execution_cmd.Get, app_args=['id'])
self.assertEqual(('123', 'some', '', 'RUNNING', None,
'1', '1'), result[1])
self.assertEqual(
('123', 'some', '', 'RUNNING', None, '1', '1'),
result[1]
)
def test_delete(self):
self.call(execution_cmd.Delete, app_args=['id'])

View File

@ -1,4 +1,5 @@
# Copyright 2014 Mirantis, Inc.
# Copyright 2014 - Mirantis, Inc.
# Copyright 2015 - StackStorm, Inc.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -101,3 +102,23 @@ class TestCLITasksV2(base.BaseCommandTest):
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])

View File

@ -1,4 +1,5 @@
# 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.
@ -48,12 +49,18 @@ class TestExecutionsV2(base.BaseClientV2Test):
'input': json.dumps(EXEC['input']),
}
ex = self.executions.create(EXEC['workflow_name'],
EXEC['input'])
ex = self.executions.create(
EXEC['workflow_name'],
EXEC['input']
)
self.assertIsNotNone(ex)
self.assertEqual(executions.Execution(self.executions, EXEC).to_dict(),
ex.to_dict())
self.assertEqual(
executions.Execution(self.executions, EXEC).to_dict(),
ex.to_dict()
)
mock.assert_called_once_with(URL_TEMPLATE, json.dumps(body))
@unittest2.expectedFailure
@ -64,8 +71,10 @@ class TestExecutionsV2(base.BaseClientV2Test):
@unittest2.expectedFailure
def test_create_failure2(self):
self.mock_http_post(content=EXEC)
self.executions.create(EXEC['workflow_name'],
list('343', 'sdfsd'))
self.executions.create(
EXEC['workflow_name'],
list('343', 'sdfsd')
)
def test_update(self):
mock = self.mock_http_put(content=EXEC)
@ -76,10 +85,43 @@ class TestExecutionsV2(base.BaseClientV2Test):
ex = self.executions.update(EXEC['id'], EXEC['state'])
self.assertIsNotNone(ex)
self.assertEqual(executions.Execution(self.executions, EXEC).to_dict(),
ex.to_dict())
self.assertEqual(
executions.Execution(self.executions, EXEC).to_dict(),
ex.to_dict()
)
mock.assert_called_once_with(
URL_TEMPLATE_ID % EXEC['id'], json.dumps(body))
URL_TEMPLATE_ID % EXEC['id'],
json.dumps(body)
)
def test_update_env(self):
mock = self.mock_http_put(content=EXEC)
body = {
'state': EXEC['state'],
'params': {
'env': {'k1': 'foobar'}
}
}
ex = self.executions.update(
EXEC['id'],
EXEC['state'],
env={'k1': 'foobar'}
)
self.assertIsNotNone(ex)
self.assertEqual(
executions.Execution(self.executions, EXEC).to_dict(),
ex.to_dict()
)
mock.assert_called_once_with(
URL_TEMPLATE_ID % EXEC['id'],
json.dumps(body)
)
def test_list(self):
mock = self.mock_http_get(content={'executions': [EXEC]})
@ -116,8 +158,11 @@ class TestExecutionsV2(base.BaseClientV2Test):
ex = self.executions.get(EXEC['id'])
self.assertEqual(executions.Execution(self.executions, EXEC).to_dict(),
ex.to_dict())
self.assertEqual(
executions.Execution(self.executions, EXEC).to_dict(),
ex.to_dict()
)
mock.assert_called_once_with(URL_TEMPLATE_ID % EXEC['id'])
def test_delete(self):

View File

@ -1,4 +1,5 @@
# 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.
@ -101,3 +102,25 @@ class TestTasksV2(base.BaseClientV2Test):
},
json.loads(mock.call_args[0][1])
)
def test_rerun_update_env(self):
mock = self.mock_http_put(content=TASK)
task = self.tasks.rerun(TASK['id'], env={'k1': 'foobar'})
self.assertDictEqual(
tasks.Task(self.tasks, TASK).to_dict(),
task.to_dict()
)
self.assertEqual(1, mock.call_count)
self.assertEqual(URL_TEMPLATE_ID % TASK['id'], mock.call_args[0][0])
self.assertDictEqual(
{
'reset': True,
'state': 'RUNNING',
'id': TASK['id'],
'env': json.dumps({'k1': 'foobar'})
},
json.loads(mock.call_args[0][1])
)

View File

@ -1,4 +1,5 @@
# 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.
@ -12,6 +13,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import yaml
from mistralclient import exceptions
@ -29,3 +34,20 @@ def do_action_on_many(action, resources, success_msg, error_msg):
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())