Skip to content

Commit 3e11661

Browse files
author
sunyajing
committed
Add "image unset" command
This patch add a command that supports unsetting image tags and properties Change-Id: I6f2cf45a61ff89da6664f3a34ae49fdd85d8c986 Closes-Bug:#1582968
1 parent 9da02d1 commit 3e11661

7 files changed

Lines changed: 223 additions & 2 deletions

File tree

doc/source/command-objects/image.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,3 +499,30 @@ Display image details
499499
.. describe:: <image>
500500
501501
Image to display (name or ID)
502+
503+
image unset
504+
-----------
505+
506+
*Only supported for Image v2*
507+
508+
Unset image tags or properties
509+
510+
.. program:: image unset
511+
.. code:: bash
512+
513+
os image set
514+
[--tag <tag>]
515+
[--property <property>]
516+
<image>
517+
518+
.. option:: --tag <tag>
519+
520+
Unset a tag on this image (repeat option to unset multiple tags)
521+
522+
.. option:: --property <property>
523+
524+
Unset a property on this image (repeat option to unset multiple properties)
525+
526+
.. describe:: <image>
527+
528+
Image to modify (name or ID)

functional/tests/image/v2/test_image.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,12 @@ def test_image_metadata(self):
6565
self.openstack('image set --property a=b --property c=d ' + self.NAME)
6666
raw_output = self.openstack('image show ' + self.NAME + opts)
6767
self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output)
68+
69+
def test_image_unset(self):
70+
opts = self.get_show_opts(["name", "tags", "properties"])
71+
self.openstack('image set --tag 01 ' + self.NAME)
72+
self.openstack('image unset --tag 01 ' + self.NAME)
73+
# test_image_metadata has set image properties "a" and "c"
74+
self.openstack('image unset --property a --property c ' + self.NAME)
75+
raw_output = self.openstack('image show ' + self.NAME + opts)
76+
self.assertEqual(self.NAME + "\n\n", raw_output)

openstackclient/image/v2/image.py

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -805,8 +805,8 @@ def take_action(self, parsed_args):
805805

806806
# Checks if anything that requires getting the image
807807
if not (kwargs or parsed_args.deactivate or parsed_args.activate):
808-
self.log.warning(_("No arguments specified"))
809-
return {}, {}
808+
msg = _("No arguments specified")
809+
raise exceptions.CommandError(msg)
810810

811811
image = utils.find_resource(
812812
image_client.images, parsed_args.image)
@@ -856,3 +856,88 @@ def take_action(self, parsed_args):
856856

857857
info = _format_image(image)
858858
return zip(*sorted(six.iteritems(info)))
859+
860+
861+
class UnsetImage(command.Command):
862+
"""Unset image tags and properties"""
863+
864+
def get_parser(self, prog_name):
865+
parser = super(UnsetImage, self).get_parser(prog_name)
866+
parser.add_argument(
867+
"image",
868+
metavar="<image>",
869+
help=_("Image to modify (name or ID)"),
870+
)
871+
parser.add_argument(
872+
"--tag",
873+
dest="tags",
874+
metavar="<tag>",
875+
default=[],
876+
action='append',
877+
help=_("Unset a tag on this image "
878+
"(repeat option to set multiple tags)"),
879+
)
880+
parser.add_argument(
881+
"--property",
882+
dest="properties",
883+
metavar="<property_key>",
884+
default=[],
885+
action='append',
886+
help=_("Unset a property on this image "
887+
"(repeat option to set multiple properties)"),
888+
)
889+
return parser
890+
891+
def take_action(self, parsed_args):
892+
image_client = self.app.client_manager.image
893+
image = utils.find_resource(
894+
image_client.images,
895+
parsed_args.image,
896+
)
897+
898+
if not (parsed_args.tags or parsed_args.properties):
899+
msg = _("No arguments specified")
900+
raise exceptions.CommandError(msg)
901+
902+
kwargs = {}
903+
tagret = 0
904+
propret = 0
905+
if parsed_args.tags:
906+
for k in parsed_args.tags:
907+
try:
908+
image_client.image_tags.delete(image.id, k)
909+
except Exception:
910+
self.log.error(_("tag unset failed,"
911+
" '%s' is a nonexistent tag ") % k)
912+
tagret += 1
913+
914+
if parsed_args.properties:
915+
for k in parsed_args.properties:
916+
try:
917+
assert(k in image.keys())
918+
except AssertionError:
919+
self.log.error(_("property unset failed,"
920+
" '%s' is a nonexistent property ") % k)
921+
propret += 1
922+
image_client.images.update(
923+
image.id,
924+
parsed_args.properties,
925+
**kwargs)
926+
927+
tagtotal = len(parsed_args.tags)
928+
proptotal = len(parsed_args.properties)
929+
if (tagret > 0 and propret > 0):
930+
msg = (_("Failed to unset %(tagret)s of %(tagtotal)s tags,"
931+
"Failed to unset %(propret)s of %(proptotal)s properties.")
932+
% {'tagret': tagret, 'tagtotal': tagtotal,
933+
'propret': propret, 'proptotal': proptotal})
934+
raise exceptions.CommandError(msg)
935+
elif tagret > 0:
936+
msg = (_("Failed to unset %(target)s of %(tagtotal)s tags.")
937+
% {'tagret': tagret, 'tagtotal': tagtotal})
938+
raise exceptions.CommandError(msg)
939+
elif propret > 0:
940+
msg = (_("Failed to unset %(propret)s of %(proptotal)s"
941+
" properties.")
942+
% {'propret': propret, 'proptotal': proptotal})
943+
raise exceptions.CommandError(msg)

