509 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			509 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # Copyright 2012 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.
 | |
| 
 | |
| """
 | |
| DEPRECATED functions that implement the same command line interface as the
 | |
| legacy glance client.
 | |
| """
 | |
| 
 | |
| import argparse
 | |
| import sys
 | |
| import urlparse
 | |
| 
 | |
| from glanceclient.common import utils
 | |
| 
 | |
| 
 | |
| SUCCESS = 0
 | |
| FAILURE = 1
 | |
| 
 | |
| 
 | |
| def get_image_fields_from_args(args):
 | |
|     """
 | |
|     Validate the set of arguments passed as field name/value pairs
 | |
|     and return them as a mapping.
 | |
|     """
 | |
|     fields = {}
 | |
|     for arg in args:
 | |
|         pieces = arg.strip(',').split('=')
 | |
|         if len(pieces) != 2:
 | |
|             msg = ("Arguments should be in the form of field=value. "
 | |
|                    "You specified %s." % arg)
 | |
|             raise RuntimeError(msg)
 | |
|         fields[pieces[0]] = pieces[1]
 | |
| 
 | |
|     return fields
 | |
| 
 | |
| 
 | |
| def get_image_filters_from_args(args):
 | |
|     """Build a dictionary of query filters based on the supplied args."""
 | |
|     try:
 | |
|         fields = get_image_fields_from_args(args)
 | |
|     except RuntimeError as e:
 | |
|         print e
 | |
|         return FAILURE
 | |
| 
 | |
|     SUPPORTED_FILTERS = ['name', 'disk_format', 'container_format', 'status',
 | |
|                          'min_ram', 'min_disk', 'size_min', 'size_max',
 | |
|                          'changes-since']
 | |
|     filters = {}
 | |
|     for (key, value) in fields.items():
 | |
|         if key not in SUPPORTED_FILTERS:
 | |
|             key = 'property-%s' % (key,)
 | |
|         filters[key] = value
 | |
| 
 | |
|     return filters
 | |
| 
 | |
| 
 | |
| def print_image_formatted(client, image):
 | |
|     """
 | |
|     Formatted print of image metadata.
 | |
| 
 | |
|     :param client: The Glance client object
 | |
|     :param image: The image metadata
 | |
|     """
 | |
|     uri_parts = urlparse.urlparse(client.endpoint)
 | |
|     if uri_parts.port:
 | |
|         hostbase = "%s:%s" % (uri_parts.hostname, uri_parts.port)
 | |
|     else:
 | |
|         hostbase = uri_parts.hostname
 | |
|     print "URI: %s://%s/v1/images/%s" % (uri_parts.scheme, hostbase, image.id)
 | |
|     print "Id: %s" % image.id
 | |
|     print "Public: " + (image.is_public and "Yes" or "No")
 | |
|     print "Protected: " + (image.protected and "Yes" or "No")
 | |
|     print "Name: %s" % getattr(image, 'name', '')
 | |
|     print "Status: %s" % image.status
 | |
|     print "Size: %d" % int(image.size)
 | |
|     print "Disk format: %s" % getattr(image, 'disk_format', '')
 | |
|     print "Container format: %s" % getattr(image, 'container_format', '')
 | |
|     print "Minimum Ram Required (MB): %s" % image.min_ram
 | |
|     print "Minimum Disk Required (GB): %s" % image.min_disk
 | |
|     if hasattr(image, 'owner'):
 | |
|         print "Owner: %s" % image.owner
 | |
|     if len(image.properties) > 0:
 | |
|         for k, v in image.properties.items():
 | |
|             print "Property '%s': %s" % (k, v)
 | |
|     print "Created at: %s" % image.created_at
 | |
|     if hasattr(image, 'deleted_at'):
 | |
|         print "Deleted at: %s" % image.deleted_at
 | |
|     if hasattr(image, 'updated_at'):
 | |
|         print "Updated at: %s" % image.updated_at
 | |
| 
 | |
| 
 | |
| @utils.arg('--silent-upload', action="store_true",
 | |
|            help="DEPRECATED! Animations are always off.")
 | |
| @utils.arg('fields', default=[], nargs='*', help=argparse.SUPPRESS)
 | |
| def do_add(gc, args):
 | |
|     """DEPRECATED! Use image-create instead."""
 | |
|     try:
 | |
|         fields = get_image_fields_from_args(args.fields)
 | |
