Skip to content

Commit 1afb574

Browse files
NiallBuntingSteve Martinelli
authored andcommitted
Add tags to image set
This adds --tag to the v2 version of `image set`. This is another step to compatability between the osc image api. Added merge of tags into existing tags and handling duplicates, and tests for same. Co-Authored-By: Steve Martinelli <stevemar@ca.ibm.com> Change-Id: Ie800fcbf8bbc0978c54ace3278750a18023e8ce4
1 parent 201b1ce commit 1afb574

5 files changed

Lines changed: 122 additions & 10 deletions

File tree

doc/source/command-objects/image.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ Set image properties
238238
[--checksum <checksum>]
239239
[--stdin]
240240
[--property <key=value> [...] ]
241+
[--tag <tag> [...] ]
241242
[--architecture <architecture>]
242243
[--instance-id <instance-id>]
243244
[--kernel-id <kernel-id>]
@@ -344,6 +345,14 @@ Set image properties
344345
345346
Set a property on this image (repeat option to set multiple properties)
346347
348+
.. versionadded:: 2
349+
350+
.. option:: --tag <tag>
351+
352+
Set a tag on this image (repeat for multiple values)
353+
354+
.. versionadded:: 2
355+
347356
.. option:: --architecture <architecture>
348357
349358
Operating system architecture

openstackclient/image/v2/image.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,7 @@ def _format_image(image):
5757
properties[key] = image.get(key)
5858

5959
# format the tags if they are there
60-
if image.get('tags'):
61-
info['tags'] = utils.format_list(image.get('tags'))
60+
info['tags'] = utils.format_list(image.get('tags'))
6261

6362
# add properties back into the dictionary as a top-level key
6463
if properties:
@@ -540,7 +539,6 @@ def get_parser(self, prog_name):
540539
# --force - needs adding
541540
# --checksum - maybe could be done client side
542541
# --stdin - could be implemented
543-
# --tags - needs adding
544542
parser.add_argument(
545543
"image",
546544
metavar="<image>",
@@ -610,6 +608,15 @@ def get_parser(self, prog_name):
610608
help="Set a property on this image "
611609
"(repeat option to set multiple properties)",
612610
)
611+
parser.add_argument(
612+
"--tag",
613+
dest="tags",
614+
metavar="<tag>",
615+
default=[],
616+
action='append',
617+
help="Set a tag on this image "
618+
"(repeat option to set multiple tags)",
619+
)
613620
parser.add_argument(
614621
"--architecture",
615622
metavar="<architecture>",
@@ -669,7 +676,7 @@ def take_action(self, parsed_args):
669676
copy_attrs = ('architecture', 'container_format', 'disk_format',
670677
'file', 'instance_id', 'kernel_id', 'locations',
671678
'min_disk', 'min_ram', 'name', 'os_distro', 'os_version',
672-
'owner', 'prefix', 'progress', 'ramdisk_id')
679+
'owner', 'prefix', 'progress', 'ramdisk_id', 'tags')
673680
for attr in copy_attrs:
674681
if attr in parsed_args:
675682
val = getattr(parsed_args, attr, None)
@@ -705,6 +712,10 @@ def take_action(self, parsed_args):
705712
image = utils.find_resource(
706713
image_client.images, parsed_args.image)
707714

715+
if parsed_args.tags:
716+
# Tags should be extended, but duplicates removed
717+
kwargs['tags'] = list(set(image.tags).union(set(parsed_args.tags)))
718+
708719
image = image_client.images.update(image.id, **kwargs)
709720
info = {}
710721
info.update(image)

openstackclient/tests/compute/v2/test_server.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -410,13 +410,14 @@ def test_server_image_create_no_options(self):
410410
compute_fakes.server_name,
411411
)
412412

413-
collist = ('id', 'name', 'owner', 'protected', 'visibility')
413+
collist = ('id', 'name', 'owner', 'protected', 'tags', 'visibility')
414414
self.assertEqual(collist, columns)
415415
datalist = (
416416
image_fakes.image_id,
417417
image_fakes.image_name,
418418
image_fakes.image_owner,
419419
image_fakes.image_protected,
420+
image_fakes.image_tags,
420421
image_fakes.image_visibility,
421422
)
422423
self.assertEqual(datalist, data)
@@ -441,13 +442,14 @@ def test_server_image_create_name(self):
441442
'img-nam',
442443
)
443444

444-
collist = ('id', 'name', 'owner', 'protected', 'visibility')
445+
collist = ('id', 'name', 'owner', 'protected', 'tags', 'visibility')
445446
self.assertEqual(collist, columns)
446447
datalist = (
447448
image_fakes.image_id,
448449
image_fakes.image_name,
449450
image_fakes.image_owner,
450451
image_fakes.image_protected,
452+
image_fakes.image_tags,
451453
image_fakes.image_visibility,
452454
)
453455
self.assertEqual(datalist, data)

openstackclient/tests/image/v2/fakes.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# under the License.
1414
#
1515

16+
import copy
1617
import mock
1718

1819
from openstackclient.tests import fakes
@@ -25,18 +26,24 @@
2526
image_owner = 'baal'
2627
image_protected = False
2728
image_visibility = 'public'
29+
image_tags = []
2830

2931
IMAGE = {
3032
'id': image_id,
3133
'name': image_name,
3234
'owner': image_owner,
3335
'protected': image_protected,
3436
'visibility': image_visibility,
37+
'tags': image_tags
3538
}
3639