openstackclient/tests/image/v2/fakes.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ def __init__(self, **kwargs):
157157
self.images.resource_class = fakes.FakeResource(None, {})
158158
self.image_members = mock.Mock()
159159
self.image_members.resource_class = fakes.FakeResource(None, {})
160+
self.image_tags = mock.Mock()
161+
self.image_tags.resource_class = fakes.FakeResource(None, {})
160162
self.auth_token = kwargs['token']
161163
self.management_url = kwargs['endpoint']
162164

openstackclient/tests/image/v2/test_image.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ def setUp(self):
3737
self.images_mock.reset_mock()
3838
self.image_members_mock = self.app.client_manager.image.image_members
3939
self.image_members_mock.reset_mock()
40+
self.image_tags_mock = self.app.client_manager.image.image_tags
41+
self.image_tags_mock.reset_mock()
4042

4143
# Get shortcut to the Mocks in identity client
4244
self.project_mock = self.app.client_manager.identity.projects
@@ -1184,3 +1186,94 @@ def test_image_show(self):
11841186

11851187
self.assertEqual(image_fakes.IMAGE_columns, columns)
11861188
self.assertEqual(image_fakes.IMAGE_SHOW_data, data)
1189+
1190+
1191+
class TestImageUnset(TestImage):
1192+
1193+
attrs = {}
1194+
attrs['tags'] = ['test']
1195+
attrs['prop'] = 'test'
1196+
image = image_fakes.FakeImage.create_one_image(attrs)
1197+
1198+
def setUp(self):
1199+
super(TestImageUnset, self).setUp()
1200+
1201+
# Set up the schema
1202+
self.model = warlock.model_factory(
1203+
image_fakes.IMAGE_schema,
1204+
schemas.SchemaBasedModel,
1205+
)
1206+
1207+
self.images_mock.get.return_value = self.image
1208+
self.image_tags_mock.delete.return_value = self.image
1209+
1210+
# Get the command object to test
1211+
self.cmd = image.UnsetImage(self.app, None)
1212+
1213+
def test_image_unset_tag_option(self):
1214+
1215+
arglist = [
1216+
'--tag', 'test',
1217+
self.image.id,
1218+
]
1219+
1220+
verifylist = [
1221+
('tags', ['test']),
1222+
('image', self.image.id),
1223+
]
1224+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
1225+
result = self.cmd.take_action(parsed_args)
1226+
1227+
self.image_tags_mock.delete.assert_called_with(
1228+
self.image.id, 'test'
1229+
)
1230+
self.assertIsNone(result)
1231+
1232+
def test_image_unset_property_option(self):
1233+
1234+
arglist = [
1235+
'--property', 'prop',
1236+
self.image.id,
1237+
]
1238+
1239+
verifylist = [
1240+
('properties', ['prop']),
1241+
('image', self.image.id)
1242+
]
1243+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
1244+
result = self.cmd.take_action(parsed_args)
1245+
1246+
kwargs = {}
1247+
self.images_mock.update.assert_called_with(
1248+
self.image.id,
1249+
parsed_args.properties,
1250+
**kwargs)
1251+
1252+
self.assertIsNone(result)
1253+
1254+
def test_image_unset_mixed_option(self):
1255+
1256+
arglist = [
1257+
'--tag', 'test',
1258+
'--property', 'prop',
1259+
self.image.id,
1260+
]
1261+
1262+
verifylist = [
1263+
('tags', ['test']),
1264+
('properties', ['prop']),
1265+
('image', self.image.id)
1266+
]
1267+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
1268+
result = self.cmd.take_action(parsed_args)
1269+
1270+
kwargs = {}
1271+
self.images_mock.update.assert_called_with(
1272+
self.image.id,
1273+
parsed_args.properties,
1274+
**kwargs)
1275+
1276+
self.image_tags_mock.delete.assert_called_with(
1277+
self.image.id, 'test'
1278+
)
1279+
self.assertIsNone(result)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
features:
3+
- Add "image unset" command.
4+
[Bug '1582968 <https://bugs.launchpad.net/python-openstackclient/+bug/1582968>']

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ openstack.image.v2 =
322322
image_save = openstackclient.image.v2.image:SaveImage
323323
image_show = openstackclient.image.v2.image:ShowImage
324324
image_set = openstackclient.image.v2.image:SetImage
325+
image_unset = openstackclient.image.v2.image:UnsetImage
325326

326327
openstack.network.v2 =
327328
address_scope_create = openstackclient.network.v2.address_scope:CreateAddressScope

0 commit comments

Comments
 (0)