|     except RuntimeError as e:
 | |
|         print e
 | |
|         return FAILURE
 | |
| 
 | |
|     image_meta = {
 | |
|         'is_public': utils.string_to_bool(
 | |
|             fields.pop('is_public', 'False')),
 | |
|         'protected': utils.string_to_bool(
 | |
|             fields.pop('protected', 'False')),
 | |
|         'min_disk': fields.pop('min_disk', 0),
 | |
|         'min_ram': fields.pop('min_ram', 0),
 | |
|     }
 | |
| 
 | |
|     #NOTE(bcwaldon): Use certain properties only if they are explicitly set
 | |
|     optional = ['id', 'name', 'disk_format', 'container_format']
 | |
|     for field in optional:
 | |
|         if field in fields:
 | |
|             image_meta[field] = fields.pop(field)
 | |
| 
 | |
|     # Strip any args that are not supported
 | |
|     unsupported_fields = ['status', 'size']
 | |
|     for field in unsupported_fields:
 | |
|         if field in fields.keys():
 | |
|             print 'Found non-settable field %s. Removing.' % field
 | |
|             fields.pop(field)
 | |
| 
 | |
|     # We need either a location or image data/stream to add...
 | |
|     image_data = None
 | |
|     if 'location' in fields.keys():
 | |
|         image_meta['location'] = fields.pop('location')
 | |
|         if 'checksum' in fields.keys():
 | |
|             image_meta['checksum'] = fields.pop('checksum')
 | |
|     elif 'copy_from' in fields.keys():
 | |
|         image_meta['copy_from'] = fields.pop('copy_from')
 | |
|     else:
 | |
|         # Grab the image data stream from stdin or redirect,
 | |
|         # otherwise error out
 | |
|         image_data = sys.stdin
 | |
| 
 | |
|     image_meta['data'] = image_data
 | |
| 
 | |
|     # allow owner to be set when image is created
 | |
|     if 'owner' in fields.keys():
 | |
|         image_meta['owner'] = fields.pop('owner')
 | |
| 
 | |
|     # Add custom attributes, which are all the arguments remaining
 | |
|     image_meta['properties'] = fields
 | |
| 
 | |
|     if not args.dry_run:
 | |
|         image = gc.images.create(**image_meta)
 | |
|         print "Added new image with ID: %s" % image.id
 | |
|         if args.verbose:
 | |
|             print "Returned the following metadata for the new image:"
 | |
|             for k, v in sorted(image.to_dict().items()):
 | |
|                 print " %(k)30s => %(v)s" % locals()
 | |
|     else:
 | |
|         print "Dry run. We would have done the following:"
 | |
| 
 | |
|         def _dump(dict):
 | |
|             for k, v in sorted(dict.items()):
 | |
|                 print " %(k)30s => %(v)s" % locals()
 | |
| 
 | |
|         print "Add new image with metadata:"
 | |
|         _dump(image_meta)
 | |
| 
 | |
|     return SUCCESS
 | |
| 
 | |
| 
 | |
| @utils.arg('id', metavar='<IMAGE_ID>', help='ID of image to describe.')
 | |
| @utils.arg('fields', default=[], nargs='*', help=argparse.SUPPRESS)
 | |
| def do_update(gc, args):
 | |
|     """DEPRECATED! Use image-update instead."""
 | |
|     try:
 | |
|         fields = get_image_fields_from_args(args.fields)
 | |
|     except RuntimeError as e:
 | |
|         print e
 | |
|         return FAILURE
 | |
| 
 | |
|     image_meta = {}
 | |
| 
 | |
|     # Strip any args that are not supported
 | |
|     nonmodifiable_fields = ['created_at', 'deleted_at', 'deleted',
 | |
|                             'updated_at', 'size', 'status']
 | |
|     for field in nonmodifiable_fields:
 | |
|         if field in fields.keys():
 | |
|             print 'Found non-modifiable field %s. Removing.' % field
 | |
|             fields.pop(field)
 | |
| 
 | |
|     base_image_fields = ['disk_format', 'container_format', 'name',
 | |
|                          'min_disk', 'min_ram', 'location', 'owner',
 | |
|                          'copy_from']
 | |
|     for field in base_image_fields:
 | |
|         fvalue = fields.pop(field, None)
 | |
|         if fvalue is not None:
 | |
|             image_meta[field] = fvalue
 | |
| 
 | |
