Merge pull request #100 from throughnothing/image_metadata

Add support for adding/updating/deleting image metadata
This commit is contained in:
Josh Kearney 2011-09-01 13:04:17 -07:00
commit 230c4f4bea
7 changed files with 123 additions and 4 deletions

View File

@ -47,7 +47,7 @@ copyright = u'Rackspace, based on work by Jacob Kaplan-Moss'
# The short X.Y version.
version = '2.6'
# The full version, including alpha/beta/rc tags.
release = '2.6.3'
release = '2.6.4'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View File

@ -56,3 +56,24 @@ class ImageManager(base.ManagerWithFind):
:param image: The :class:`Image` (or its ID) to delete.
"""
self._delete("/images/%s" % base.getid(image))
def set_meta(self, image, metadata):
"""
Set an images metadata
:param image: The :class:`Image` to add metadata to
:param metadata: A dict of metadata to add to the image
"""
body = {'metadata': metadata}
return self._create("/images/%s/metadata" % base.getid(image), body,
"metadata")
def delete_meta(self, image, keys):
"""
Delete metadata from an image
:param image: The :class:`Image` to add metadata to
:param keys: A list of metadata keys to delete from the image
"""
for k in keys:
self._delete("/images/%s/metadata/%s" % (base.getid(image), k))

View File

@ -237,6 +237,52 @@ def do_image_list(cs, args):
"""Print a list of available images to boot from."""
utils.print_list(cs.images.list(), ['ID', 'Name', 'Status'])
@utils.arg('image',
metavar='<image>',
help="Name or ID of image")
@utils.arg('action',
metavar='<action>',
choices=['set', 'delete'],
help="Actions: 'set' or 'delete'")
@utils.arg('metadata',
metavar='<key=value>',
nargs='+',
action='append',
default=[],
help='Metadata to add/update or delete (only key is necessary on delete)')
def do_image_meta(cs, args):
"""Set or Delete metadata on an image."""
image = _find_image(cs, args.image)
metadata = {}
for metadatum in args.metadata[0]:
# Can only pass the key in on 'delete'
# So this doesn't have to have '='
if metadatum.find('=') > -1:
(key, value) = metadatum.split('=',1)
else:
key = metadatum
value = None
metadata[key] = value
if args.action == 'set':
cs.images.set_meta(image, metadata)
elif args.action == 'delete':
cs.images.delete_meta(image, metadata.keys())
def _print_image(image):
links = image.links
info = image._info.copy()
info.pop('links')
utils.print_dict(info)
@utils.arg('image',
metavar='<image>',
help="Name or ID of image")
def do_image_show(cs, args):
"""Show details about the given image."""
image = _find_image(cs, args.image)
_print_image(image)
@utils.arg('image', metavar='<image>', help='Name or ID of image.')
def do_image_delete(cs, args):

View File

@ -12,7 +12,7 @@ if sys.version_info < (2, 6):
setup(
name = "python-novaclient",
version = "2.6.3",
version = "2.6.4",
description = "Client library for OpenStack Nova API",
long_description = read('README.rst'),
url = 'https://github.com/rackspace/python-novaclient',

View File

@ -338,7 +338,11 @@ class FakeHTTPClient(base_client.HTTPClient):
'name': 'CentOS 5.2',
"updated": "2010-10-10T12:00:00Z",
"created": "2010-08-10T12:00:00Z",
"status": "ACTIVE"
"status": "ACTIVE",
"metadata": {
"test_key": "test_value",
},
"links": {},
},
{
"id": 743,
@ -347,7 +351,8 @@ class FakeHTTPClient(base_client.HTTPClient):
"updated": "2010-10-10T12:00:00Z",
"created": "2010-08-10T12:00:00Z",
"status": "SAVING",
"progress": 80
"progress": 80,
"links": {},
}
]})
@ -362,9 +367,19 @@ class FakeHTTPClient(base_client.HTTPClient):
fakes.assert_has_keys(body['image'], required=['serverId', 'name'])
return (202, self.get_images_1()[1])
def post_images_1_metadata(self, body, **kw):
assert body.keys() == ['metadata']
fakes.assert_has_keys(body['metadata'],
required=['test_key'])
return (200,
{'metadata': self.get_images_1()[1]['image']['metadata']})
def delete_images_1(self, **kw):
return (204, None)
def delete_images_1_metadata_test_key(self, **kw):
return (204, None)
#
# Zones
#

View File

@ -29,6 +29,15 @@ class ImagesTest(utils.TestCase):
cs.images.delete(1)
cs.assert_called('DELETE', '/images/1')
def test_delete_meta(self):
cs.images.delete_meta(1, {'test_key': 'test_value'})
cs.assert_called('DELETE', '/images/1/metadata/test_key')
def test_set_meta(self):
cs.images.set_meta(1, {'test_key': 'test_value'})
cs.assert_called('POST', '/images/1/metadata',
{"metadata": {'test_key': 'test_value'}})
def test_find(self):
i = cs.images.find(name="CentOS 5.2")
self.assertEqual(i.id, 1)

View File

@ -1,5 +1,7 @@
import os
import mock
import sys
import tempfile
from novaclient.shell import OpenStackComputeShell
from novaclient import exceptions
@ -157,6 +159,32 @@ class ShellTest(utils.TestCase):
self.run_command('flavor-list')
self.assert_called_anytime('GET', '/flavors/detail')
def test_image_show(self):
self.run_command('image-show 1')
self.assert_called('GET', '/images/1')
def test_image_meta_set(self):
self.run_command('image-meta 1 set test_key=test_value')
self.assert_called('POST', '/images/1/metadata',
{'metadata': {'test_key': 'test_value'}})
def test_image_meta_del(self):
self.run_command('image-meta 1 delete test_key=test_value')
self.assert_called('DELETE', '/images/1/metadata/test_key')
def test_image_meta_bad_action(self):
tmp = tempfile.TemporaryFile()
# Suppress stdout and stderr
(stdout, stderr) = (sys.stdout, sys.stderr)
(sys.stdout, sys.stderr) = (tmp, tmp)
self.assertRaises(SystemExit, self.run_command,
'image-meta 1 BAD_ACTION test_key=test_value')
# Put stdout and stderr back
sys.stdout, sys.stderr = (stdout, stderr)
def test_image_list(self):
self.run_command('image-list')
self.assert_called('GET', '/images/detail')