OpenStackClient plugin for stack list

This change implements the "openstack stack list" command.

Blueprint: heat-support-python-openstackclient

Change-Id: Ib3001dfaae0a4242b44fb3bb85713229c49ad8e8
This commit is contained in:
Bryan Jones
2015-10-15 18:16:05 +00:00
parent 4c53ed854c
commit 7a249cf7e9
3 changed files with 286 additions and 0 deletions

View File

@@ -15,12 +15,15 @@
import logging import logging
from cliff import lister
from cliff import show from cliff import show
from openstackclient.common import exceptions as exc from openstackclient.common import exceptions as exc
from openstackclient.common import parseractions
from openstackclient.common import utils from openstackclient.common import utils
from heatclient.common import utils as heat_utils from heatclient.common import utils as heat_utils
from heatclient import exc as heat_exc from heatclient import exc as heat_exc
from heatclient.openstack.common._i18n import _
class ShowStack(show.ShowOne): class ShowStack(show.ShowOne):
@@ -82,3 +85,146 @@ def _show_stack(heat_client, stack_id, format):
return columns, utils.get_item_properties(data, columns, return columns, utils.get_item_properties(data, columns,
formatters=formatters) 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)
)

View File

@@ -11,9 +11,13 @@
# under the License. # under the License.
# #
import copy
import mock import mock
import testscenarios import testscenarios
from openstackclient.common import exceptions as exc
from openstackclient.common import utils
from heatclient.osc.v1 import stack from heatclient.osc.v1 import stack
from heatclient.tests.unit.osc.v1 import fakes as orchestration_fakes from heatclient.tests.unit.osc.v1 import fakes as orchestration_fakes
from heatclient.v1 import stacks from heatclient.v1 import stacks
@@ -81,3 +85,138 @@ class TestStackShow(TestStack):
self.stack_client.get.assert_called_with(**{ self.stack_client.get.assert_called_with(**{
'stack_id': 'my_stack', '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)

View File

@@ -32,6 +32,7 @@ openstack.cli.extension =
openstack.orchestration.v1 = openstack.orchestration.v1 =
stack_show = heatclient.osc.v1.stack:ShowStack stack_show = heatclient.osc.v1.stack:ShowStack
stack_list = heatclient.osc.v1.stack:ListStack
[global] [global]