diff --git a/doc/source/cli/data/glance.csv b/doc/source/cli/data/glance.csv index 0c34c7b953..90625a6faf 100644 --- a/doc/source/cli/data/glance.csv +++ b/doc/source/cli/data/glance.csv @@ -55,6 +55,6 @@ 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-show,,Describe a specific task. +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 new file mode 100644 index 0000000000..5f0c4ed5a7 --- /dev/null +++ b/openstackclient/image/v2/task.py @@ -0,0 +1,78 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from osc_lib.cli import format_columns +from osc_lib.command import command + +from openstackclient.i18n import _ + + +def _format_task(task): + """Format an task to make it more consistent with OSC operations.""" + + info = {} + properties = {} + + # the only fields we're not including is "links", "tags" and the properties + fields_to_show = [ + 'created_at', + 'expires_at', + 'id', + 'input', + 'message', + 'owner_id', + 'result', + 'status', + 'type', + 'updated_at', + ] + + # split out the usual key and the properties which are top-level + for field in fields_to_show: + info[field] = task.get(field) + + for key in task: + if key in fields_to_show: + continue + + if key in {'location', 'name', 'schema'}: + continue + + properties[key] = task.get(key) + + # add properties back into the dictionary as a top-level key + info['properties'] = format_columns.DictColumn(properties) + + return info + + +class ShowTask(command.ShowOne): + _description = _('Display task details') + + def get_parser(self, prog_name): + parser = super(ShowTask, self).get_parser(prog_name) + + parser.add_argument( + 'task', + metavar='<Task ID>', + help=_('Task to display (ID)'), + ) + + return parser + + def take_action(self, parsed_args): + image_client = self.app.client_manager.image + + task = image_client.get_task(parsed_args.task) + info = _format_task(task) + + return zip(*sorted(info.items())) diff --git a/openstackclient/tests/unit/image/v2/fakes.py b/openstackclient/tests/unit/image/v2/fakes.py index a0eda6d23a..2decd12292 100644 --- a/openstackclient/tests/unit/image/v2/fakes.py +++ b/openstackclient/tests/unit/image/v2/fakes.py @@ -18,6 +18,7 @@ import uuid from openstack.image.v2 import image from openstack.image.v2 import member +from openstack.image.v2 import task from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes @@ -44,6 +45,9 @@ class FakeImagev2Client: self.remove_tag = mock.Mock() + self.tasks = mock.Mock() + self.get_task = mock.Mock() + self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] self.version = 2.0 @@ -129,3 +133,53 @@ def create_one_image_member(attrs=None): image_member_info.update(attrs) return member.Member(**image_member_info) + + +def create_one_task(attrs=None): + """Create a fake task. + + :param attrs: A dictionary with all attributes of task + :type attrs: dict + :return: A fake Task object. + :rtype: `openstack.image.v2.task.Task` + """ + attrs = attrs or {} + + # Set default attribute + task_info = { + 'created_at': '2016-06-29T16:13:07Z', + 'expires_at': '2016-07-01T16:13:07Z', + 'id': str(uuid.uuid4()), + 'input': { + 'image_properties': { + 'container_format': 'ovf', + 'disk_format': 'vhd' + }, + 'import_from': 'https://apps.openstack.org/excellent-image', + 'import_from_format': 'qcow2' + }, + 'message': '', + 'owner': str(uuid.uuid4()), + 'result': { + 'image_id': str(uuid.uuid4()), + }, + 'schema': '/v2/schemas/task', + 'status': random.choice( + [ + 'pending', + 'processing', + 'success', + 'failure', + ] + ), + # though not documented, the API only allows 'import' + # 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) diff --git a/openstackclient/tests/unit/image/v2/test_task.py b/openstackclient/tests/unit/image/v2/test_task.py new file mode 100644 index 0000000000..90fa11d89e --- /dev/null +++ b/openstackclient/tests/unit/image/v2/test_task.py @@ -0,0 +1,80 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from osc_lib.cli import format_columns + +from openstackclient.image.v2 import task +from openstackclient.tests.unit.image.v2 import fakes as image_fakes + + +class TestTask(image_fakes.TestImagev2): + def setUp(self): + super().setUp() + + # Get shortcuts to mocked image client + self.client = self.app.client_manager.image + + +class TestTaskShow(TestTask): + + task = image_fakes.create_one_task() + + columns = ( + 'created_at', + 'expires_at', + 'id', + 'input', + 'message', + 'owner_id', + 'properties', + 'result', + 'status', + 'type', + 'updated_at', + ) + data = ( + task.created_at, + task.expires_at, + task.id, + task.input, + task.message, + task.owner_id, + format_columns.DictColumn({}), + task.result, + task.status, + task.type, + task.updated_at, + ) + + def setUp(self): + super().setUp() + + self.client.get_task.return_value = self.task + + # Get the command object to test + self.cmd = task.ShowTask(self.app, None) + + def test_task_show(self): + arglist = [self.task.id] + verifylist = [ + ('task', self.task.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class ShowOne in cliff, abstract method take_action() + # returns a two-part tuple with a tuple of column names and a tuple of + # data to be shown. + columns, data = self.cmd.take_action(parsed_args) + self.client.get_task.assert_called_with(self.task.id) + + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) diff --git a/releasenotes/notes/add-image-task-commands-50c3643ebfd0421f.yaml b/releasenotes/notes/add-image-task-commands-50c3643ebfd0421f.yaml new file mode 100644 index 0000000000..927f6a80db --- /dev/null +++ b/releasenotes/notes/add-image-task-commands-50c3643ebfd0421f.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Add ``image task show`` command to show a task for the image service. diff --git a/setup.cfg b/setup.cfg index fe5db3f2c3..1d00ec33af 100644 --- a/setup.cfg +++ b/setup.cfg @@ -382,6 +382,7 @@ openstack.image.v2 = image_show = openstackclient.image.v2.image:ShowImage image_set = openstackclient.image.v2.image:SetImage image_unset = openstackclient.image.v2.image:UnsetImage + image_task_show = openstackclient.image.v2.task:ShowTask openstack.network.v2 = address_group_create = openstackclient.network.v2.address_group:CreateAddressGroup