diff --git a/doc/source/command-objects/project.rst b/doc/source/command-objects/project.rst index f76f79ff5..c4393d179 100644 --- a/doc/source/command-objects/project.rst +++ b/doc/source/command-objects/project.rst @@ -96,6 +96,7 @@ List projects [--domain ] [--user ] [--long] + [--sort [:,:,..]] .. option:: --domain @@ -113,6 +114,12 @@ List projects List additional fields in output +.. option:: --sort [:,:,..] + + Sort output by selected keys and directions (asc or desc) (default: asc), + multiple keys and directions can be specified --sort + [:,:,..] + project set ----------- diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index fc5c92017..5c3ec7300 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -136,6 +136,13 @@ class ListProject(command.Lister): default=False, help=_('List additional fields in output'), ) + parser.add_argument( + '--sort', + metavar='[:]', + help=_('Sort output by selected keys and directions (asc or desc) ' + '(default: asc), repeat this option to specify multiple ' + 'keys and directions.'), + ) return parser def take_action(self, parsed_args): @@ -144,6 +151,8 @@ class ListProject(command.Lister): else: columns = ('ID', 'Name') data = self.app.client_manager.identity.tenants.list() + if parsed_args.sort: + data = utils.sort_items(data, parsed_args.sort) return (columns, (utils.get_item_properties( s, columns, diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 56c4fbc8b..861847d3d 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -180,6 +180,13 @@ class ListProject(command.Lister): default=False, help=_('List additional fields in output'), ) + parser.add_argument( + '--sort', + metavar='[:]', + help=_('Sort output by selected keys and directions (asc or desc) ' + '(default: asc), repeat this option to specify multiple ' + 'keys and directions.'), + ) return parser def take_action(self, parsed_args): @@ -208,6 +215,8 @@ class ListProject(command.Lister): kwargs['user'] = user_id data = identity_client.projects.list(**kwargs) + if parsed_args.sort: + data = utils.sort_items(data, parsed_args.sort) return (columns, (utils.get_item_properties( s, columns, diff --git a/openstackclient/tests/unit/identity/v2_0/test_project.py b/openstackclient/tests/unit/identity/v2_0/test_project.py index c1f00762e..69da739f5 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_project.py +++ b/openstackclient/tests/unit/identity/v2_0/test_project.py @@ -23,6 +23,7 @@ from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes class TestProject(identity_fakes.TestIdentityv2): fake_project = identity_fakes.FakeProject.create_one_project() + fake_projects = identity_fakes.FakeProject.create_projects() columns = ( 'description', @@ -36,6 +37,12 @@ class TestProject(identity_fakes.TestIdentityv2): fake_project.id, fake_project.name, ) + datalists = ( + (fake_projects[0].description, True, + fake_projects[0].id, fake_projects[0].name,), + (fake_projects[1].description, True, + fake_projects[1].id, fake_projects[1].name,), + ) def setUp(self): super(TestProject, self).setUp() @@ -357,6 +364,35 @@ class TestProjectList(TestProject): ), ) self.assertEqual(datalist, tuple(data)) + def test_project_list_sort(self): + self.projects_mock.list.return_value = self.fake_projects + + arglist = ['--sort', 'name:asc', ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + (columns, data) = self.cmd.take_action(parsed_args) + self.projects_mock.list.assert_called_with() + + collist = ('ID', 'Name') + self.assertEqual(collist, columns) + + if self.fake_projects[0].name > self.fake_projects[1].name: + datalists = ( + (self.fake_projects[1].id, self.fake_projects[1].name), + (self.fake_projects[0].id, self.fake_projects[0].name), + ) + else: + datalists = ( + (self.fake_projects[0].id, self.fake_projects[0].name), + (self.fake_projects[1].id, self.fake_projects[1].name), + ) + + self.assertEqual(datalists, tuple(data)) + class TestProjectSet(TestProject): diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index 7b76fa608..473efcfe8 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -622,6 +622,23 @@ class FakeProject(object): loaded=True) return project + @staticmethod + def create_projects(attrs=None, count=2): + """Create multiple fake projects. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of projects to fake + :return: + A list of FakeResource objects faking the projects + """ + + projects = [] + for i in range(0, count): + projects.append(FakeProject.create_one_project(attrs)) + return projects + class FakeDomain(object): """Fake one or more domain.""" diff --git a/openstackclient/tests/unit/identity/v3/test_project.py b/openstackclient/tests/unit/identity/v3/test_project.py index 702d92097..2edb49ef5 100644 --- a/openstackclient/tests/unit/identity/v3/test_project.py +++ b/openstackclient/tests/unit/identity/v3/test_project.py @@ -451,6 +451,7 @@ class TestProjectList(TestProject): domain = identity_fakes.FakeDomain.create_one_domain() project = identity_fakes.FakeProject.create_one_project( attrs={'domain_id': domain.id}) + projects = identity_fakes.FakeProject.create_projects() columns = ( 'ID', @@ -462,6 +463,12 @@ class TestProjectList(TestProject): project.name, ), ) + datalists = ( + (projects[0].description, True, + projects[0].id, projects[0].name,), + (projects[1].description, True, + projects[1].id, projects[1].name,), + ) def setUp(self): super(TestProjectList, self).setUp() @@ -552,6 +559,36 @@ class TestProjectList(TestProject): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) + def test_project_list_sort(self): + self.projects_mock.list.return_value = self.projects + + arglist = ['--sort', 'name:asc', ] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + (columns, data) = self.cmd.take_action(parsed_args) + self.projects_mock.list.assert_called_with() + + collist = ('ID', 'Name') + self.assertEqual(collist, columns) + + if self.projects[0].name > self.projects[1].name: + datalists = ( + (self.projects[1].id, self.projects[1].name), + (self.projects[0].id, self.projects[0].name), + ) + else: + datalists = ( + (self.projects[0].id, self.projects[0].name), + (self.projects[1].id, self.projects[1].name), + ) + + self.assertEqual(datalists, tuple(data)) + class TestProjectSet(TestProject): diff --git a/releasenotes/notes/bug-1596818-d4cd93dd4d38d3d6.yaml b/releasenotes/notes/bug-1596818-d4cd93dd4d38d3d6.yaml new file mode 100644 index 000000000..6e31db8bf --- /dev/null +++ b/releasenotes/notes/bug-1596818-d4cd93dd4d38d3d6.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--sort`` support to ``project list`` by sorting items in client side + By default project list will be sorted by name. + [Bug `1596818 `_] \ No newline at end of file