diff options
-rw-r--r-- | heatclient/osc/v1/stack.py | 146 | ||||
-rw-r--r-- | heatclient/tests/unit/osc/v1/test_stack.py | 139 | ||||
-rw-r--r-- | setup.cfg | 1 |
3 files changed, 286 insertions, 0 deletions
diff --git a/heatclient/osc/v1/stack.py b/heatclient/osc/v1/stack.py index c654627..d9381b6 100644 --- a/heatclient/osc/v1/stack.py +++ b/heatclient/osc/v1/stack.py @@ -15,12 +15,15 @@ import logging +from cliff import lister from cliff import show from openstackclient.common import exceptions as exc +from openstackclient.common import parseractions from openstackclient.common import utils from heatclient.common import utils as heat_utils from heatclient import exc as heat_exc +from heatclient.openstack.common._i18n import _ class ShowStack(show.ShowOne): @@ -82,3 +85,146 @@ def _show_stack(heat_client, stack_id, format): return columns, utils.get_item_properties(data, columns, formatters=formatters) + + +class ListStack(lister.Lister): + """List stacks.""" + + log = logging.getLogger(__name__ + '.ListStack') + + def get_parser(self, prog_name): + parser = super(ListStack, self).get_parser(prog_name) + parser.add_argument( + '--deleted', + action='store_true', + help=_('Include soft-deleted stacks in the stack listing') + ) + parser.add_argument( + '--nested', + action='store_true', + help=_('Include nested stacks in the stack listing') + ) + parser.add_argument( + '--hidden', + action='store_true', + help=_('Include hidden stacks in the stack listing') + ) + parser.add_argument( + '--property', + dest='properties', + metavar='<KEY=VALUE>', + help=_('Filter properties to apply on returned stacks (repeat to ' + 'filter on multiple properties)'), + action=parseractions.KeyValueAction + ) + parser.add_argument( + '--tags', + metavar='<TAG1,TAG2...>', + help=_('List of tags to filter by. Can be combined with ' + '--tag-mode to specify how to filter tags') + ) + parser.add_argument( + '--tag-mode', + metavar='<MODE>', + help=_('Method of filtering tags. Must be one of "any", "not", ' + 'or "not-any". If not specified, multiple tags will be ' + 'combined with the boolean AND expression') + ) + parser.add_argument( + '--limit', + metavar='<LIMIT>', + help=_('The number of stacks returned') + ) + parser.add_argument( + '--marker', + metavar='<ID>', + help=_('Only return stacks that appear after the given ID') + ) + parser.add_argument( + '--sort', + metavar='<KEY>[:<DIRECTION>]', + help=_('Sort output by selected keys and directions (asc or desc) ' + '(default: asc). Specify multiple times to sort on ' + 'multiple properties') + ) + parser.add_argument( + '--all-projects', + action='store_true', + help=_('Include all projects (admin only)') + ) + parser.add_argument( + '--short', + action='store_true', + help=_('List fewer fields in output') + ) + parser.add_argument( + '--long', + action='store_true', + help=_('List additional fields in output, this is implied by ' + '--all-projects') + ) + + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + client = self.app.client_manager.orchestration + return _list(client, args=parsed_args) + + +def _list(client, args=None): + kwargs = {} + columns = [ + 'ID', + 'Stack Name', + 'Stack Status', + 'Creation Time', + 'Updated Time', + ] + + if args: + kwargs = {'limit': args.limit, + 'marker': args.marker, + 'filters': heat_utils.format_parameters(args.properties), + 'tags': None, + 'tags_any': None, + 'not_tags': None, + 'not_tags_any': None, + 'global_tenant': args.all_projects or args.long, + 'show_deleted': args.deleted, + 'show_hidden': args.hidden} + + if args.tags: + if args.tag_mode: + if args.tag_mode == 'any': + kwargs['tags_any'] = args.tags + elif args.tag_mode == 'not': + kwargs['not_tags'] = args.tags + elif args.tag_mode == 'not-any': + kwargs['not_tags_any'] = args.tags + else: + err = _('tag mode must be one of "any", "not", "not-any"') + raise exc.CommandError(err) + else: + kwargs['tags'] = args.tags + + if args.short: + columns.pop() + columns.pop() + if args.long: + columns.insert(2, 'Stack Owner') + if args.long or args.all_projects: + columns.insert(2, 'Project') + + if args.nested: + columns.append('Parent') + kwargs['show_nested'] = True + + data = client.stacks.list(**kwargs) + data = utils.sort_items(data, args.sort if args else None) + + return ( + columns, + (utils.get_dict_properties(s, columns) for s in data) + ) diff --git a/heatclient/tests/unit/osc/v1/test_stack.py b/heatclient/tests/unit/osc/v1/test_stack.py index ca6b1e0..95514d5 100644 --- a/heatclient/tests/unit/osc/v1/test_stack.py +++ b/heatclient/tests/unit/osc/v1/test_stack.py @@ -11,9 +11,13 @@ # under the License. # +import copy import mock import testscenarios +from openstackclient.common import exceptions as exc +from openstackclient.common import utils + from heatclient.osc.v1 import stack from heatclient.tests.unit.osc.v1 import fakes as orchestration_fakes from heatclient.v1 import stacks @@ -81,3 +85,138 @@ class TestStackShow(TestStack): self.stack_client.get.assert_called_with(**{ 'stack_id': 'my_stack', }) + + +class TestStackList(TestStack): + + defaults = { + 'limit': None, + 'marker': None, + 'filters': {}, + 'tags': None, + 'tags_any': None, + 'not_tags': None, + 'not_tags_any': None, + 'global_tenant': False, + 'show_deleted': False, + 'show_hidden': False, + } + + columns = ['ID', 'Stack Name', 'Stack Status', 'Creation Time', + 'Updated Time'] + + data = { + 'id': '1234', + 'stack_name': 'my_stack', + 'stack_status': 'CREATE_COMPLETE', + 'creation_time': '2015-10-21T07:28:00Z', + 'update_time': '2015-10-21T07:30:00Z' + } + + def setUp(self): + super(TestStackList, self).setUp() + self.cmd = stack.ListStack(self.app, None) + self.stack_client.list = mock.MagicMock(return_value=[self.data]) + utils.get_dict_properties = mock.MagicMock(return_value='') + + def test_stack_list_defaults(self): + arglist = [] + parsed_args = self.check_parser(self.cmd, arglist, []) + + columns, data = self.cmd.take_action(parsed_args) + + self.stack_client.list.assert_called_with(**self.defaults) + self.assertEqual(self.columns, columns) + + def test_stack_list_nested(self): + kwargs = copy.deepcopy(self.defaults) + kwargs['show_nested'] = True + cols = copy.deepcopy(self.columns) + cols.append('Parent') + arglist = ['--nested'] + parsed_args = self.check_parser(self.cmd, arglist, []) + + columns, data = self.cmd.take_action(parsed_args) + + self.stack_client.list.assert_called_with(**kwargs) + self.assertEqual(cols, columns) + + def test_stack_list_all_projects(self): + kwargs = copy.deepcopy(self.defaults) + kwargs['global_tenant'] = True + cols = copy.deepcopy(self.columns) + cols.insert(2, 'Project') + arglist = ['--all-projects'] + parsed_args = self.check_parser(self.cmd, arglist, []) + + columns, data = self.cmd.take_action(parsed_args) + + self.stack_client.list.assert_called_with(**kwargs) + self.assertEqual(cols, columns) + + def test_stack_list_long(self): + kwargs = copy.deepcopy(self.defaults) + kwargs['global_tenant'] = True + cols = copy.deepcopy(self.columns) + cols.insert(2, 'Stack Owner') + cols.insert(2, 'Project') + arglist = ['--long'] + parsed_args = self.check_parser(self.cmd, arglist, []) + + columns, data = self.cmd.take_action(parsed_args) + + self.stack_client.list.assert_called_with(**kwargs) + self.assertEqual(cols, columns) + + def test_stack_list_short(self): + cols = ['ID', 'Stack Name', 'Stack Status'] + arglist = ['--short'] + parsed_args = self.check_parser(self.cmd, arglist, []) + + columns, data = self.cmd.take_action(parsed_args) + + self.stack_client.list.assert_called_with(**self.defaults) + self.assertEqual(cols, columns) + + def test_stack_list_sort(self): + arglist = ['--sort', 'stack_name:desc,id'] + parsed_args = self.check_parser(self.cmd, arglist, []) + + columns, data = self.cmd.take_action(parsed_args) + + self.stack_client.list.assert_called_with(**self.defaults) + self.assertEqual(self.columns, columns) + + def test_stack_list_sort_invalid_key(self): + arglist = ['--sort', 'bad_key'] + parsed_args = self.check_parser(self.cmd, arglist, []) + + self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) + + def test_stack_list_tags(self): + kwargs = copy.deepcopy(self.defaults) + kwargs['tags'] = 'tag1,tag2' + arglist = ['--tags', 'tag1,tag2'] + parsed_args = self.check_parser(self.cmd, arglist, []) + + columns, data = self.cmd.take_action(parsed_args) + + self.stack_client.list.assert_called_with(**kwargs) + self.assertEqual(self.columns, columns) + + def test_stack_list_tags_mode(self): + kwargs = copy.deepcopy(self.defaults) + kwargs['not_tags'] = 'tag1,tag2' + arglist = ['--tags', 'tag1,tag2', '--tag-mode', 'not'] + parsed_args = self.check_parser(self.cmd, arglist, []) + + columns, data = self.cmd.take_action(parsed_args) + + self.stack_client.list.assert_called_with(**kwargs) + self.assertEqual(self.columns, columns) + + def test_stack_list_tags_bad_mode(self): + arglist = ['--tags', 'tag1,tag2', '--tag-mode', 'bad_mode'] + parsed_args = self.check_parser(self.cmd, arglist, []) + + self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) @@ -32,6 +32,7 @@ openstack.cli.extension = openstack.orchestration.v1 = stack_show = heatclient.osc.v1.stack:ShowStack + stack_list = heatclient.osc.v1.stack:ListStack [global] |