Enable client V2 to update/delete tags for a given image.

Added the CLI option image-tag-update to associate a tag to an image via API V2.
Added the CLI option image-tag-delete to delete a tag associated with an image via API V2.

Related to bp glance-client-v2

Change-Id: I76060e1982223770a6c2c0bd9376d568af0df456
This commit is contained in:
Venkatesh Sampath 2013-06-25 17:58:42 +05:30
parent 62579fbb21
commit b9c1df8dfc
5 changed files with 206 additions and 4 deletions

View File

@ -18,6 +18,7 @@ import warlock
from glanceclient.common import http
from glanceclient.v2 import images
from glanceclient.v2 import image_members
from glanceclient.v2 import image_tags
from glanceclient.v2 import schemas
@ -34,8 +35,10 @@ class Client(object):
def __init__(self, *args, **kwargs):
self.http_client = http.HTTPClient(*args, **kwargs)
self.schemas = schemas.Controller(self.http_client)
image_model = self._get_image_model()
self.images = images.Controller(self.http_client,
self._get_image_model())
image_model)
self.image_tags = image_tags.Controller(self.http_client, image_model)
self.image_members = image_members.Controller(self.http_client,
self._get_member_model())

View File

@ -0,0 +1,40 @@
# Copyright 2013 OpenStack Foundation
# 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.
class Controller(object):
def __init__(self, http_client, model):
self.http_client = http_client
self.model = model
def update(self, image_id, tag_value):
"""
Update an image with the given tag.
:param image_id: image to be updated with the given tag.
:param tag_value: value of the tag.
"""
url = '/v2/images/%s/tags/%s' % (image_id, tag_value)
self.http_client.json_request('PUT', url)
def delete(self, image_id, tag_value):
"""
Delete the tag associated with the given image.
:param image_id: Image whose tag to be deleted.
:param tag_value: tag value to be deleted.
"""
url = '/v2/images/%s/tags/%s' % (image_id, tag_value)
self.http_client.json_request('DELETE', url)

View File

@ -133,3 +133,31 @@ def do_image_download(gc, args):
def do_image_delete(gc, args):
"""Delete specified image."""
gc.images.delete(args.id)
@utils.arg('image_id', metavar='<IMAGE_ID>',
help='Image to be updated with the given tag')
@utils.arg('tag_value', metavar='<TAG_VALUE>',
help='Value of the tag')
def do_image_tag_update(gc, args):
"""Update an image with the given tag."""
if not (args.image_id and args.tag_value):
utils.exit('Unable to update tag. Specify image_id and tag_value')
else:
gc.image_tags.update(args.image_id, args.tag_value)
image = gc.images.get(args.image_id)
image = [image]
columns = ['ID', 'Tags']
utils.print_list(image, columns)
@utils.arg('image_id', metavar='<IMAGE_ID>',
help='Image whose tag to be deleted')
@utils.arg('tag_value', metavar='<TAG_VALUE>',
help='Value of the tag')
def do_image_tag_delete(gc, args):
"""Delete the tag associated with the given image."""
if not (args.image_id and args.tag_value):
utils.exit('Unable to delete tag. Specify image_id and tag_value')
else:
gc.image_tags.delete(args.image_id, args.tag_value)

View File

@ -24,6 +24,12 @@ from glanceclient.v2 import shell as test_shell
class LegacyShellV1Test(testtools.TestCase):
def _mock_glance_client(self):
my_mocked_gc = mock.Mock()
my_mocked_gc.schemas.return_value = 'test'
my_mocked_gc.get.return_value = {}
return my_mocked_gc
def test_do_image_list(self):
gc = client.Client('1', 'http://no.where')
@ -51,9 +57,7 @@ class LegacyShellV1Test(testtools.TestCase):
actual = test_shell.do_image_show(gc, Fake())
def test_do_explain(self):
my_mocked_gc = mock.Mock()
my_mocked_gc.schemas.return_value = 'test'
my_mocked_gc.get.return_value = {}
my_mocked_gc = self._mock_glance_client()
class Fake():
def __init__(self):
@ -84,3 +88,54 @@ class LegacyShellV1Test(testtools.TestCase):
with mock.patch.object(gc.images, 'delete') as mocked_delete:
mocked_delete.return_value = 0
test_shell.do_image_delete(gc, Fake())
def test_image_tag_update(self):
class Fake():
image_id = 'IMG-01'
tag_value = 'tag01'
gc = self._mock_glance_client()
with mock.patch.object(gc.image_tags, 'update') as mocked_update:
gc.images.get = mock.Mock(return_value={})
mocked_update.return_value = None
test_shell.do_image_tag_update(gc, Fake())
mocked_update.assert_called_once_with('IMG-01', 'tag01')
def test_image_tag_update_with_few_arguments(self):
class Fake():
image_id = None
tag_value = 'tag01'
gc = self._mock_glance_client()
with mock.patch.object(utils, 'exit') as mocked_utils_exit:
err_msg = 'Unable to update tag. Specify image_id and tag_value'
mocked_utils_exit.return_value = '%s' % err_msg
test_shell.do_image_tag_update(gc, Fake())
mocked_utils_exit.assert_called_once_with(err_msg)
def test_image_tag_delete(self):
class Fake():
image_id = 'IMG-01'
tag_value = 'tag01'
gc = self._mock_glance_client()
with mock.patch.object(gc.image_tags, 'delete') as mocked_delete:
mocked_delete.return_value = None
test_shell.do_image_tag_delete(gc, Fake())
mocked_delete.assert_called_once_with('IMG-01', 'tag01')
def test_image_tag_delete_with_few_arguments(self):
class Fake():
image_id = 'IMG-01'
tag_value = None
gc = self._mock_glance_client()
with mock.patch.object(utils, 'exit') as mocked_utils_exit:
err_msg = 'Unable to delete tag. Specify image_id and tag_value'
mocked_utils_exit.return_value = '%s' % err_msg
test_shell.do_image_tag_delete(gc, Fake())
mocked_utils_exit.assert_called_once_with(err_msg)

76
tests/v2/test_tags.py Normal file
View File

@ -0,0 +1,76 @@
# Copyright 2013 OpenStack Foundation
# 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.
import testtools
import warlock
from glanceclient.v2 import image_tags
from tests import utils
IMAGE = '3a4560a1-e585-443e-9b39-553b46ec92d1'
TAG = 'tag01'
fixtures = {
'/v2/images/{image}/tags/{tag_value}'.format(image=IMAGE, tag_value=TAG): {
'DELETE': (
{},
None,
),
'PUT': (
{},
{
'image_id': IMAGE,
'tag_value': TAG
}
),
},
}
fake_schema = {'name': 'image', 'properties': {'image_id': {}, 'tags': {}}}
FakeModel = warlock.model_factory(fake_schema)
class TestController(testtools.TestCase):
def setUp(self):
super(TestController, self).setUp()
self.api = utils.FakeAPI(fixtures)
self.controller = image_tags.Controller(self.api, FakeModel)
def test_update_image_tag(self):
image_id = IMAGE
tag_value = TAG
self.controller.update(image_id, tag_value)
expect = [
('PUT',
'/v2/images/{image}/tags/{tag_value}'.format(image=IMAGE,
tag_value=TAG),
{},
None)]
self.assertEqual(self.api.calls, expect)
def test_delete_image_tag(self):
image_id = IMAGE
tag_value = TAG
self.controller.delete(image_id, tag_value)
expect = [
('DELETE',
'/v2/images/{image}/tags/{tag_value}'.format(image=IMAGE,
tag_value=TAG),
{},
None)]
self.assertEqual(self.api.calls, expect)