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 = copy.copy(kwargs)
|
||||||
_kwargs.setdefault('headers', kwargs.get('headers', {}))
|
_kwargs.setdefault('headers', kwargs.get('headers', {}))
|
||||||
_kwargs['headers']['User-Agent'] = USER_AGENT
|
_kwargs['headers']['User-Agent'] = USER_AGENT
|
||||||
if 'body' in kwargs:
|
if 'body' in kwargs and kwargs['body'] is not None:
|
||||||
_kwargs['headers']['Content-Type'] = 'application/json'
|
_kwargs['headers']['Content-Type'] = 'application/octet-stream'
|
||||||
_kwargs['body'] = json.dumps(kwargs['body'])
|
_kwargs['body'] = kwargs['body']
|
||||||
|
|
||||||
resp, body = super(HTTPClient, self).request(url, method, **_kwargs)
|
resp, body = super(HTTPClient, self).request(url, method, **_kwargs)
|
||||||
self.http_log((url, method,), _kwargs, resp, body)
|
self.http_log((url, method,), _kwargs, resp, body)
|
||||||
|
@@ -14,6 +14,8 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import errno
|
||||||
|
import os
|
||||||
import urllib
|
import urllib
|
||||||
|
|
||||||
from glanceclient.common import base
|
from glanceclient.common import base
|
||||||
@@ -88,12 +90,42 @@ class ImageManager(base.Manager):
|
|||||||
"""Delete an image."""
|
"""Delete an image."""
|
||||||
self._delete("/v1/images/%s" % base.getid(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):
|
def create(self, **kwargs):
|
||||||
"""Create an image
|
"""Create an image
|
||||||
|
|
||||||
TODO(bcwaldon): document accepted params
|
TODO(bcwaldon): document accepted params
|
||||||
"""
|
"""
|
||||||
image_data = kwargs.pop('data', None)
|
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 = {}
|
fields = {}
|
||||||
for field in kwargs:
|
for field in kwargs:
|
||||||
@@ -112,7 +144,18 @@ class ImageManager(base.Manager):
|
|||||||
return Image(self, body['image'])
|
return Image(self, body['image'])
|
||||||
|
|
||||||
def update(self, image, **kwargs):
|
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 = {}
|
fields = {}
|
||||||
for field in kwargs:
|
for field in kwargs:
|
||||||
if field in UPDATE_PARAMS:
|
if field in UPDATE_PARAMS:
|
||||||
@@ -127,5 +170,6 @@ class ImageManager(base.Manager):
|
|||||||
hdrs['x-glance-api-copy-from'] = copy_from
|
hdrs['x-glance-api-copy-from'] = copy_from
|
||||||
|
|
||||||
image_id = base.getid(image)
|
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'])
|
return Image(self, body['image'])
|
||||||
|
@@ -14,6 +14,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import sys
|
||||||
|
|
||||||
from glanceclient.common import utils
|
from glanceclient.common import utils
|
||||||
import glanceclient.v1.images
|
import glanceclient.v1.images
|
||||||
@@ -53,7 +54,8 @@ def do_image_show(gc, args):
|
|||||||
@utils.arg('--owner', metavar='<TENANT_ID>',
|
@utils.arg('--owner', metavar='<TENANT_ID>',
|
||||||
help='Tenant who should own image.')
|
help='Tenant who should own image.')
|
||||||
@utils.arg('--size', metavar='<SIZE>',
|
@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>',
|
@utils.arg('--min_disk', metavar='<DISK_GB>',
|
||||||
help='Minimum size of disk needed to boot image (in gigabytes).')
|
help='Minimum size of disk needed to boot image (in gigabytes).')
|
||||||
@utils.arg('--min_ram', metavar='<DISK_RAM>',
|
@utils.arg('--min_ram', metavar='<DISK_RAM>',
|
||||||
@@ -92,10 +94,14 @@ def do_image_create(gc, args):
|
|||||||
CREATE_PARAMS = glanceclient.v1.images.CREATE_PARAMS
|
CREATE_PARAMS = glanceclient.v1.images.CREATE_PARAMS
|
||||||
fields = dict(filter(lambda x: x[0] in CREATE_PARAMS, fields.items()))
|
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 = gc.images.create(**fields)
|
||||||
_image_show(image)
|
_image_show(image)
|
||||||
|
|
||||||
|
|
||||||
|
@utils.arg('id', metavar='<IMAGE_ID>', help='ID of image to modify.')
|
||||||
@utils.arg('--name', metavar='<NAME>',
|
@utils.arg('--name', metavar='<NAME>',
|
||||||
help='Name of image.')
|
help='Name of image.')
|
||||||
@utils.arg('--disk_format', metavar='<CONTAINER_FORMAT>',
|
@utils.arg('--disk_format', metavar='<CONTAINER_FORMAT>',
|
||||||
@@ -132,6 +138,8 @@ def do_image_update(gc, args):
|
|||||||
# Filter out None values
|
# Filter out None values
|
||||||
fields = dict(filter(lambda x: x[1] is not None, vars(args).items()))
|
fields = dict(filter(lambda x: x[1] is not None, vars(args).items()))
|
||||||
|
|
||||||
|
image_id = fields.pop('id')
|
||||||
|
|
||||||
raw_properties = fields.pop('property')
|
raw_properties = fields.pop('property')
|
||||||
fields['properties'] = {}
|
fields['properties'] = {}
|
||||||
for datum in raw_properties:
|
for datum in raw_properties:
|
||||||
@@ -142,7 +150,10 @@ def do_image_update(gc, args):
|
|||||||
UPDATE_PARAMS = glanceclient.v1.images.UPDATE_PARAMS
|
UPDATE_PARAMS = glanceclient.v1.images.UPDATE_PARAMS
|
||||||
fields = dict(filter(lambda x: x[0] in UPDATE_PARAMS, fields.items()))
|
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)
|
_image_show(image)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -86,7 +86,8 @@ class ImageManagerTest(unittest.TestCase):
|
|||||||
def test_create_with_data(self):
|
def test_create_with_data(self):
|
||||||
image_data = StringIO.StringIO('XXX')
|
image_data = StringIO.StringIO('XXX')
|
||||||
self.mgr.create(data=image_data)
|
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)
|
self.assertEqual(self.api.calls, expect)
|
||||||
|
|
||||||
def test_update(self):
|
def test_update(self):
|
||||||
@@ -119,6 +120,13 @@ class ImageManagerTest(unittest.TestCase):
|
|||||||
self.assertEqual(image.id, '1')
|
self.assertEqual(image.id, '1')
|
||||||
self.assertEqual(image.name, 'image-2')
|
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):
|
class ImageTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
Reference in New Issue
Block a user