From c9d445fc4baf036793103b15c9eb2632da3610e0 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 7 Jul 2022 12:31:44 +0100 Subject: [PATCH] image: Add 'image task list' command This replaces the 'glance task-list' command. $ openstack image task list We also indicate that the 'image task create' command will never be implemented. This is an admin-only API that isn't really intended to be used by humans thus it does not need an OSC command implementation. Change-Id: Id8a943a5443782fc70c0fbf3639f5aa17b9d30af --- doc/source/cli/data/glance.csv | 4 +- openstackclient/image/v2/task.py | 101 +++++++++++++++++ openstackclient/tests/unit/image/v2/fakes.py | 21 +++- .../tests/unit/image/v2/test_task.py | 107 ++++++++++++++++++ ...-image-task-commands-50c3643ebfd0421f.yaml | 2 + setup.cfg | 1 + 6 files changed, 233 insertions(+), 3 deletions(-) diff --git a/doc/source/cli/data/glance.csv b/doc/source/cli/data/glance.csv index c44c50896f..7dd967c914 100644 --- a/doc/source/cli/data/glance.csv +++ b/doc/source/cli/data/glance.csv @@ -53,8 +53,8 @@ member-list,,Describe sharing permissions by image. member-update,image set --accept --reject --status,Update the status of a member for a given image. stores-delete,,Delete image from specific store. stores-info,,Print available backends from Glance. -task-create,,Create a new task. -task-list,,List tasks you can access. +task-create,WONTFIX,Create a new task. +task-list,image task list,List tasks you can access. task-show,image task show,Describe a specific task. bash-completion,complete,Prints arguments for bash_completion. help,help,Display help about this program or one of its subcommands. diff --git a/openstackclient/image/v2/task.py b/openstackclient/image/v2/task.py index 5f0c4ed5a7..924eaaf13d 100644 --- a/openstackclient/image/v2/task.py +++ b/openstackclient/image/v2/task.py @@ -12,9 +12,14 @@ from osc_lib.cli import format_columns from osc_lib.command import command +from osc_lib import utils from openstackclient.i18n import _ +_formatters = { + 'tags': format_columns.ListColumn, +} + def _format_task(task): """Format an task to make it more consistent with OSC operations.""" @@ -76,3 +81,99 @@ class ShowTask(command.ShowOne): info = _format_task(task) return zip(*sorted(info.items())) + + +class ListTask(command.Lister): + _description = _('List tasks') + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + + parser.add_argument( + '--sort-key', + metavar='[:]', + help=_( + 'Sorts the response by one of the following attributes: ' + 'created_at, expires_at, id, status, type, updated_at. ' + '(default is created_at) ' + '(multiple keys and directions can be specified separated ' + 'by comma)' + ), + ) + parser.add_argument( + '--sort-dir', + metavar='[:]', + help=_( + 'Sort output by selected keys and directions (asc or desc) ' + '(default: name:desc) ' + '(multiple keys and directions can be specified separated ' + 'by comma)' + ), + ) + parser.add_argument( + '--limit', + metavar='', + type=int, + help=_('Maximum number of tasks to display.'), + ) + parser.add_argument( + '--marker', + metavar='', + help=_( + 'The last task of the previous page. ' + 'Display list of tasks after marker. ' + 'Display all tasks if not specified. ' + '(name or ID)' + ), + ) + parser.add_argument( + '--type', + metavar='', + choices=['import'], + help=_('Filters the response by a task type.'), + ) + parser.add_argument( + '--status', + metavar='', + choices=[ + 'pending', + 'processing', + 'success', + 'failure', + ], + help=_('Filter tasks based on status.'), + ) + + return parser + + def take_action(self, parsed_args): + image_client = self.app.client_manager.image + + columns = ('id', 'type', 'status', 'owner_id') + column_headers = ('ID', 'Type', 'Status', 'Owner') + + kwargs = {} + copy_attrs = { + 'sort_key', + 'sort_dir', + 'limit', + 'marker', + 'type', + 'status', + } + for attr in copy_attrs: + val = getattr(parsed_args, attr, None) + if val is not None: + # Only include a value in kwargs for attributes that are + # actually present on the command line + kwargs[attr] = val + + data = image_client.tasks(**kwargs) + + return ( + column_headers, + ( + utils.get_item_properties(s, columns, formatters=_formatters) + for s in data + ), + ) diff --git a/openstackclient/tests/unit/image/v2/fakes.py b/openstackclient/tests/unit/image/v2/fakes.py index 2decd12292..f20154502e 100644 --- a/openstackclient/tests/unit/image/v2/fakes.py +++ b/openstackclient/tests/unit/image/v2/fakes.py @@ -52,6 +52,9 @@ class FakeImagev2Client: self.management_url = kwargs['endpoint'] self.version = 2.0 + self.tasks = mock.Mock() + self.tasks.resource_class = fakes.FakeResource(None, {}) + class TestImagev2(utils.TestCommand): @@ -176,10 +179,26 @@ def create_one_task(attrs=None): # https://github.com/openstack/glance/blob/24.0.0/glance/api/v2/tasks.py#L186-L190 'type': 'import', 'updated_at': '2016-06-29T16:13:07Z', - } # Overwrite default attributes if there are some attributes set task_info.update(attrs) return task.Task(**task_info) + + +def create_tasks(attrs=None, count=2): + """Create multiple fake tasks. + + :param attrs: A dictionary with all attributes of Task + :type attrs: dict + :param count: The number of tasks to be faked + :type count: int + :return: A list of fake Task objects + :rtype: list + """ + tasks = [] + for n in range(0, count): + tasks.append(create_one_task(attrs)) + + return tasks diff --git a/openstackclient/tests/unit/image/v2/test_task.py b/openstackclient/tests/unit/image/v2/test_task.py index 90fa11d89e..e077e2b140 100644 --- a/openstackclient/tests/unit/image/v2/test_task.py +++ b/openstackclient/tests/unit/image/v2/test_task.py @@ -78,3 +78,110 @@ class TestTaskShow(TestTask): self.assertEqual(self.columns, columns) self.assertCountEqual(self.data, data) + + +class TestTaskList(TestTask): + + tasks = image_fakes.create_tasks() + + columns = ( + 'ID', + 'Type', + 'Status', + 'Owner', + ) + datalist = [ + ( + task.id, + task.type, + task.status, + task.owner_id, + ) + for task in tasks + ] + + def setUp(self): + super().setUp() + + self.client.tasks.side_effect = [self.tasks, []] + + # Get the command object to test + self.cmd = task.ListTask(self.app, None) + + def test_task_list_no_options(self): + arglist = [] + verifylist = [ + ('sort_key', None), + ('sort_dir', None), + ('limit', None), + ('marker', None), + ('type', None), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.client.tasks.assert_called_with() + + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.datalist, data) + + def test_task_list_sort_key_option(self): + arglist = ['--sort-key', 'created_at'] + verifylist = [('sort_key', 'created_at')] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.client.tasks.assert_called_with( + sort_key=parsed_args.sort_key, + ) + + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.datalist, data) + + def test_task_list_sort_dir_option(self): + arglist = ['--sort-dir', 'desc'] + verifylist = [('sort_dir', 'desc')] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.client.tasks.assert_called_with( + sort_dir=parsed_args.sort_dir, + ) + + def test_task_list_pagination_options(self): + arglist = ['--limit', '1', '--marker', self.tasks[0].id] + verifylist = [('limit', 1), ('marker', self.tasks[0].id)] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.client.tasks.assert_called_with( + limit=parsed_args.limit, + marker=parsed_args.marker, + ) + + def test_task_list_type_option(self): + arglist = ['--type', self.tasks[0].type] + verifylist = [('type', self.tasks[0].type)] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.client.tasks.assert_called_with( + type=self.tasks[0].type, + ) + + def test_task_list_status_option(self): + arglist = ['--status', self.tasks[0].status] + verifylist = [('status', self.tasks[0].status)] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.client.tasks.assert_called_with( + status=self.tasks[0].status, + ) diff --git a/releasenotes/notes/add-image-task-commands-50c3643ebfd0421f.yaml b/releasenotes/notes/add-image-task-commands-50c3643ebfd0421f.yaml index 927f6a80db..1e4419792a 100644 --- a/releasenotes/notes/add-image-task-commands-50c3643ebfd0421f.yaml +++ b/releasenotes/notes/add-image-task-commands-50c3643ebfd0421f.yaml @@ -2,3 +2,5 @@ features: - | Add ``image task show`` command to show a task for the image service. + - | + Add ``image task list`` command to list tasks for the image service. diff --git a/setup.cfg b/setup.cfg index 1d00ec33af..fd43d1ab84 100644 --- a/setup.cfg +++ b/setup.cfg @@ -383,6 +383,7 @@ openstack.image.v2 = image_set = openstackclient.image.v2.image:SetImage image_unset = openstackclient.image.v2.image:UnsetImage image_task_show = openstackclient.image.v2.task:ShowTask + image_task_list = openstackclient.image.v2.task:ListTask openstack.network.v2 = address_group_create = openstackclient.network.v2.address_group:CreateAddressGroup