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:
parent
62579fbb21
commit
b9c1df8dfc
@ -18,6 +18,7 @@ import warlock
|
|||||||
from glanceclient.common import http
|
from glanceclient.common import http
|
||||||
from glanceclient.v2 import images
|
from glanceclient.v2 import images
|
||||||
from glanceclient.v2 import image_members
|
from glanceclient.v2 import image_members
|
||||||
|
from glanceclient.v2 import image_tags
|
||||||
from glanceclient.v2 import schemas
|
from glanceclient.v2 import schemas
|
||||||
|
|
||||||
|
|
||||||
@ -34,8 +35,10 @@ class Client(object):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.http_client = http.HTTPClient(*args, **kwargs)
|
self.http_client = http.HTTPClient(*args, **kwargs)
|
||||||
self.schemas = schemas.Controller(self.http_client)
|
self.schemas = schemas.Controller(self.http_client)
|
||||||
|
image_model = self._get_image_model()
|
||||||
self.images = images.Controller(self.http_client,
|
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.image_members = image_members.Controller(self.http_client,
|
||||||
self._get_member_model())
|
self._get_member_model())
|
||||||
|
|
||||||
|
40
glanceclient/v2/image_tags.py
Normal file
40
glanceclient/v2/image_tags.py
Normal 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)
|
@ -133,3 +133,31 @@ def do_image_download(gc, args):
|
|||||||
def do_image_delete(gc, args):
|
def do_image_delete(gc, args):
|
||||||
"""Delete specified image."""
|
"""Delete specified image."""
|
||||||
gc.images.delete(args.id)
|
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)
|
||||||
|
@ -24,6 +24,12 @@ from glanceclient.v2 import shell as test_shell
|
|||||||
|
|
||||||
|
|
||||||
class LegacyShellV1Test(testtools.TestCase):
|
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):
|
def test_do_image_list(self):
|
||||||
gc = client.Client('1', 'http://no.where')
|
gc = client.Client('1', 'http://no.where')
|
||||||
|
|
||||||
@ -51,9 +57,7 @@ class LegacyShellV1Test(testtools.TestCase):
|
|||||||
actual = test_shell.do_image_show(gc, Fake())
|
actual = test_shell.do_image_show(gc, Fake())
|
||||||
|
|
||||||
def test_do_explain(self):
|
def test_do_explain(self):
|
||||||
my_mocked_gc = mock.Mock()
|
my_mocked_gc = self._mock_glance_client()
|
||||||
my_mocked_gc.schemas.return_value = 'test'
|
|
||||||
my_mocked_gc.get.return_value = {}
|
|
||||||
|
|
||||||
class Fake():
|
class Fake():
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -84,3 +88,54 @@ class LegacyShellV1Test(testtools.TestCase):
|
|||||||
with mock.patch.object(gc.images, 'delete') as mocked_delete:
|
with mock.patch.object(gc.images, 'delete') as mocked_delete:
|
||||||
mocked_delete.return_value = 0
|
mocked_delete.return_value = 0
|
||||||
test_shell.do_image_delete(gc, Fake())
|
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
76
tests/v2/test_tags.py
Normal 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)
|
Loading…
x
Reference in New Issue
Block a user