diff options
-rw-r--r-- | heatclient/common/format_utils.py | 40 | ||||
-rw-r--r-- | heatclient/osc/v1/software_deployment.py | 82 | ||||
-rw-r--r-- | heatclient/tests/unit/osc/v1/test_software_deployment.py | 52 | ||||
-rw-r--r-- | heatclient/tests/unit/test_format_utils.py | 91 | ||||
-rw-r--r-- | setup.cfg | 1 |
5 files changed, 259 insertions, 7 deletions
diff --git a/heatclient/common/format_utils.py b/heatclient/common/format_utils.py index 2b02fc9..5cb691a 100644 --- a/heatclient/common/format_utils.py +++ b/heatclient/common/format_utils.py @@ -13,6 +13,8 @@ # Copyright 2015 IBM Corp. from cliff import show +import six +import sys class RawFormat(show.ShowOne): @@ -51,3 +53,41 @@ class ValueFormat(RawFormat): @property def formatter_default(self): return 'value' + + +def indent_and_truncate(txt, spaces=0, truncate=False, truncate_limit=10, + truncate_prefix=None, truncate_postfix=None): + """Indents supplied multiline text by the specified number of spaces + + """ + if txt is None: + return + lines = six.text_type(txt).splitlines() + if truncate and len(lines) > truncate_limit: + lines = lines[-truncate_limit:] + if truncate_prefix is not None: + lines.insert(0, truncate_prefix) + if truncate_postfix is not None: + lines.append(truncate_postfix) + + if spaces > 0: + lines = [" " * spaces + line for line in lines] + return '\n'.join(lines) + + +def print_software_deployment_output(data, name, out=sys.stdout, long=False): + """Prints details of the software deployment for user consumption + + The format attempts to be valid yaml, but is primarily aimed at showing + useful information to the user in a helpful layout. + """ + if name in ('deploy_stdout', 'deploy_stderr'): + output = indent_and_truncate( + data.get(name), + spaces=4, + truncate=not long, + truncate_prefix='...', + truncate_postfix='(truncated, view all with --long)') + out.write(' %s: |\n%s\n' % (name, output)) + else: + out.write(' %s: %s\n' % (name, data.get(name))) diff --git a/heatclient/osc/v1/software_deployment.py b/heatclient/osc/v1/software_deployment.py index 545449b..b60f4c1 100644 --- a/heatclient/osc/v1/software_deployment.py +++ b/heatclient/osc/v1/software_deployment.py @@ -140,8 +140,8 @@ class DeleteDeployment(command.Command): def get_parser(self, prog_name): parser = super(DeleteDeployment, self).get_parser(prog_name) parser.add_argument( - 'id', - metavar='<ID>', + 'deployment', + metavar='<deployment>', nargs='+', help=_('ID of the deployment(s) to delete.') ) @@ -152,7 +152,7 @@ class DeleteDeployment(command.Command): hc = self.app.client_manager.orchestration failure_count = 0 - for deploy_id in parsed_args.id: + for deploy_id in parsed_args.deployment: try: sd = hc.software_deployments.get(deployment_id=deploy_id) hc.software_deployments.delete( @@ -178,7 +178,7 @@ class DeleteDeployment(command.Command): raise exc.CommandError(_('Unable to delete %(count)s of the ' '%(total)s deployments.') % {'count': failure_count, - 'total': len(parsed_args.id)}) + 'total': len(parsed_args.deployment)}) class ListDeployment(lister.Lister): @@ -229,8 +229,8 @@ class ShowDeployment(show.ShowOne): def get_parser(self, prog_name): parser = super(ShowDeployment, self).get_parser(prog_name) parser.add_argument( - 'id', - metavar='<id>', + 'deployment', + metavar='<deployment>', help=_('ID of the deployment') ) parser.add_argument( @@ -246,7 +246,7 @@ class ShowDeployment(show.ShowOne): heat_client = self.app.client_manager.orchestration try: data = heat_client.software_deployments.get( - deployment_id=parsed_args.id) + deployment_id=parsed_args.deployment) except heat_exc.HTTPNotFound: raise exc.CommandError(_('Software Deployment not found: %s') % id) else: @@ -286,3 +286,71 @@ class ShowMetadataDeployment(command.Command): md = heat_client.software_deployments.metadata( server_id=parsed_args.server) print(jsonutils.dumps(md, indent=2)) + + +class ShowOutputDeployment(command.Command): + """Show a specific deployment output.""" + + log = logging.getLogger(__name__ + '.ShowOutputDeployment') + + def get_parser(self, prog_name): + parser = super(ShowOutputDeployment, self).get_parser(prog_name) + parser.add_argument( + 'deployment', + metavar='<deployment>', + help=_('ID of deployment to show the output for') + ) + parser.add_argument( + 'output', + metavar='<output-name>', + nargs='?', + default=None, + help=_('Name of an output to display') + ) + parser.add_argument( + '--all', + default=False, + action='store_true', + help=_('Display all deployment outputs') + ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help='Show full deployment logs in output', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + heat_client = self.app.client_manager.orchestration + if (not parsed_args.all and parsed_args.output is None or + parsed_args.all and parsed_args.output is not None): + raise exc.CommandError( + _('Error: either %(output)s or %(all)s argument is needed.') + % {'output': '<output-name>', 'all': '--all'}) + try: + sd = heat_client.software_deployments.get( + deployment_id=parsed_args.deployment) + except heat_exc.HTTPNotFound: + raise exc.CommandError(_('Deployment not found: %s') + % parsed_args.deployment) + outputs = sd.output_values + if outputs: + if parsed_args.all: + print('output_values:\n') + for k in outputs.keys(): + format_utils.print_software_deployment_output( + data=outputs, name=k, long=parsed_args.long) + else: + if parsed_args.output not in outputs: + msg = (_('Output %(output)s does not exist in deployment' + ' %(deployment)s') + % {'output': parsed_args.output, + 'deployment': parsed_args.deployment}) + raise exc.CommandError(msg) + else: + print('output_value:\n') + format_utils.print_software_deployment_output( + data=outputs, name=parsed_args.output) diff --git a/heatclient/tests/unit/osc/v1/test_software_deployment.py b/heatclient/tests/unit/osc/v1/test_software_deployment.py index 3799c2b..e44f9a8 100644 --- a/heatclient/tests/unit/osc/v1/test_software_deployment.py +++ b/heatclient/tests/unit/osc/v1/test_software_deployment.py @@ -398,3 +398,55 @@ class TestDeploymentMetadataShow(TestDeployment): self.cmd.take_action(parsed_args) self.sd_client.metadata.assert_called_with( server_id='ec14c864-096e-4e27-bb8a-2c2b4dc6f3f5') + + +class TestDeploymentOutputShow(TestDeployment): + + get_response = { + "status": "IN_PROGRESS", + "server_id": "ec14c864-096e-4e27-bb8a-2c2b4dc6f3f5", + "config_id": "3d5ec2a8-7004-43b6-a7f6-542bdbe9d434", + "output_values": None, + "input_values": None, + "action": "CREATE", + "status_reason": "Deploy data available", + "id": "06e87bcc-33a2-4bce-aebd-533e698282d3", + "creation_time": "2015-01-31T15:12:36Z", + "updated_time": "2015-01-31T15:18:21Z" + } + + def setUp(self): + super(TestDeploymentOutputShow, self).setUp() + self.cmd = software_deployment.ShowOutputDeployment(self.app, None) + + def test_deployment_output_show(self): + arglist = ['85c3a507-351b-4b28-a7d8-531c8d53f4e6', '--all', '--long'] + parsed_args = self.check_parser(self.cmd, arglist, []) + self.sd_client.get = mock.Mock( + return_value=software_deployments.SoftwareDeployment( + None, self.get_response)) + self.cmd.take_action(parsed_args) + self.sd_client.get.assert_called_with(**{ + 'deployment_id': '85c3a507-351b-4b28-a7d8-531c8d53f4e6' + }) + + def test_deployment_output_show_invalid(self): + arglist = ['85c3a507-351b-4b28-a7d8-531c8d53f4e6'] + parsed_args = self.check_parser(self.cmd, arglist, []) + self.sd_client.get = mock.Mock() + error = self.assertRaises( + exc.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn('either <output-name> or --all argument is needed', + str(error)) + + def test_deployment_output_show_not_found(self): + arglist = ['85c3a507-351b-4b28-a7d8-531c8d53f4e6', '--all'] + parsed_args = self.check_parser(self.cmd, arglist, []) + self.sd_client.get = mock.Mock() + self.sd_client.get.side_effect = heat_exc.HTTPNotFound() + self.assertRaises( + exc.CommandError, + self.cmd.take_action, + parsed_args) diff --git a/heatclient/tests/unit/test_format_utils.py b/heatclient/tests/unit/test_format_utils.py index 8e948ce..3962702 100644 --- a/heatclient/tests/unit/test_format_utils.py +++ b/heatclient/tests/unit/test_format_utils.py @@ -13,6 +13,7 @@ # Copyright 2015 IBM Corp. import json +import six import yaml from heatclient.common import format_utils @@ -89,3 +90,93 @@ abcde self.cmd.run(parsed_args) self.assertEqual(expected, self.app.stdout.make_string()) + + def test_indent_and_truncate(self): + self.assertEqual( + None, + format_utils.indent_and_truncate(None)) + self.assertEqual( + None, + format_utils.indent_and_truncate(None, truncate=True)) + self.assertEqual( + '', + format_utils.indent_and_truncate('')) + self.assertEqual( + 'one', + format_utils.indent_and_truncate('one')) + self.assertEqual( + None, + format_utils.indent_and_truncate(None, spaces=2)) + self.assertEqual( + '', + format_utils.indent_and_truncate('', spaces=2)) + self.assertEqual( + ' one', + format_utils.indent_and_truncate('one', spaces=2)) + self.assertEqual( + 'one\ntwo\nthree\nfour\nfive', + format_utils.indent_and_truncate('one\ntwo\nthree\nfour\nfive')) + self.assertEqual( + 'three\nfour\nfive', + format_utils.indent_and_truncate( + 'one\ntwo\nthree\nfour\nfive', + truncate=True, + truncate_limit=3)) + self.assertEqual( + ' and so on\n three\n four\n five\n truncated', + format_utils.indent_and_truncate( + 'one\ntwo\nthree\nfour\nfive', + spaces=2, + truncate=True, + truncate_limit=3, + truncate_prefix='and so on', + truncate_postfix='truncated')) + + def test_print_software_deployment_output(self): + out = six.StringIO() + format_utils.print_software_deployment_output( + {'deploy_stdout': ''}, out=out, name='deploy_stdout') + self.assertEqual( + ' deploy_stdout: |\n\n', + out.getvalue()) + ov = {'deploy_stdout': '', 'deploy_stderr': '1\n2\n3\n4\n5\n6\n7\n8\n9' + '\n10\n11', + 'deploy_status_code': 0} + out = six.StringIO() + format_utils.print_software_deployment_output(ov, out=out, + name='deploy_stderr') + self.assertEqual( + u'''\ + deploy_stderr: | + ... + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + (truncated, view all with --long) +''', out.getvalue()) + out = six.StringIO() + format_utils.print_software_deployment_output(ov, out=out, + name='deploy_stderr', + long=True) + self.assertEqual( + u'''\ + deploy_stderr: | + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 +''', out.getvalue()) @@ -42,6 +42,7 @@ openstack.orchestration.v1 = software_deployment_delete = heatclient.osc.v1.software_deployment:DeleteDeployment software_deployment_list = heatclient.osc.v1.software_deployment:ListDeployment software_deployment_metadata_show = heatclient.osc.v1.software_deployment:ShowMetadataDeployment + software_deployment_output_show = heatclient.osc.v1.software_deployment:ShowOutputDeployment software_deployment_show = heatclient.osc.v1.software_deployment:ShowDeployment stack_abandon = heatclient.osc.v1.stack:AbandonStack stack_adopt = heatclient.osc.v1.stack:AdoptStack |