From 3e11661074e1a7e051e0ebff5800f8f1aac85153 Mon Sep 17 00:00:00 2001 From: sunyajing Date: Sat, 28 May 2016 11:01:22 +0800 Subject: [PATCH] Add "image unset" command This patch add a command that supports unsetting image tags and properties Change-Id: I6f2cf45a61ff89da6664f3a34ae49fdd85d8c986 Closes-Bug:#1582968 --- doc/source/command-objects/image.rst | 27 ++++++ functional/tests/image/v2/test_image.py | 9 ++ openstackclient/image/v2/image.py | 89 +++++++++++++++++- openstackclient/tests/image/v2/fakes.py | 2 + openstackclient/tests/image/v2/test_image.py | 93 +++++++++++++++++++ .../notes/bug-1582968-4d44912a033b242c.yaml | 4 + setup.cfg | 1 + 7 files changed, 223 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/bug-1582968-4d44912a033b242c.yaml diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index 942c03d57c..d6451af76e 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -499,3 +499,30 @@ Display image details .. describe:: Image to display (name or ID) + +image unset +----------- + +*Only supported for Image v2* + +Unset image tags or properties + +.. program:: image unset +.. code:: bash + + os image set + [--tag ] + [--property ] + + +.. option:: --tag + + Unset a tag on this image (repeat option to unset multiple tags) + +.. option:: --property + + Unset a property on this image (repeat option to unset multiple properties) + +.. describe:: + + Image to modify (name or ID) diff --git a/functional/tests/image/v2/test_image.py b/functional/tests/image/v2/test_image.py index f0ebc11665..6a33ad88e1 100644 --- a/functional/tests/image/v2/test_image.py +++ b/functional/tests/image/v2/test_image.py @@ -65,3 +65,12 @@ class ImageTests(test.TestCase): self.openstack('image set --property a=b --property c=d ' + self.NAME) raw_output = self.openstack('image show ' + self.NAME + opts) self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output) + + def test_image_unset(self): + opts = self.get_show_opts(["name", "tags", "properties"]) + self.openstack('image set --tag 01 ' + self.NAME) + self.openstack('image unset --tag 01 ' + self.NAME) + # test_image_metadata has set image properties "a" and "c" + self.openstack('image unset --property a --property c ' + self.NAME) + raw_output = self.openstack('image show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\n\n", raw_output) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index a9c0f1fdc1..a81f092c31 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -805,8 +805,8 @@ class SetImage(command.Command): # Checks if anything that requires getting the image if not (kwargs or parsed_args.deactivate or parsed_args.activate): - self.log.warning(_("No arguments specified")) - return {}, {} + msg = _("No arguments specified") + raise exceptions.CommandError(msg) image = utils.find_resource( image_client.images, parsed_args.image) @@ -856,3 +856,88 @@ class ShowImage(command.ShowOne): info = _format_image(image) return zip(*sorted(six.iteritems(info))) + + +class UnsetImage(command.Command): + """Unset image tags and properties""" + + def get_parser(self, prog_name): + parser = super(UnsetImage, self).get_parser(prog_name) + parser.add_argument( + "image", + metavar="", + help=_("Image to modify (name or ID)"), + ) + parser.add_argument( + "--tag", + dest="tags", + metavar="", + default=[], + action='append', + help=_("Unset a tag on this image " + "(repeat option to set multiple tags)"), + ) + parser.add_argument( + "--property", + dest="properties", + metavar="", + default=[], + action='append', + help=_("Unset a property on this image " + "(repeat option to set multiple properties)"), + ) + return parser + + def take_action(self, parsed_args): + image_client = self.app.client_manager.image + image = utils.find_resource( + image_client.images, + parsed_args.image, + ) + + if not (parsed_args.tags or parsed_args.properties): + msg = _("No arguments specified") + raise exceptions.CommandError(msg) + + kwargs = {} + tagret = 0 + propret = 0 + if parsed_args.tags: + for k in parsed_args.tags: + try: + image_client.image_tags.delete(image.id, k) + except Exception: + self.log.error(_("tag unset failed," + " '%s' is a nonexistent tag ") % k) + tagret += 1 + + if parsed_args.properties: + for k in parsed_args.properties: + try: + assert(k in image.keys()) + except AssertionError: + self.log.error(_("property unset failed," + " '%s' is a nonexistent property ") % k) + propret += 1 + image_client.images.update( + image.id, + parsed_args.properties, + **kwargs) + + tagtotal = len(parsed_args.tags) + proptotal = len(parsed_args.properties) + if (tagret > 0 and propret > 0): + msg = (_("Failed to unset %(tagret)s of %(tagtotal)s tags," + "Failed to unset %(propret)s of %(proptotal)s properties.") + % {'tagret': tagret, 'tagtotal': tagtotal, + 'propret': propret, 'proptotal': proptotal}) + raise exceptions.CommandError(msg) + elif tagret > 0: + msg = (_("Failed to unset %(target)s of %(tagtotal)s tags.") + % {'tagret': tagret, 'tagtotal': tagtotal}) + raise exceptions.CommandError(msg) + elif propret > 0: + msg = (_("Failed to unset %(propret)s of %(proptotal)s" + " properties.") + % {'propret': propret, 'proptotal': proptotal}) + raise exceptions.CommandError(msg) diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py index a662a58524..24aaec51f6 100644 --- a/openstackclient/tests/image/v2/fakes.py +++ b/openstackclient/tests/image/v2/fakes.py @@ -157,6 +157,8 @@ class FakeImagev2Client(object): self.images.resource_class = fakes.FakeResource(None, {}) self.image_members = mock.Mock() self.image_members.resource_class = fakes.FakeResource(None, {}) + self.image_tags = mock.Mock() + self.image_tags.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index beebdef995..ca20d83da4 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -37,6 +37,8 @@ class TestImage(image_fakes.TestImagev2): self.images_mock.reset_mock() self.image_members_mock = self.app.client_manager.image.image_members self.image_members_mock.reset_mock() + self.image_tags_mock = self.app.client_manager.image.image_tags + self.image_tags_mock.reset_mock() # Get shortcut to the Mocks in identity client self.project_mock = self.app.client_manager.identity.projects @@ -1184,3 +1186,94 @@ class TestImageShow(TestImage): self.assertEqual(image_fakes.IMAGE_columns, columns) self.assertEqual(image_fakes.IMAGE_SHOW_data, data) + + +class TestImageUnset(TestImage): + + attrs = {} + attrs['tags'] = ['test'] + attrs['prop'] = 'test' + image = image_fakes.FakeImage.create_one_image(attrs) + + def setUp(self): + super(TestImageUnset, self).setUp() + + # Set up the schema + self.model = warlock.model_factory( + image_fakes.IMAGE_schema, + schemas.SchemaBasedModel, + ) + + self.images_mock.get.return_value = self.image + self.image_tags_mock.delete.return_value = self.image + + # Get the command object to test + self.cmd = image.UnsetImage(self.app, None) + + def test_image_unset_tag_option(self): + + arglist = [ + '--tag', 'test', + self.image.id, + ] + + verifylist = [ + ('tags', ['test']), + ('image', self.image.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.image_tags_mock.delete.assert_called_with( + self.image.id, 'test' + ) + self.assertIsNone(result) + + def test_image_unset_property_option(self): + + arglist = [ + '--property', 'prop', + self.image.id, + ] + + verifylist = [ + ('properties', ['prop']), + ('image', self.image.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + kwargs = {} + self.images_mock.update.assert_called_with( + self.image.id, + parsed_args.properties, + **kwargs) + + self.assertIsNone(result) + + def test_image_unset_mixed_option(self): + + arglist = [ + '--tag', 'test', + '--property', 'prop', + self.image.id, + ] + + verifylist = [ + ('tags', ['test']), + ('properties', ['prop']), + ('image', self.image.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + kwargs = {} + self.images_mock.update.assert_called_with( + self.image.id, + parsed_args.properties, + **kwargs) + + self.image_tags_mock.delete.assert_called_with( + self.image.id, 'test' + ) + self.assertIsNone(result) diff --git a/releasenotes/notes/bug-1582968-4d44912a033b242c.yaml b/releasenotes/notes/bug-1582968-4d44912a033b242c.yaml new file mode 100644 index 0000000000..9d9794e31a --- /dev/null +++ b/releasenotes/notes/bug-1582968-4d44912a033b242c.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add "image unset" command. + [Bug '1582968 '] diff --git a/setup.cfg b/setup.cfg index a62f5d25cd..a2da70489c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -322,6 +322,7 @@ openstack.image.v2 = image_save = openstackclient.image.v2.image:SaveImage image_show = openstackclient.image.v2.image:ShowImage image_set = openstackclient.image.v2.image:SetImage + image_unset = openstackclient.image.v2.image:UnsetImage openstack.network.v2 = address_scope_create = openstackclient.network.v2.address_scope:CreateAddressScope