3740
IMAGE_columns = tuple(sorted(IMAGE))
3841
IMAGE_data = tuple((IMAGE[x] for x in sorted(IMAGE)))
3942

43+
IMAGE_SHOW = copy.copy(IMAGE)
44+
IMAGE_SHOW['tags'] = ''
45+
IMAGE_SHOW_data = tuple((IMAGE_SHOW[x] for x in sorted(IMAGE_SHOW)))
46+
4047
member_status = 'pending'
4148
MEMBER = {
4249
'member_id': identity_fakes.project_id,
@@ -117,6 +124,14 @@
117124
"type": "string",
118125
"description": "Status of the image (READ-ONLY)"
119126
},
127+
"tags": {
128+
"items": {
129+
"type": "string",
130+
"maxLength": 255
131+
},
132+
"type": "array",
133+
"description": "List of strings related to the image"
134+
},
120135
"visibility": {
121136
"enum": [
122137
"public",

openstackclient/tests/image/v2/test_image.py

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def test_image_reserve_no_options(self):
9696
)
9797

9898
self.assertEqual(image_fakes.IMAGE_columns, columns)
99-
self.assertEqual(image_fakes.IMAGE_data, data)
99+
self.assertEqual(image_fakes.IMAGE_SHOW_data, data)
100100

101101
@mock.patch('glanceclient.common.utils.get_data_file', name='Open')
102102
def test_image_reserve_options(self, mock_open):
@@ -151,7 +151,7 @@ def test_image_reserve_options(self, mock_open):
151151
)
152152

153153
self.assertEqual(image_fakes.IMAGE_columns, columns)
154-
self.assertEqual(image_fakes.IMAGE_data, data)
154+
self.assertEqual(image_fakes.IMAGE_SHOW_data, data)
155155

156156
@mock.patch('glanceclient.common.utils.get_data_file', name='Open')
157157
def test_image_create_file(self, mock_open):
@@ -208,7 +208,7 @@ def test_image_create_file(self, mock_open):
208208
)
209209

210210
self.assertEqual(image_fakes.IMAGE_columns, columns)
211-
self.assertEqual(image_fakes.IMAGE_data, data)
211+
self.assertEqual(image_fakes.IMAGE_SHOW_data, data)
212212

213213
def test_image_create_dead_options(self):
214214

@@ -812,6 +812,81 @@ def test_image_set_fake_properties(self):
812812
**kwargs
813813
)
814814

815+
def test_image_set_tag(self):
816+
arglist = [
817+
'--tag', 'test-tag',
818+
image_fakes.image_name,
819+
]
820+
verifylist = [
821+
('tags', ['test-tag']),
822+
('image', image_fakes.image_name),
823+
]
824+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
825+
826+
# DisplayCommandBase.take_action() returns two tuples
827+
self.cmd.take_action(parsed_args)
828+
829+
kwargs = {
830+
'tags': ['test-tag'],
831+
}
832+
# ImageManager.update(image, **kwargs)
833+
self.images_mock.update.assert_called_with(
834+
image_fakes.image_id,
835+
**kwargs
836+
)
837+
838+
def test_image_set_tag_merge(self):
839+
old_image = copy.copy(image_fakes.IMAGE)
840+
old_image['tags'] = ['old1', 'new2']
841+
self.images_mock.get.return_value = self.model(**old_image)
842+
arglist = [
843+
'--tag', 'test-tag',
844+
image_fakes.image_name,
845+
]
846+
verifylist = [
847+
('tags', ['test-tag']),
848+
('image', image_fakes.image_name),
849+
]
850+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
851+
852+
# DisplayCommandBase.take_action() returns two tuples
853+
self.cmd.take_action(parsed_args)
854+
855+
kwargs = {
856+
'tags': ['old1', 'new2', 'test-tag'],
857+
}
858+
# ImageManager.update(image, **kwargs)
859+
a, k = self.images_mock.update.call_args
860+
self.assertEqual(image_fakes.image_id, a[0])
861+
self.assertTrue('tags' in k)
862+
self.assertEqual(set(kwargs['tags']), set(k['tags']))
863+
864+
def test_image_set_tag_merge_dupe(self):
865+
old_image = copy.copy(image_fakes.IMAGE)
866+
old_image['tags'] = ['old1', 'new2']
867+
self.images_mock.get.return_value = self.model(**old_image)
868+
arglist = [
869+
'--tag', 'old1',
870+
image_fakes.image_name,
871+
]
872+
verifylist = [
873+
('tags', ['old1']),
874+
('image', image_fakes.image_name),
875+
]
876+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
877+
878+
# DisplayCommandBase.take_action() returns two tuples
879+
self.cmd.take_action(parsed_args)
880+
881+
kwargs = {
882+
'tags': ['new2', 'old1'],
883+
}
884+
# ImageManager.update(image, **kwargs)
885+
a, k = self.images_mock.update.call_args
886+
self.assertEqual(image_fakes.image_id, a[0])
887+
self.assertTrue('tags' in k)
888+
self.assertEqual(set(kwargs['tags']), set(k['tags']))
889+
815890
def test_image_set_dead_options(self):
816891

817892
arglist = [
@@ -861,4 +936,4 @@ def test_image_show(self):
861936
)
862937

863938
self.assertEqual(image_fakes.IMAGE_columns, columns)
864-
self.assertEqual(image_fakes.IMAGE_data, data)
939+
self.assertEqual(image_fakes.IMAGE_SHOW_data, data)

0 commit comments

Comments
 (0)