|     # Have to handle "boolean" values specially...
 | |
|     if 'is_public' in fields:
 | |
|         image_meta['is_public'] = utils.string_to_bool(fields.pop('is_public'))
 | |
|     if 'protected' in fields:
 | |
|         image_meta['protected'] = utils.string_to_bool(fields.pop('protected'))
 | |
| 
 | |
|     # Add custom attributes, which are all the arguments remaining
 | |
|     image_meta['properties'] = fields
 | |
| 
 | |
|     if not args.dry_run:
 | |
|         image = gc.images.update(args.id, **image_meta)
 | |
|         print "Updated image %s" % args.id
 | |
| 
 | |
|         if args.verbose:
 | |
|             print "Updated image metadata for image %s:" % args.id
 | |
|             print_image_formatted(gc, image)
 | |
|     else:
 | |
|         def _dump(dict):
 | |
|             for k, v in sorted(dict.items()):
 | |
|                 print " %(k)30s => %(v)s" % locals()
 | |
| 
 | |
|         print "Dry run. We would have done the following:"
 | |
|         print "Update existing image with metadata:"
 | |
|         _dump(image_meta)
 | |
| 
 | |
|     return SUCCESS
 | |
| 
 | |
| 
 | |
| @utils.arg('id', metavar='<IMAGE_ID>', help='ID of image to describe.')
 | |
| def do_delete(gc, args):
 | |
|     """DEPRECATED! Use image-delete instead."""
 | |
|     if not (args.force or
 | |
|             user_confirm("Delete image %s?" % args.id, default=False)):
 | |
|         print 'Not deleting image %s' % args.id
 | |
|         return FAILURE
 | |
| 
 | |
|     gc.images.get(args.id).delete()
 | |
| 
 | |
| 
 | |
| @utils.arg('id', metavar='<IMAGE_ID>', help='ID of image to describe.')
 | |
| def do_show(gc, args):
 | |
|     """DEPRECATED! Use image-show instead."""
 | |
|     image = gc.images.get(args.id)
 | |
|     print_image_formatted(gc, image)
 | |
|     return SUCCESS
 | |
| 
 | |
| 
 | |
| def _get_images(gc, args):
 | |
|     parameters = {
 | |
|         'filters': get_image_filters_from_args(args.filters),
 | |
|         'page_size': args.limit,
 | |
|     }
 | |
| 
 | |
|     optional_kwargs = ['marker', 'sort_key', 'sort_dir']
 | |
|     for kwarg in optional_kwargs:
 | |
|         value = getattr(args, kwarg, None)
 | |
|         if value is not None:
 | |
|             parameters[kwarg] = value
 | |
| 
 | |
|     return gc.images.list(**parameters)
 | |
| 
 | |
| 
 | |
| @utils.arg('--limit', dest="limit", metavar="LIMIT", default=10,
 | |
|            type=int, help="Page size to use while requesting image metadata")
 | |
| @utils.arg('--marker', dest="marker", metavar="MARKER",
 | |
|            default=None, help="Image index after which to begin pagination")
 | |
| @utils.arg('--sort_key', dest="sort_key", metavar="KEY",
 | |
|            help="Sort results by this image attribute.")
 | |
| @utils.arg('--sort_dir', dest="sort_dir", metavar="[desc|asc]",
 | |
|            help="Sort results in this direction.")
 | |
| @utils.arg('filters', default=[], nargs='*', help=argparse.SUPPRESS)
 | |
| def do_index(gc, args):
 | |
|     """DEPRECATED! Use image-list instead."""
 | |
|     images = _get_images(gc, args)
 | |
| 
 | |
|     if not images:
 | |
|         return SUCCESS
 | |
| 
 | |
|     pretty_table = PrettyTable()
 | |
|     pretty_table.add_column(36, label="ID")
 | |
|     pretty_table.add_column(30, label="Name")
 | |
|     pretty_table.add_column(20, label="Disk Format")
 | |
|     pretty_table.add_column(20, label="Container Format")
 | |
|     pretty_table.add_column(14, label="Size", just="r")
 | |
| 
 | |
|     print pretty_table.make_header()
 | |
| 
 | |
|     for image in images:
 | |
|         print pretty_table.make_row(image.id,
 | |
|                                     image.name,
 | |
|                                     image.disk_format,
 | |
|                                     image.container_format,
 | |
|                                     image.size)
 | |
| 
 | |
| 
 | |
