Add "image unset" command

This patch add a command that supports
unsetting image tags and properties

Change-Id: I6f2cf45a61ff89da6664f3a34ae49fdd85d8c986
Closes-Bug:#1582968
This commit is contained in:
sunyajing 2016-05-28 11:01:22 +08:00
parent 9da02d14ea
commit 3e11661074
7 changed files with 223 additions and 2 deletions

View File

@ -499,3 +499,30 @@ Display image details
.. describe:: <image>
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 <tag>]
[--property <property>]
<image>
.. option:: --tag <tag>
Unset a tag on this image (repeat option to unset multiple tags)
.. option:: --property <property>
Unset a property on this image (repeat option to unset multiple properties)
.. describe:: <image>
Image to modify (name or ID)

View File

@ -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)

View File

@ -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="<image>",
help=_("Image to modify (name or ID)"),
)
parser.add_argument(
"--tag",
dest="tags",
metavar="<tag>",
default=[],
action='append',
help=_("Unset a tag on this image "
"(repeat option to set multiple tags)"),
)
parser.add_argument(
"--property",
dest="properties",
metavar="<property_key>",
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)

View File

@ -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']

View File

@ -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)

View File

@ -0,0 +1,4 @@
---
features:
- Add "image unset" command.
[Bug '1582968 <https://bugs.launchpad.net/python-openstackclient/+bug/1582968>']

View File

@ -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