diff --git a/glanceclient/v2/images.py b/glanceclient/v2/images.py index 8f8072e0..d95ebca5 100644 --- a/glanceclient/v2/images.py +++ b/glanceclient/v2/images.py @@ -218,19 +218,47 @@ class Controller(object): return utils.IterableWithLength(body, content_length), resp @utils.add_req_id_to_object() - def upload(self, image_id, image_data, image_size=None): + def upload(self, image_id, image_data, image_size=None, u_url=None): """Upload the data for an image. :param image_id: ID of the image to upload data for. :param image_data: File-like object supplying the data to upload. :param image_size: Unused - present for backwards compatibility + :param u_url: Upload url to upload the data to. """ - url = '/v2/images/%s/file' % image_id + url = u_url or '/v2/images/%s/file' % image_id hdrs = {'Content-Type': 'application/octet-stream'} body = image_data resp, body = self.http_client.put(url, headers=hdrs, data=body) return (resp, body), resp + @utils.add_req_id_to_object() + def get_import_info(self): + """Get Import info from discovery endpoint.""" + url = '/v2/info/import' + resp, body = self.http_client.get(url) + return body, resp + + @utils.add_req_id_to_object() + def stage(self, image_id, image_data): + """Upload the data to image staging. + + :param image_id: ID of the image to upload data for. + :param image_data: File-like object supplying the data to upload. + """ + url = '/v2/images/%s/stage' % image_id + return self.upload(image_id, + image_data, + u_url=url) + + @utils.add_req_id_to_object() + def image_import(self, image_id, method='glance-direct'): + """Import Image via method.""" + url = '/v2/images/%s/import' % image_id + data = {'import_method': method} + resp, body = self.http_client.post(url, data=data) + return body, resp + @utils.add_req_id_to_object() def delete(self, image_id): """Delete an image.""" diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index 9ce6f96f..87285e49 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -89,6 +89,56 @@ def do_image_create(gc, args): utils.print_image(image) +@utils.schema_args(get_image_schema, omit=['created_at', 'updated_at', 'file', + 'checksum', 'virtual_size', 'size', + 'status', 'schema', 'direct_url', + 'locations', 'self']) +@utils.arg('--property', metavar="", action='append', + default=[], help=_('Arbitrary property to associate with image.' + ' May be used multiple times.')) +@utils.arg('--file', metavar='', + help=_('Local file that contains disk image to be uploaded ' + 'during creation. Alternatively, the image data can be ' + 'passed to the client via stdin.')) +@utils.arg('--progress', action='store_true', default=False, + help=_('Show upload progress bar.')) +@utils.on_data_require_fields(DATA_FIELDS) +def do_image_create_via_import(gc, args): + """EXPERIMENTAL: Create a new image via image import.""" + schema = gc.schemas.get("image") + _args = [(x[0].replace('-', '_'), x[1]) for x in vars(args).items()] + fields = dict(filter(lambda x: x[1] is not None and + (x[0] == 'property' or + schema.is_core_property(x[0])), + _args)) + + raw_properties = fields.pop('property', []) + for datum in raw_properties: + key, value = datum.split('=', 1) + fields[key] = value + + file_name = fields.pop('file', None) + if file_name is not None and os.access(file_name, os.R_OK) is False: + utils.exit("File %s does not exist or user does not have read " + "privileges to it" % file_name) + import_methods = gc.images.get_import_info().get('import-methods') + if file_name and (not import_methods or + 'glance-direct' not in import_methods.get('value')): + utils.exit("No suitable import method available for direct upload, " + "please use image-create instead.") + image, resp = gc.images.create(**fields) + try: + if utils.get_data_file(args) is not None: + args.id = image['id'] + args.size = None + do_image_stage(gc, args) + args.from_create = True + do_image_import(gc, args) + image = gc.images.get(args.id) + finally: + utils.print_image(image) + + @utils.arg('id', metavar='', help=_('ID of image to update.')) @utils.schema_args(get_image_schema, omit=['id', 'locations', 'created_at', 'updated_at', 'file', 'checksum', @@ -269,6 +319,16 @@ def do_explain(gc, args): utils.print_list(schema.properties, columns, formatters) +def do_import_info(gc, args): + """Print import methods available from Glance.""" + try: + import_info = gc.images.get_import_info() + except exc.HTTPNotFound: + utils.exit('Target Glance does not support Image Import workflow') + else: + utils.print_dict(import_info) + + @utils.arg('--file', metavar='', help=_('Local file to save downloaded image data to. ' 'If this is not specified and there is no redirection ' @@ -325,6 +385,49 @@ def do_image_upload(gc, args): gc.images.upload(args.id, image_data, args.size) +@utils.arg('--file', metavar='', + help=_('Local file that contains disk image to be uploaded.' + ' Alternatively, images can be passed' + ' to the client via stdin.')) +@utils.arg('--size', metavar='', type=int, + help=_('Size in bytes of image to be uploaded. Default is to get ' + 'size from provided data object but this is supported in ' + 'case where size cannot be inferred.'), + default=None) +@utils.arg('--progress', action='store_true', default=False, + help=_('Show upload progress bar.')) +@utils.arg('id', metavar='', + help=_('ID of image to upload data to.')) +def do_image_stage(gc, args): + """Upload data for a specific image to staging.""" + image_data = utils.get_data_file(args) + if args.progress: + filesize = utils.get_file_size(image_data) + if filesize is not None: + # NOTE(kragniz): do not show a progress bar if the size of the + # input is unknown (most likely a piped input) + image_data = progressbar.VerboseFileWrapper(image_data, filesize) + gc.images.stage(args.id, image_data, args.size) + + +@utils.arg('--import-method', metavar='', default='glance-direct', + help=_('Import method used for Image Import workflow. ' + 'Valid values can be retrieved with import-info command ' + 'and the default "glance-direct" is used with ' + '"image-stage".')) +@utils.arg('id', metavar='', + help=_('ID of image to import.')) +def do_image_import(gc, args): + try: + gc.images.image_import(args.id, args.import_method) + except exc.HTTPNotFound: + utils.exit('Target Glance does not support Image Import workflow') + else: + if not getattr(args, 'from_create', False): + image = gc.images.get(args.id) + utils.print_image(image) + + @utils.arg('id', metavar='', nargs='+', help=_('ID of image(s) to delete.')) def do_image_delete(gc, args):