| @utils.arg('--limit', dest="limit", metavar="LIMIT", default=10,
 | |
|            type=int, help="Page size to use while requesting image metadata")
 | |
| @utils.arg('--marker', dest="marker", metavar="MARKER",
 | |
|            default=None, help="Image index after which to begin pagination")
 | |
| @utils.arg('--sort_key', dest="sort_key", metavar="KEY",
 | |
|            help="Sort results by this image attribute.")
 | |
| @utils.arg('--sort_dir', dest="sort_dir", metavar="[desc|asc]",
 | |
|            help="Sort results in this direction.")
 | |
| @utils.arg('filters', default='', nargs='*', help=argparse.SUPPRESS)
 | |
| def do_details(gc, args):
 | |
|     """DEPRECATED! Use image-list instead."""
 | |
|     images = _get_images(gc, args)
 | |
|     for i, image in enumerate(images):
 | |
|         if i == 0:
 | |
|             print "=" * 80
 | |
|         print_image_formatted(gc, image)
 | |
|         print "=" * 80
 | |
| 
 | |
| 
 | |
| def do_clear(gc, args):
 | |
|     """DEPRECATED!"""
 | |
|     if not (args.force or
 | |
|             user_confirm("Delete all images?", default=False)):
 | |
|         print 'Not deleting any images'
 | |
|         return FAILURE
 | |
| 
 | |
|     images = gc.images.list()
 | |
|     for image in images:
 | |
|         if args.verbose:
 | |
|             print 'Deleting image %s "%s" ...' % (image.id, image.name),
 | |
|         try:
 | |
|             image.delete()
 | |
|             if args.verbose:
 | |
|                 print 'done'
 | |
|         except Exception as e:
 | |
|             print 'Failed to delete image %s' % image.id
 | |
|             print e
 | |
|             return FAILURE
 | |
|     return SUCCESS
 | |
| 
 | |
| 
 | |
| @utils.arg('image_id', help='Image ID to filters members with.')
 | |
| def do_image_members(gc, args):
 | |
|     """DEPRECATED! Use member-list instead."""
 | |
|     members = gc.image_members.list(image=args.image_id)
 | |
|     sharers = 0
 | |
|     # Output the list of members
 | |
|     for memb in members:
 | |
|         can_share = ''
 | |
|         if memb.can_share:
 | |
|             can_share = ' *'
 | |
|             sharers += 1
 | |
|         print "%s%s" % (memb.member_id, can_share)
 | |
| 
 | |
|     # Emit a footnote
 | |
|     if sharers > 0:
 | |
|         print "\n(*: Can share image)"
 | |
| 
 | |
| 
 | |
| @utils.arg('--can-share', default=False, action="store_true",
 | |
|            help="Allow member to further share image.")
 | |
| @utils.arg('member_id',
 | |
|            help='ID of member (typically tenant) to grant access.')
 | |
| def do_member_images(gc, args):
 | |
|     """DEPRECATED! Use member-list instead."""
 | |
|     members = gc.image_members.list(member=args.member_id)
 | |
| 
 | |
|     if not len(members):
 | |
|         print "No images shared with member %s" % args.member_id
 | |
|         return SUCCESS
 | |
| 
 | |
|     sharers = 0
 | |
|     # Output the list of images
 | |
|     for memb in members:
 | |
|         can_share = ''
 | |
|         if memb.can_share:
 | |
|             can_share = ' *'
 | |
|             sharers += 1
 | |
|         print "%s%s" % (memb.image_id, can_share)
 | |
| 
 | |
|     # Emit a footnote
 | |
|     if sharers > 0:
 | |
|         print "\n(*: Can share image)"
 | |
| 
 | |
| 
 | |
| @utils.arg('--can-share', default=False, action="store_true",
 | |
|            help="Allow member to further share image.")
 | |
| @utils.arg('image_id', help='ID of image to describe.')
 | |
| @utils.arg('member_id',
 | |
|            help='ID of member (typically tenant) to grant access.')
 | |
| def do_members_replace(gc, args):
 | |
|     """DEPRECATED!"""
 | |
|     if not args.dry_run:
 | |
|         for member in gc.image_members.list(image=args.image_id):
 | |
|             gc.image_members.delete(args.image_id, member.member_id)
 | |
|         gc.image_members.create(args.image_id, args.member_id, args.can_share)
 | |
|     else:
 | |
