summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--heatclient/common/format_utils.py40
-rw-r--r--heatclient/osc/v1/software_deployment.py82
-rw-r--r--heatclient/tests/unit/osc/v1/test_software_deployment.py52
-rw-r--r--heatclient/tests/unit/test_format_utils.py91
-rw-r--r--setup.cfg1
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())
diff --git a/setup.cfg b/setup.cfg
index 7cbead2..bf8dbec 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -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