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:
parent
2c816bf238
commit
91535df1bf
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
57
mistralclient/tests/unit/test_utils.py
Normal file
57
mistralclient/tests/unit/test_utils.py
Normal 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))
|
@ -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'])
|
||||
|
@ -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])
|
||||
|
@ -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):
|
||||
|
@ -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])
|
||||
)
|
||||
|
@ -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())
|
||||
|
Loading…
Reference in New Issue
Block a user