diff --git a/doc/source/command-errors.rst b/doc/source/command-errors.rst index 48b828fd42..c4adb7d190 100644 --- a/doc/source/command-errors.rst +++ b/doc/source/command-errors.rst @@ -197,9 +197,6 @@ multiple ``delete_network()`` calls. if ret > 0: total = len(parsed_args.network) - msg = _("Failed to delete %(ret)s of %(total)s networks.") % - { - "ret": ret, - "total": total, - } + msg = (_("Failed to delete %(ret)s of %(total)s networks.") + % {"ret": ret, "total": total}) raise exceptions.CommandError(msg) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 62f7bee88c..d20fdba607 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -372,13 +372,27 @@ class DeleteImage(command.Command): return parser def take_action(self, parsed_args): + + del_result = 0 image_client = self.app.client_manager.image for image in parsed_args.images: - image_obj = utils.find_resource( - image_client.images, - image, - ) - image_client.images.delete(image_obj.id) + try: + image_obj = utils.find_resource( + image_client.images, + image, + ) + image_client.images.delete(image_obj.id) + except Exception as e: + del_result += 1 + self.app.log.error(_("Failed to delete image with " + "name or ID '%(image)s': %(e)s") + % {'image': image, 'e': e}) + + total = len(parsed_args.images) + if (del_result > 0): + msg = (_("Failed to delete %(dresult)s of %(total)s images.") + % {'dresult': del_result, 'total': total}) + raise exceptions.CommandError(msg) class ListImage(command.Lister): diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 92e0660faf..96e8734f8a 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -474,6 +474,37 @@ class TestImageDelete(TestImage): self.images_mock.delete.assert_has_calls(calls) self.assertIsNone(result) + def test_image_delete_multi_images_exception(self): + + images = image_fakes.FakeImage.create_images(count=2) + arglist = [ + images[0].id, + images[1].id, + 'x-y-x', + ] + verifylist = [ + ('images', arglist) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Fake exception in utils.find_resource() + # In image v2, we use utils.find_resource() to find a network. + # It calls get() several times, but find() only one time. So we + # choose to fake get() always raise exception, then pass through. + # And fake find() to find the real network or not. + ret_find = [ + images[0], + images[1], + exceptions.NotFound('404'), + ] + + self.images_mock.get = Exception() + self.images_mock.find.side_effect = ret_find + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + calls = [mock.call(i.id) for i in images] + self.images_mock.delete.assert_has_calls(calls) + class TestImageList(TestImage):