Adding support for passing image data through cli
This commit is contained in:
@@ -71,9 +71,9 @@ class HTTPClient(httplib2.Http):
|
||||
_kwargs = copy.copy(kwargs)
|
||||
_kwargs.setdefault('headers', kwargs.get('headers', {}))
|
||||
_kwargs['headers']['User-Agent'] = USER_AGENT
|
||||
if 'body' in kwargs:
|
||||
_kwargs['headers']['Content-Type'] = 'application/json'
|
||||
_kwargs['body'] = json.dumps(kwargs['body'])
|
||||
if 'body' in kwargs and kwargs['body'] is not None:
|
||||
_kwargs['headers']['Content-Type'] = 'application/octet-stream'
|
||||
_kwargs['body'] = kwargs['body']
|
||||
|
||||
resp, body = super(HTTPClient, self).request(url, method, **_kwargs)
|
||||
self.http_log((url, method,), _kwargs, resp, body)
|
||||
|
@@ -14,6 +14,8 @@
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
import errno
|
||||
import os
|
||||
import urllib
|
||||
|
||||
from glanceclient.common import base
|
||||
@@ -88,12 +90,42 @@ class ImageManager(base.Manager):
|
||||
"""Delete an image."""
|
||||
self._delete("/v1/images/%s" % base.getid(image))
|
||||
|
||||
def _get_file_size(self, obj):
|
||||
"""Analyze file-like object and attempt to determine its size.
|
||||
|
||||
:param obj: file-like object, typically redirected from stdin.
|
||||
:retval The file's size or None if it cannot be determined.
|
||||
"""
|
||||
# For large images, we need to supply the size of the
|
||||
# image file. See LP Bugs #827660 and #845788.
|
||||
if hasattr(obj, 'seek') and hasattr(obj, 'tell'):
|
||||
try:
|
||||
obj.seek(0, os.SEEK_END)
|
||||
obj_size = obj.tell()
|
||||
obj.seek(0)
|
||||
return obj_size
|
||||
except IOError, e:
|
||||
if e.errno == errno.ESPIPE:
|
||||
# Illegal seek. This means the user is trying
|
||||
# to pipe image data to the client, e.g.
|
||||
# echo testdata | bin/glance add blah..., or
|
||||
# that stdin is empty
|
||||
return None
|
||||
else:
|
||||
raise
|
||||
|
||||
def create(self, **kwargs):
|
||||
"""Create an image
|
||||
|
||||
TODO(bcwaldon): document accepted params
|
||||
"""
|
||||
image_data = kwargs.pop('data', None)
|
||||
if image_data is not None:
|
||||
image_size = self._get_file_size(image_data)
|
||||
if image_size != 0:
|
||||
kwargs.setdefault('size', image_size)
|
||||
else:
|
||||
image_data = None
|
||||
|
||||
fields = {}
|
||||
for field in kwargs:
|
||||
@@ -112,7 +144,18 @@ class ImageManager(base.Manager):
|
||||
return Image(self, body['image'])
|
||||
|
||||
def update(self, image, **kwargs):
|
||||
"""Update an image"""
|
||||
"""Update an image
|
||||
|
||||
TODO(bcwaldon): document accepted params
|
||||
"""
|
||||
image_data = kwargs.pop('data', None)
|
||||
if image_data is not None:
|
||||
image_size = self._get_file_size(image_data)
|
||||
if image_size != 0:
|
||||
kwargs.setdefault('size', image_size)
|
||||
else:
|
||||
image_data = None
|
||||
|
||||
fields = {}
|
||||
for field in kwargs:
|
||||
if field in UPDATE_PARAMS:
|
||||
@@ -127,5 +170,6 @@ class ImageManager(base.Manager):
|
||||
hdrs['x-glance-api-copy-from'] = copy_from
|
||||
|
||||
image_id = base.getid(image)
|
||||
resp, body = self.api.put('/v1/images/%s' % image_id, headers=hdrs)
|
||||
resp, body = self.api.put('/v1/images/%s' % image_id, headers=hdrs,
|
||||
body=image_data)
|
||||
return Image(self, body['image'])
|
||||
|
@@ -14,6 +14,7 @@
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
import sys
|
||||
|
||||
from glanceclient.common import utils
|
||||
import glanceclient.v1.images
|
||||
@@ -53,7 +54,8 @@ def do_image_show(gc, args):
|
||||
@utils.arg('--owner', metavar='<TENANT_ID>',
|
||||
help='Tenant who should own image.')
|
||||
@utils.arg('--size', metavar='<SIZE>',
|
||||
help='Size of image data (in bytes).')
|
||||
help=('Size of image data (in bytes). Only used with'
|
||||
' \'--location\' and \'--copy_from\'.'))
|
||||
@utils.arg('--min_disk', metavar='<DISK_GB>',
|
||||
help='Minimum size of disk needed to boot image (in gigabytes).')
|
||||
@utils.arg('--min_ram', metavar='<DISK_RAM>',
|
||||
@@ -92,10 +94,14 @@ def do_image_create(gc, args):
|
||||
CREATE_PARAMS = glanceclient.v1.images.CREATE_PARAMS
|
||||
fields = dict(filter(lambda x: x[0] in CREATE_PARAMS, fields.items()))
|
||||
|
||||
if 'location' not in fields and 'copy_from' not in fields:
|
||||
fields['data'] = sys.stdin
|
||||
|
||||
image = gc.images.create(**fields)
|
||||
_image_show(image)
|
||||
|
||||
|
||||
@utils.arg('id', metavar='<IMAGE_ID>', help='ID of image to modify.')
|
||||
@utils.arg('--name', metavar='<NAME>',
|
||||
help='Name of image.')
|
||||
@utils.arg('--disk_format', metavar='<CONTAINER_FORMAT>',
|
||||
@@ -132,6 +138,8 @@ def do_image_update(gc, args):
|
||||
# Filter out None values
|
||||
fields = dict(filter(lambda x: x[1] is not None, vars(args).items()))
|
||||
|
||||
image_id = fields.pop('id')
|
||||
|
||||
raw_properties = fields.pop('property')
|
||||
fields['properties'] = {}
|
||||
for datum in raw_properties:
|
||||
@@ -142,7 +150,10 @@ def do_image_update(gc, args):
|
||||
UPDATE_PARAMS = glanceclient.v1.images.UPDATE_PARAMS
|
||||
fields = dict(filter(lambda x: x[0] in UPDATE_PARAMS, fields.items()))
|
||||
|
||||
image = gc.images.create(**fields)
|
||||
if 'location' not in fields and 'copy_from' not in fields:
|
||||
fields['data'] = sys.stdin
|
||||
|
||||
image = gc.images.update(image_id, **fields)
|
||||
_image_show(image)
|
||||
|
||||
|
||||
|
@@ -86,7 +86,8 @@ class ImageManagerTest(unittest.TestCase):
|
||||
def test_create_with_data(self):
|
||||
image_data = StringIO.StringIO('XXX')
|
||||
self.mgr.create(data=image_data)
|
||||
expect = [('POST', '/v1/images', {}, image_data)]
|
||||
expect_headers = {'x-image-meta-size': '3'}
|
||||
expect = [('POST', '/v1/images', expect_headers, image_data)]
|
||||
self.assertEqual(self.api.calls, expect)
|
||||
|
||||
def test_update(self):
|
||||
@@ -119,6 +120,13 @@ class ImageManagerTest(unittest.TestCase):
|
||||
self.assertEqual(image.id, '1')
|
||||
self.assertEqual(image.name, 'image-2')
|
||||
|
||||
def test_update_with_data(self):
|
||||
image_data = StringIO.StringIO('XXX')
|
||||
self.mgr.update('1', data=image_data)
|
||||
expect_headers = {'x-image-meta-size': '3'}
|
||||
expect = [('PUT', '/v1/images/1', expect_headers, image_data)]
|
||||
self.assertEqual(self.api.calls, expect)
|
||||
|
||||
|
||||
class ImageTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
|
Reference in New Issue
Block a user