|         print "Dry run. We would have done the following:"
 | |
|         print ('Replace members of image %s with "%s"'
 | |
|                % (args.image_id, args.member_id))
 | |
|         if args.can_share:
 | |
|             print "New member would have been able to further share image."
 | |
| 
 | |
| 
 | |
| @utils.arg('--can-share', default=False, action="store_true",
 | |
|            help="Allow member to further share image.")
 | |
| @utils.arg('image_id', help='ID of image to describe.')
 | |
| @utils.arg('member_id',
 | |
|            help='ID of member (typically tenant) to grant access.')
 | |
| def do_member_add(gc, args):
 | |
|     """DEPRECATED! Use member-create instead."""
 | |
|     if not args.dry_run:
 | |
|         gc.image_members.create(args.image_id, args.member_id, args.can_share)
 | |
|     else:
 | |
|         print "Dry run. We would have done the following:"
 | |
|         print ('Add "%s" to membership of image %s' %
 | |
|                (args.member_id, args.image_id))
 | |
|         if args.can_share:
 | |
|             print "New member would have been able to further share image."
 | |
| 
 | |
| 
 | |
| def user_confirm(prompt, default=False):
 | |
|     """
 | |
|     Yes/No question dialog with user.
 | |
| 
 | |
|     :param prompt: question/statement to present to user (string)
 | |
|     :param default: boolean value to return if empty string
 | |
|                     is received as response to prompt
 | |
| 
 | |
|     """
 | |
|     if default:
 | |
|         prompt_default = "[Y/n]"
 | |
|     else:
 | |
|         prompt_default = "[y/N]"
 | |
| 
 | |
|     # for bug 884116, don't issue the prompt if stdin isn't a tty
 | |
|     if not (hasattr(sys.stdin, 'isatty') and sys.stdin.isatty()):
 | |
|         return default
 | |
| 
 | |
|     answer = raw_input("%s %s " % (prompt, prompt_default))
 | |
| 
 | |
|     if answer == "":
 | |
|         return default
 | |
|     else:
 | |
|         return answer.lower() in ("yes", "y")
 | |
| 
 | |
| 
 | |
| class PrettyTable(object):
 | |
|     """Creates an ASCII art table
 | |
| 
 | |
|     Example:
 | |
| 
 | |
|         ID  Name              Size         Hits
 | |
|         --- ----------------- ------------ -----
 | |
|         122 image                       22     0
 | |
|     """
 | |
|     def __init__(self):
 | |
|         self.columns = []
 | |
| 
 | |
|     def add_column(self, width, label="", just='l'):
 | |
|         """Add a column to the table
 | |
| 
 | |
|         :param width: number of characters wide the column should be
 | |
|         :param label: column heading
 | |
|         :param just: justification for the column, 'l' for left,
 | |
|                      'r' for right
 | |
|         """
 | |
|         self.columns.append((width, label, just))
 | |
| 
 | |
|     def make_header(self):
 | |
|         label_parts = []
 | |
|         break_parts = []
 | |
|         for width, label, _ in self.columns:
 | |
|             # NOTE(sirp): headers are always left justified
 | |
|             label_part = self._clip_and_justify(label, width, 'l')
 | |
|             label_parts.append(label_part)
 | |
| 
 | |
|             break_part = '-' * width
 | |
|             break_parts.append(break_part)
 | |
| 
 | |
|         label_line = ' '.join(label_parts)
 | |
|         break_line = ' '.join(break_parts)
 | |
|         return '\n'.join([label_line, break_line])
 | |
| 
 | |
|     def make_row(self, *args):
 | |
|         row = args
 | |
|         row_parts = []
 | |
|         for data, (width, _, just) in zip(row, self.columns):
 | |
|             row_part = self._clip_and_justify(data, width, just)
 | |
|             row_parts.append(row_part)
 | |
| 
 | |
|         row_line = ' '.join(row_parts)
 | |
|         return row_line
 | |
| 
 | |
|     @staticmethod
 | |
|     def _clip_and_justify(data, width, just):
 | |
|         # clip field to column width
 | |
|         clipped_data = str(data)[:width]
 | |
| 
 | |
|         if just == 'r':
 | |
|             # right justify
 | |
|             justified = clipped_data.rjust(width)
 | |
|         else:
 | |
|             # left justify
 | |
|             justified = clipped_data.ljust(width)
 | |
| 
 | |
|         return justified
 |