diff --git a/doc/source/cli/data/glance.csv b/doc/source/cli/data/glance.csv index fd3b930936..1481610ec1 100644 --- a/doc/source/cli/data/glance.csv +++ b/doc/source/cli/data/glance.csv @@ -18,7 +18,7 @@ image-tag-update,image set --tag ,Update an image with the given tag. image-tasks,,Get tasks associated with image. image-update,image set,Update an existing image. image-upload,,Upload data for a specific image. -import-info,,Print import methods available from Glance. +import-info,image import info,Show available import methods from Glance. location-add,,Add a location (and related metadata) to an image. location-delete,,Remove locations (and related metadata) from an image. location-update,,Update metadata of an image's location. @@ -57,7 +57,7 @@ member-delete,image remove project,Delete image member. member-get,,Show details of an image member member-list,image 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-delete,image delete --store,Delete image from specific store. stores-info,,Print available backends from Glance. task-create,WONTFIX,Create a new task. task-list,image task list,List tasks you can access. diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 8a0066ca5e..89212c6925 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -653,6 +653,13 @@ class DeleteImage(command.Command): nargs="+", help=_("Image(s) to delete (name or ID)"), ) + parser.add_argument( + '--store', + metavar='', + # default=None, + dest='store', + help=_('Store to delete image(s) from.'), + ) return parser def take_action(self, parsed_args): @@ -664,7 +671,18 @@ class DeleteImage(command.Command): image, ignore_missing=False, ) - image_client.delete_image(image_obj.id) + except sdk_exceptions.ResourceNotFound as e: + msg = _("Unable to process request: %(e)s") % {'e': e} + raise exceptions.CommandError(msg) + try: + image_client.delete_image( + image_obj.id, + store=parsed_args.store, + ignore_missing=False, + ) + except sdk_exceptions.ResourceNotFound: + msg = _("Multi Backend support not enabled.") + raise exceptions.CommandError(msg) except Exception as e: del_result += 1 msg = _( diff --git a/openstackclient/image/v2/info.py b/openstackclient/image/v2/info.py new file mode 100644 index 0000000000..469b15ac29 --- /dev/null +++ b/openstackclient/image/v2/info.py @@ -0,0 +1,31 @@ +# 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 _ + + +class ImportInfo(command.ShowOne): + _description = _("Show available import methods") + + def take_action(self, parsed_args): + image_client = self.app.client_manager.image + + import_info = image_client.get_import_info() + import_methods = import_info.import_methods or {} + return ( + ('import-methods',), + (format_columns.ListColumn(import_methods.get('value', [])),), + ) diff --git a/openstackclient/tests/functional/image/v2/test_info.py b/openstackclient/tests/functional/image/v2/test_info.py new file mode 100644 index 0000000000..a04e0cb56a --- /dev/null +++ b/openstackclient/tests/functional/image/v2/test_info.py @@ -0,0 +1,30 @@ +# Copyright 2023 Red Hat. +# All Rights Reserved. +# +# 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 openstackclient.tests.functional.image import base + + +class InfoTests(base.BaseImageTests): + """Functional tests for Info commands""" + + def setUp(self): + super(InfoTests, self).setUp() + + def tearDown(self): + super().tearDown() + + def test_image_import_info(self): + output = self.openstack('image import info', parse_output=True) + self.assertIsNotNone(output['import-methods']) diff --git a/openstackclient/tests/unit/image/v2/fakes.py b/openstackclient/tests/unit/image/v2/fakes.py index af8475cba9..706ee44ae0 100644 --- a/openstackclient/tests/unit/image/v2/fakes.py +++ b/openstackclient/tests/unit/image/v2/fakes.py @@ -41,6 +41,7 @@ class FakeImagev2Client: self.deactivate_image = mock.Mock() self.stage_image = mock.Mock() self.import_image = mock.Mock() + self.get_import_info = mock.Mock() self.members = mock.Mock() self.add_member = mock.Mock() diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 934f9baf49..ec6dc9527d 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -520,7 +520,29 @@ class TestImageDelete(TestImage): result = self.cmd.take_action(parsed_args) - self.client.delete_image.assert_called_with(images[0].id) + self.client.delete_image.assert_called_with( + images[0].id, store=parsed_args.store, ignore_missing=False + ) + self.assertIsNone(result) + + def test_image_delete_from_store(self): + images = self.setup_images_mock(count=1) + + arglist = [ + images[0].id, + '--store', + 'store1', + ] + verifylist = [('images', [images[0].id]), ('store', 'store1')] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.client.find_image.side_effect = images + + result = self.cmd.take_action(parsed_args) + + self.client.delete_image.assert_called_with( + images[0].id, store=parsed_args.store, ignore_missing=False + ) self.assertIsNone(result) def test_image_delete_multi_images(self): @@ -536,10 +558,33 @@ class TestImageDelete(TestImage): result = self.cmd.take_action(parsed_args) - calls = [mock.call(i.id) for i in images] + calls = [ + mock.call(i.id, store=parsed_args.store, ignore_missing=False) + for i in images + ] self.client.delete_image.assert_has_calls(calls) self.assertIsNone(result) + def test_image_delete_from_store_without_multi_backend(self): + images = self.setup_images_mock(count=1) + + arglist = [images[0].id, '--store', 'store1'] + verifylist = [('images', [images[0].id]), ('store', 'store1')] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.client.find_image.side_effect = images + + self.client.delete_image.side_effect = sdk_exceptions.ResourceNotFound + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args, + ) + self.assertIn( + "Multi Backend support not enabled", + str(exc), + ) + def test_image_delete_multi_images_exception(self): images = image_fakes.create_images(count=2) arglist = [ @@ -562,7 +607,10 @@ class TestImageDelete(TestImage): self.assertRaises( exceptions.CommandError, self.cmd.take_action, parsed_args ) - calls = [mock.call(i.id) for i in images] + calls = [ + mock.call(i.id, store=parsed_args.store, ignore_missing=False) + for i in images + ] self.client.delete_image.assert_has_calls(calls) diff --git a/openstackclient/tests/unit/image/v2/test_info.py b/openstackclient/tests/unit/image/v2/test_info.py new file mode 100644 index 0000000000..2489bfc9e0 --- /dev/null +++ b/openstackclient/tests/unit/image/v2/test_info.py @@ -0,0 +1,43 @@ +# Copyright 2023 Red Hat. +# All Rights Reserved. +# +# 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 openstackclient.image.v2 import info +from openstackclient.tests.unit.image.v2 import fakes as info_fakes + + +class TestInfo(info_fakes.TestImagev2): + def setUp(self): + super().setUp() + + # Get shortcuts to mocked image client + self.client = self.app.client_manager.image + + +class TestImportInfo(TestInfo): + import_info = info_fakes.create_one_import_info() + + def setUp(self): + super().setUp() + + self.client.get_import_info.return_value = self.import_info + + self.cmd = info.ImportInfo(self.app, None) + + def test_import_info(self): + arglist = [] + parsed_args = self.check_parser(self.cmd, arglist, []) + self.cmd.take_action(parsed_args) + + self.client.get_import_info.assert_called() diff --git a/releasenotes/notes/add-import-info-stores-delete-c50b5222c21e1077.yaml b/releasenotes/notes/add-import-info-stores-delete-c50b5222c21e1077.yaml new file mode 100644 index 0000000000..f6038c3c5e --- /dev/null +++ b/releasenotes/notes/add-import-info-stores-delete-c50b5222c21e1077.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``image import info`` command, allowing users to know available import + methods, and `--store` option to ``image delete``, allowing users to delete + image from particular store. diff --git a/setup.cfg b/setup.cfg index 6578d31f1d..819dc23b71 100644 --- a/setup.cfg +++ b/setup.cfg @@ -387,7 +387,7 @@ openstack.image.v2 = image_stage = openstackclient.image.v2.image:StageImage image_task_show = openstackclient.image.v2.task:ShowTask image_task_list = openstackclient.image.v2.task:ListTask - image_import = openstackclient.image.v2.image:ImportImage + image_import_info = openstackclient.image.v2.info:ImportInfo image_metadef_namespace_create = openstackclient.image.v2.metadef_namespaces:CreateMetadefNameSpace image_metadef_namespace_delete = openstackclient.image.v2.metadef_namespaces:DeleteMetadefNameSpace