Add workbook and workflow validate commands
Add workbook-validate and workflow-validate commands to validate workbook and workflow definition respectively. The commands print the error if validation fails else returns command prompt. Change-Id: I25b220a2a9abd720f24ee6b23ce37ee977845793 Implements: blueprint api-validate-dsl
This commit is contained in:
		| @@ -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. | ||||
| @@ -62,3 +63,17 @@ class WorkbookManager(base.ResourceManager): | ||||
|         self._ensure_not_empty(name=name) | ||||
|  | ||||
|         self._delete('/workbooks/%s' % name) | ||||
|  | ||||
|     def validate(self, definition): | ||||
|         self._ensure_not_empty(definition=definition) | ||||
|  | ||||
|         resp = self.client.http_client.post( | ||||
|             '/workbooks/validate', | ||||
|             definition, | ||||
|             headers={'content-type': 'text/plain'} | ||||
|         ) | ||||
|  | ||||
|         if resp.status_code != 200: | ||||
|             self._raise_api_exception(resp) | ||||
|  | ||||
|         return base.extract_json(resp, None) | ||||
|   | ||||
| @@ -1,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. | ||||
| @@ -64,3 +65,17 @@ class WorkflowManager(base.ResourceManager): | ||||
|         self._ensure_not_empty(name=name) | ||||
|  | ||||
|         self._delete('/workflows/%s' % name) | ||||
|  | ||||
|     def validate(self, definition): | ||||
|         self._ensure_not_empty(definition=definition) | ||||
|  | ||||
|         resp = self.client.http_client.post( | ||||
|             '/workflows/validate', | ||||
|             definition, | ||||
|             headers={'content-type': 'text/plain'} | ||||
|         ) | ||||
|  | ||||
|         if resp.status_code != 200: | ||||
|             self._raise_api_exception(resp) | ||||
|  | ||||
|         return base.extract_json(resp, None) | ||||
|   | ||||
| @@ -1,18 +1,17 @@ | ||||
| # Copyright 2014 - Mirantis, Inc. | ||||
| # All Rights Reserved | ||||
| # 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 | ||||
| #    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 | ||||
| #        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. | ||||
| # | ||||
| #    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 logging | ||||
| @@ -22,6 +21,8 @@ from cliff import show | ||||
|  | ||||
| from mistralclient.api.v2 import workbooks | ||||
| from mistralclient.commands.v2 import base | ||||
| from mistralclient import exceptions as exc | ||||
|  | ||||
|  | ||||
| LOG = logging.getLogger(__name__) | ||||
|  | ||||
| @@ -153,3 +154,28 @@ class GetDefinition(command.Command): | ||||
|             parsed_args.name).definition | ||||
|  | ||||
|         self.app.stdout.write(definition or "\n") | ||||
|  | ||||
|  | ||||
| class Validate(show.ShowOne): | ||||
|     """Validate workbook.""" | ||||
|  | ||||
|     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): | ||||
|         result = workbooks.WorkbookManager(self.app.client).validate( | ||||
|             parsed_args.definition.read()) | ||||
|  | ||||
|         if not result.get('valid', None): | ||||
|             raise exc.MistralClientException( | ||||
|                 result.get('error', 'Unknown exception.')) | ||||
|  | ||||
|         return tuple(), tuple() | ||||
|   | ||||
| @@ -1,18 +1,17 @@ | ||||
| # Copyright 2014 - Mirantis, Inc. | ||||
| # All Rights Reserved | ||||
| # 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 | ||||
| #    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 | ||||
| #        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. | ||||
| # | ||||
| #    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 logging | ||||
| @@ -22,6 +21,8 @@ from cliff import show | ||||
|  | ||||
| from mistralclient.api.v2 import workflows | ||||
| from mistralclient.commands.v2 import base | ||||
| from mistralclient import exceptions as exc | ||||
|  | ||||
|  | ||||
| LOG = logging.getLogger(__name__) | ||||
|  | ||||
| @@ -164,3 +165,28 @@ class GetDefinition(command.Command): | ||||
|             parsed_args.name).definition | ||||
|  | ||||
|         self.app.stdout.write(definition or "\n") | ||||
|  | ||||
|  | ||||
| class Validate(show.ShowOne): | ||||
|     """Validate workflow.""" | ||||
|  | ||||
|     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): | ||||
|         result = workflows.WorkflowManager(self.app.client).validate( | ||||
|             parsed_args.definition.read()) | ||||
|  | ||||
|         if not result.get('valid', None): | ||||
|             raise exc.MistralClientException( | ||||
|                 result.get('error', 'Unknown exception.')) | ||||
|  | ||||
|         return tuple(), tuple() | ||||
|   | ||||
| @@ -1,18 +1,16 @@ | ||||
| # Copyright 2015 StackStorm, Inc. | ||||
| # All Rights Reserved | ||||
| # 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 | ||||
| #    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 | ||||
| #        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. | ||||
| # | ||||
| #    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 | ||||
| @@ -247,6 +245,7 @@ class MistralShell(app.App): | ||||
|             '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, | ||||
| @@ -254,6 +253,7 @@ class MistralShell(app.App): | ||||
|             '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': | ||||
|   | ||||
| @@ -1,23 +1,23 @@ | ||||
| # Copyright 2014 Mirantis, Inc. | ||||
| # All Rights Reserved | ||||
| # 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 | ||||
| #    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 | ||||
| #        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. | ||||
| # | ||||
| #    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 import exceptions as exc | ||||
| from mistralclient.tests.unit import base | ||||
|  | ||||
|  | ||||
| @@ -96,3 +96,25 @@ class TestCLIWorkbooksV2(base.BaseCommandTest): | ||||
|         self.call(workbook_cmd.GetDefinition, app_args=['name']) | ||||
|  | ||||
|         self.app.stdout.write.assert_called_with(WB_DEF) | ||||
|  | ||||
|     @mock.patch('argparse.open', create=True) | ||||
|     @mock.patch('mistralclient.api.v2.workbooks.WorkbookManager.validate') | ||||
|     def test_validate(self, mock, mock_open): | ||||
|         mock.return_value = {'valid': True} | ||||
|         mock_open.return_value = mock.MagicMock(spec=file) | ||||
|  | ||||
|         result = self.call(workbook_cmd.Validate, app_args=['wb.yaml']) | ||||
|  | ||||
|         self.assertEqual(result[0], tuple()) | ||||
|         self.assertEqual(result[1], tuple()) | ||||
|  | ||||
|     @mock.patch('argparse.open', create=True) | ||||
|     @mock.patch('mistralclient.api.v2.workbooks.WorkbookManager.validate') | ||||
|     def test_validate_failed(self, mock, mock_open): | ||||
|         mock.return_value = {'valid': False, 'error': 'Invalid DSL...'} | ||||
|         mock_open.return_value = mock.MagicMock(spec=file) | ||||
|  | ||||
|         self.assertRaises(exc.MistralClientException, | ||||
|                           self.call, | ||||
|                           workbook_cmd.Validate, | ||||
|                           app_args=['wb.yaml']) | ||||
|   | ||||
| @@ -1,23 +1,23 @@ | ||||
| # Copyright 2014 Mirantis, Inc. | ||||
| # All Rights Reserved | ||||
| # 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 | ||||
| #    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 | ||||
| #        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. | ||||
| # | ||||
| #    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 workflows | ||||
| from mistralclient.commands.v2 import workflows as workflow_cmd | ||||
| from mistralclient import exceptions as exc | ||||
| from mistralclient.tests.unit import base | ||||
|  | ||||
|  | ||||
| @@ -91,3 +91,25 @@ class TestCLIWorkflowsV2(base.BaseCommandTest): | ||||
|         self.call(workflow_cmd.GetDefinition, app_args=['name']) | ||||
|  | ||||
|         self.app.stdout.write.assert_called_with(WF_DEF) | ||||
|  | ||||
|     @mock.patch('argparse.open', create=True) | ||||
|     @mock.patch('mistralclient.api.v2.workflows.WorkflowManager.validate') | ||||
|     def test_validate(self, mock, mock_open): | ||||
|         mock.return_value = {'valid': True} | ||||
|         mock_open.return_value = mock.MagicMock(spec=file) | ||||
|  | ||||
|         result = self.call(workflow_cmd.Validate, app_args=['wf.yaml']) | ||||
|  | ||||
|         self.assertEqual(result[0], tuple()) | ||||
|         self.assertEqual(result[1], tuple()) | ||||
|  | ||||
|     @mock.patch('argparse.open', create=True) | ||||
|     @mock.patch('mistralclient.api.v2.workflows.WorkflowManager.validate') | ||||
|     def test_validate_failed(self, mock, mock_open): | ||||
|         mock.return_value = {'valid': False, 'error': 'Invalid DSL...'} | ||||
|         mock_open.return_value = mock.MagicMock(spec=file) | ||||
|  | ||||
|         self.assertRaises(exc.MistralClientException, | ||||
|                           self.call, | ||||
|                           workflow_cmd.Validate, | ||||
|                           app_args=['wf.yaml']) | ||||
|   | ||||
| @@ -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. | ||||
| @@ -12,6 +13,7 @@ | ||||
| #    See the License for the specific language governing permissions and | ||||
| #    limitations under the License. | ||||
|  | ||||
| from mistralclient.api import base as api_base | ||||
| from mistralclient.api.v2 import workbooks | ||||
| from mistralclient.tests.unit.v2 import base | ||||
|  | ||||
| @@ -41,11 +43,25 @@ workflows: | ||||
|         action: std.http url="http://some_url" server_id=1 | ||||
| """ | ||||
|  | ||||
| WORKBOOK = {'definition': WB_DEF} | ||||
| 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): | ||||
| @@ -112,3 +128,56 @@ class TestWorkbooksV2(base.BaseClientV2Test): | ||||
|         self.workbooks.delete('wb') | ||||
|  | ||||
|         mock.assert_called_once_with(URL_TEMPLATE_NAME % 'wb') | ||||
|  | ||||
|     def test_validate(self): | ||||
|         mock = self.mock_http_post(status_code=200, | ||||
|                                    content={'valid': True}) | ||||
|  | ||||
|         result = self.workbooks.validate(WB_DEF) | ||||
|  | ||||
|         self.assertIsNotNone(result) | ||||
|         self.assertIn('valid', result) | ||||
|         self.assertTrue(result['valid']) | ||||
|  | ||||
|         mock.assert_called_once_with( | ||||
|             URL_TEMPLATE_VALIDATE, | ||||
|             WB_DEF, | ||||
|             headers={'content-type': 'text/plain'} | ||||
|         ) | ||||
|  | ||||
|     def test_validate_failed(self): | ||||
|         mock_result = { | ||||
|             "valid": False, | ||||
|             "error": "Task properties 'action' and 'workflow' " | ||||
|                      "can't be specified both" | ||||
|         } | ||||
|  | ||||
|         mock = self.mock_http_post(status_code=200, content=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']) | ||||
|  | ||||
|         mock.assert_called_once_with( | ||||
|             URL_TEMPLATE_VALIDATE, | ||||
|             INVALID_WB_DEF, | ||||
|             headers={'content-type': 'text/plain'} | ||||
|         ) | ||||
|  | ||||
|     def test_validate_api_failed(self): | ||||
|         mock = self.mock_http_post(status_code=500, content={}) | ||||
|  | ||||
|         self.assertRaises(api_base.APIException, | ||||
|                           self.workbooks.validate, | ||||
|                           WB_DEF) | ||||
|  | ||||
|         mock.assert_called_once_with( | ||||
|             URL_TEMPLATE_VALIDATE, | ||||
|             WB_DEF, | ||||
|             headers={'content-type': 'text/plain'} | ||||
|         ) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Winson Chan
					Winson Chan