class server_wait(_CycladesInit, _ServerWait): """Wait for server to change its status (default: --while BUILD)""" arguments = dict( timeout=IntArgument( 'Wait limit in seconds (default: 60)', '--timeout', default=60), status=StatusArgument( 'DEPRECATED in next version, equivalent to "--while"', '--status', valid_states=server_states), status_w=StatusArgument( 'Wait while in status (%s)' % ','.join(server_states), '--while', valid_states=server_states), status_u=StatusArgument( 'Wait until status is reached (%s)' % ','.join(server_states), '--until', valid_states=server_states), ) @errors.Generic.all @errors.Cyclades.connection @errors.Cyclades.server_id def _run(self, server_id): r = self.client.get_server_details(server_id) if self['status_u']: if r['status'].lower() == self['status_u'].lower(): self.error( 'Server %s: already in %s' % (server_id, r['status'])) else: self.wait_until( server_id, self['status_u'], timeout=self['timeout']) else: status_w = self['status_w'] or self['status'] or 'BUILD' if r['status'].lower() == status_w.lower(): self.wait_while( server_id, status_w, timeout=self['timeout']) else: self.error( 'Server %s status: %s' % (server_id, r['status'])) def main(self, server_id): super(self.__class__, self)._run() status_args = [self['status'], self['status_w'], self['status_u']] if len([x for x in status_args if x]) > 1: raise CLIInvalidArgument( 'Invalid argument combination', importance=2, details=[ 'Arguments %s, %s and %s are mutually exclusive' % ( self.arguments['status'].lvalue, self.arguments['status_w'].lvalue, self.arguments['status_u'].lvalue)]) if self['status']: self.error( 'WARNING: argument %s will be deprecated ' 'in the next version, use %s instead' % ( self.arguments['status'].lvalue, self.arguments['status_w'].lvalue)) self._run(server_id=server_id)
class port_wait(_NetworkInit, _PortWait): """Wait for port to finish (default: BUILD)""" arguments = dict(port_status=StatusArgument( 'Wait while in this status (%s, default: %s)' % (', '.join(port_states), port_states[0]), '--status', valid_states=port_states), timeout=IntArgument('Wait limit in seconds (default: 60)', '--timeout', default=60)) @errors.Generic.all @errors.Cyclades.connection def _run(self, port_id, port_status): port = self.client.get_port_details(port_id) if port['status'].lower() == port_status.lower(): self.wait(port_id, port_status, timeout=self['timeout']) else: self.error('Port %s: Cannot wait for status %s, ' 'status is already %s' % (port_id, port_status, port['status'])) def main(self, port_id): super(self.__class__, self)._run() port_status = self['port_status'] or port_states[0] self._run(port_id=port_id, port_status=port_status)
class server_wait(_CycladesInit, _ServerWait): """Wait for server to change its status (default: BUILD)""" arguments = dict(timeout=IntArgument('Wait limit in seconds (default: 60)', '--timeout', default=60), server_status=StatusArgument( 'Status to wait for (%s, default: %s)' % (', '.join(server_states), server_states[0]), '--status', valid_states=server_states)) @errors.Generic.all @errors.Cyclades.connection @errors.Cyclades.server_id def _run(self, server_id, current_status): r = self.client.get_server_details(server_id) if r['status'].lower() == current_status.lower(): self.wait(server_id, current_status, timeout=self['timeout']) else: self.error('Server %s: Cannot wait for status %s, ' 'status is already %s' % (server_id, current_status, r['status'])) def main(self, server_id): super(self.__class__, self)._run() self._run(server_id=server_id, current_status=self['server_status'] or 'BUILD')
class flavor_list(_CycladesInit, OptionalOutput, NameFilter, IDFilter): """List available hardware flavors""" arguments = dict( detail=FlagArgument('show detailed output', ('-l', '--details')), limit=IntArgument('limit # of listed flavors', ('-n', '--number')), more=FlagArgument( 'output results in pages (-n to set items per page, default 10)', '--more'), enum=FlagArgument('Enumerate results', '--enumerate'), ram=ValueArgument('filter by ram', ('--ram')), vcpus=ValueArgument('filter by number of VCPUs', ('--vcpus')), disk=ValueArgument('filter by disk size in GB', ('--disk')), disk_template=ValueArgument( 'filter by disk_templace', ('--disk-template')), project_id=ValueArgument('filter by project ID', '--project'), is_public=FlagArgument('list only public flavors', '--public'), ) def _apply_common_filters(self, flavors): common_filters = dict() if self['ram']: common_filters['ram'] = self['ram'] if self['vcpus']: common_filters['vcpus'] = self['vcpus'] if self['disk']: common_filters['disk'] = self['disk'] if self['disk_template']: common_filters['SNF:disk_template'] = self['disk_template'] return filter_dicts_by_dict(flavors, common_filters) @errors.Generic.all @errors.Cyclades.connection def _run(self): withcommons = self['ram'] or self['vcpus'] or ( self['disk'] or self['disk_template']) detail = self['detail'] or withcommons flavors = self.client.list_flavors( detail, is_public=self['is_public'], project_id=self['project_id']) flavors = self._filter_by_name(flavors) flavors = self._filter_by_id(flavors) if withcommons: flavors = self._apply_common_filters(flavors) if not (self['detail'] or self['output_format']): remove_from_items(flavors, 'links') if detail and not self['detail']: for flv in flavors: for key in set(flv).difference(['id', 'name']): flv.pop(key) kwargs = dict(out=StringIO(), title=()) if self['more'] else {} self.print_(flavors, with_enumeration=self['enum'], **kwargs) if self['more']: pager(kwargs['out'].getvalue()) def main(self): super(self.__class__, self)._run() self._run()
class project_modify(_AstakosInit, OptionalOutput): """Modify properties of a project""" __doc__ += _project_specs arguments = dict( specs_path=ValueArgument('Specification file (contents in json)', '--spec-file'), project_name=ValueArgument('Name the project', '--name'), owner_uuid=ValueArgument('Project owner', '--owner'), homepage_url=ValueArgument('Project homepage', '--homepage'), description=ValueArgument('Describe the project', '--description'), max_members=IntArgument('Maximum subscribers', '--max-members'), private=FlagArgument('Make the project private', '--private'), public=FlagArgument('Make the project public', '--public'), start_date=DateArgument('When to start the project', '--start-date'), end_date=DateArgument('When to end the project', '--end-date'), join_policy=PolicyArgument( 'Set join policy (%s)' % ', '.join(PolicyArgument.policies), '--join-policy'), leave_policy=PolicyArgument( 'Set leave policy (%s)' % ', '.join(PolicyArgument.policies), '--leave-policy'), resource_capacities=ProjectResourceArgument( 'Set the member and project capacities for resources (repeatable) ' 'e.g., --resource cyclades.cpu=1,5 means "members will have at ' 'most 1 cpu but the project will have at most 5" To see all ' 'resources: kamaki resource list', '--resource')) required = [ 'specs_path', 'owner_uuid', 'homepage_url', 'description', 'public', 'private', 'project_name', 'start_date', 'end_date', 'join_policy', 'leave_policy', 'resource_capacities', 'max_members' ] @errors.Generic.all @errors.Astakos.astakosclient @errors.Astakos.project_id @apply_notification def _run(self, project_id): specs = dict() if self['specs_path']: with open(abspath(self['specs_path'])) as f: specs = load(f) for key, arg in (('name', self['project_name']), ('owner', self['owner_uuid']), ('homepage', self['homepage_url']), ('description', self['description']), ('max_members', self['max_members']), ('start_date', self.arguments['start_date'].isoformat), ('end_date', self.arguments['end_date'].isoformat), ('join_policy', self['join_policy']), ('leave_policy', self['leave_policy']), ('resources', self['resource_capacities'])): if arg: specs[key] = arg private = self['private'] or (False if self['public'] else None) if private is not None: self['private'] = private self.print_(self.client.modify_project(project_id, specs), self.print_dict) def main(self, project_id): super(self.__class__, self)._run() if self['private'] and self['public']: a = self.arguments raise CLIInvalidArgument( 'Invalid argument combination', details=[ 'Arguments %s and %s are mutually exclussive' % (a['private'].lvalue, a['public'].lvalue) ]) self._run(project_id=project_id)
class project_create(_AstakosInit, OptionalOutput): """Apply for a new project""" __doc__ += _project_specs arguments = dict( specs_path=ValueArgument('Specification file (contents in json)', '--spec-file'), project_name=ValueArgument('Name the project', '--name'), owner_uuid=ValueArgument('Project owner', '--owner'), homepage_url=ValueArgument('Project homepage', '--homepage'), description=ValueArgument('Describe the project', '--description'), max_members=IntArgument('Maximum subscribers', '--max-members'), private=BooleanArgument('True for private, False (default) for public', '--private'), start_date=DateArgument('When to start the project', '--start-date'), end_date=DateArgument('When to end the project', '--end-date'), join_policy=PolicyArgument( 'Set join policy (%s)' % ', '.join(PolicyArgument.policies), '--join-policy'), leave_policy=PolicyArgument( 'Set leave policy (%s)' % ', '.join(PolicyArgument.policies), '--leave-policy'), resource_capacities=ProjectResourceArgument( 'Set the member and project capacities for resources (repeatable) ' 'e.g., --resource cyclades.cpu=1,5 means "members will have at ' 'most 1 cpu but the project will have at most 5" To see all ' 'resources: kamaki resource list', '--resource')) required = ['specs_path', 'project_name', 'end_date'] @errors.Generic.all @errors.Astakos.astakosclient @apply_notification def _run(self): specs = dict() if self['specs_path']: with open(abspath(self['specs_path'])) as f: specs = load(f) for key, arg in (('name', self['project_name']), ('end_date', self.arguments['end_date'].isoformat), ('start_date', self.arguments['start_date'].isoformat), ('owner', self['owner_uuid']), ('homepage', self['homepage_url']), ('description', self['description']), ('max_members', self['max_members']), ('private', self['private']), ('join_policy', self['join_policy']), ('leave_policy', self['leave_policy']), ('resources', self['resource_capacities'])): if arg: specs[key] = arg self.print_(self.client.create_project(specs), self.print_dict) def main(self): super(self.__class__, self)._run() self._req2 = [arg for arg in self.required if arg != 'specs_path'] if not (self['specs_path'] or all(self[arg] for arg in self._req2)): raise CLIInvalidArgument( 'Insufficient arguments', details=[ 'Both of the following arguments are needed:', ', '.join( [self.arguments[arg].lvalue for arg in self._req2]), 'OR provide a spec file (json) with %s' % self.arguments['specs_path'].lvalue, 'OR combine arguments (higher priority) with a file' ]) self._run()
class server_modify(_CycladesInit): """Modify attributes of a virtual server""" arguments = dict( server_name=ValueArgument('The new name', '--name'), flavor_id=IntArgument('Resize (set another flavor)', '--flavor-id'), firewall_profile=FirewallProfileArgument( 'Valid values: %s' % (', '.join(FirewallProfileArgument.profiles)), '--firewall'), metadata_to_set=KeyValueArgument( 'Set metadata in key=value form (can be repeated)', '--metadata-set'), metadata_to_delete=RepeatableArgument( 'Delete metadata by key (can be repeated)', '--metadata-del'), public_network_port_id=ValueArgument( 'Connection to set new firewall (* for all)', '--port-id'), ) required = [ 'server_name', 'flavor_id', 'firewall_profile', 'metadata_to_set', 'metadata_to_delete'] def _set_firewall_profile(self, server_id): vm = self._restruct_server_info( self.client.get_server_details(server_id)) ports = [p for p in vm['ports'] if 'firewallProfile' in p] pick_port = self.arguments['public_network_port_id'] if pick_port.value: ports = [p for p in ports if pick_port.value in ( '*', '%s' % p['id'])] elif len(ports) > 1: port_strings = ['Server %s ports to public networks:' % server_id] for p in ports: port_strings.append(' %s' % p['id']) for k in ('network_id', 'ipv4', 'ipv6', 'firewallProfile'): v = p.get(k) if v: port_strings.append('\t%s: %s' % (k, v)) raise CLIError( 'Multiple public connections on server %s' % ( server_id), details=port_strings + [ 'To select one:', ' %s PORT_ID' % pick_port.lvalue, 'To set all:', ' %s *' % pick_port.lvalue, ]) if not ports: pp = pick_port.value raise CLIError( 'No public networks attached on server %s%s' % ( server_id, ' through port %s' % pp if pp else ''), details=[ 'To see all networks:', ' kamaki network list', 'To see all connections:', ' kamaki server info %s --nics' % server_id, 'To connect to a network:', ' kamaki network connect NETWORK_ID --device-id %s' % ( server_id)]) for port in ports: self.error('Set port %s firewall to %s' % ( port['id'], self['firewall_profile'])) self.client.set_firewall_profile( server_id=server_id, profile=self['firewall_profile'], port_id=port['id']) def _server_is_stopped(self, server_id, cerror): vm = self.client.get_server_details(server_id) if vm['status'].lower() not in ('stopped'): raise CLIError(cerror, details=[ 'To resize a virtual server, it must be STOPPED', 'Server %s status is %s' % (server_id, vm['status']), 'To stop the server', ' kamaki server shutdown %s -w' % server_id]) @errors.Generic.all @errors.Cyclades.connection @errors.Cyclades.server_id def _run(self, server_id): if self['server_name'] is not None: self.client.update_server_name((server_id), self['server_name']) if self['flavor_id']: try: self.client.resize_server(server_id, self['flavor_id']) except ClientError as ce: if ce.status in (404, ): self._flavor_exists(flavor_id=self['flavor_id']) if ce.status in (400, ): self._server_is_stopped(server_id, ce) raise if self['firewall_profile']: self._set_firewall_profile(server_id) if self['metadata_to_set']: self.client.update_server_metadata( server_id, **self['metadata_to_set']) for key in (self['metadata_to_delete'] or []): errors.Cyclades.metadata( self.client.delete_server_metadata)(server_id, key=key) def main(self, server_id): super(self.__class__, self)._run() pnpid = self.arguments['public_network_port_id'] fp = self.arguments['firewall_profile'] if pnpid.value and not fp.value: raise CLIInvalidArgument('Invalid argument compination', details=[ 'Argument %s should always be combined with %s' % ( pnpid.lvalue, fp.lvalue)]) self._run(server_id=server_id)
class server_create(_CycladesInit, OptionalOutput, _ServerWait): """Create a server (aka Virtual Machine)""" arguments = dict( server_name=ValueArgument('The name of the new server', '--name'), flavor_id=IntArgument('The ID of the flavor', '--flavor-id'), image_id=ValueArgument('The ID of the image', '--image-id'), key_name=ValueArgument('The name of the ssh key to add the server', '--key-name'), personality=PersonalityArgument( (80 * ' ').join(howto_personality), ('-p', '--personality')), wait=FlagArgument('Wait server to build', ('-w', '--wait')), cluster_size=IntArgument( 'Create a cluster of servers of this size. In this case, the name' 'parameter is the prefix of each server in the cluster (e.g.,' 'srv1, srv2, etc.', '--cluster-size'), max_threads=IntArgument( 'Max threads in cluster mode (default 1)', '--threads'), network_configuration=NetworkArgument( 'Connect server to network: [id=]NETWORK_ID[,[ip=]IP] . ' 'Use only NETWORK_ID for private networks. . ' 'Use NETWORK_ID,[ip=]IP for networks with IP. . ' 'Can be repeated, mutually exclussive with --no-network', '--network'), no_network=FlagArgument( 'Do not create any network NICs on the server. . ' 'Mutually exclusive to --network . ' 'If neither --network or --no-network are used, the default ' 'network policy is applied. These policies are set on the cloud, ' 'so kamaki is oblivious to them', '--no-network'), project_id=ValueArgument('Assign server to project', '--project-id'), metadata=KeyValueArgument( 'Add custom metadata in key=value form (can be repeated). ' 'Overwrites metadata defined otherwise (i.e., image).', ('-m', '--metadata')) ) required = ('server_name', 'flavor_id', 'image_id') @errors.Cyclades.cluster_size def _create_cluster(self, prefix, flavor_id, image_id, size): networks = self['network_configuration'] or ( [] if self['no_network'] else None) servers = [dict( name='%s%s' % (prefix, i if size > 1 else ''), flavor_id=flavor_id, image_id=image_id, key_name=self['key_name'], project_id=self['project_id'], personality=self['personality'], metadata=self['metadata'], networks=networks) for i in range(1, 1 + size)] if size == 1: return [self.client.create_server(**servers[0])] self.client.MAX_THREADS = int(self['max_threads'] or 1) try: r = self.client.async_run(self.client.create_server, servers) return r except Exception as e: if size == 1: raise e try: requested_names = [s['name'] for s in servers] spawned_servers = [dict( name=s['name'], id=s['id']) for s in self.client.list_servers() if ( s['name'] in requested_names)] self.error('Failed to build %s servers' % size) self.error('Found %s matching servers:' % len(spawned_servers)) self.print_(spawned_servers, out=self._err) self.error('Check if any of these servers should be removed') except Exception as ne: self.error('Error (%s) while notifying about errors' % ne) finally: raise e def _get_network_client(self): network = getattr(self, '_network_client', None) if not network: net_URL = self.astakos.get_endpoint_url( CycladesNetworkClient.service_type) network = CycladesNetworkClient(net_URL, self.client.token) self._network_client = network return network @errors.Image.id def _image_exists(self, image_id): self.client.get_image_details(image_id) @errors.Cyclades.network_id def _network_exists(self, network_id): network = self._get_network_client() network.get_network_details(network_id) def _ip_ready(self, ip, network_id, cerror): network = self._get_network_client() ips = [fip for fip in network.list_floatingips() if ( fip['floating_ip_address'] == ip)] if not ips: msg = 'IP %s not available for current user' % ip raise CLIError(cerror, details=[msg] + errors.Cyclades.about_ips) ipnet, ipvm = ips[0]['floating_network_id'], ips[0]['instance_id'] if getattr(cerror, 'status', 0) in (409, ): msg = '' if ipnet != network_id: msg = 'IP %s belong to network %s, not %s' % ( ip, ipnet, network_id) elif ipvm: msg = 'IP %s is already used by device %s' % (ip, ipvm) if msg: raise CLIError(cerror, details=[ msg, 'To get details on IP', ' kamaki ip info %s' % ip] + errors.Cyclades.about_ips) @errors.Generic.all @errors.Cyclades.connection def _run(self): try: for r in self._create_cluster( self['server_name'], self['flavor_id'], self['image_id'], size=self['cluster_size'] or 1): if not r: self.error('Create %s: server response was %s' % ( self['server_name'], r)) continue self.print_(r, self.print_dict) if self['wait']: self.wait_while(r['id'], r['status'] or 'BUILD') self.writeln(' ') except ClientError as ce: if ce.status in (404, 400): self._flavor_exists(flavor_id=self['flavor_id']) self._image_exists(image_id=self['image_id']) if ce.status in (404, 400, 409): for net in self['network_configuration'] or []: self._network_exists(network_id=net['uuid']) if 'fixed_ip' in net: self._ip_ready(net['fixed_ip'], net['uuid'], ce) if self['project_id'] and ce.status in (400, 403, 404): self._project_id_exists(project_id=self['project_id']) raise def main(self): super(self.__class__, self)._run() if self['no_network'] and self['network_configuration']: raise CLIInvalidArgument( 'Invalid argument combination', importance=2, details=[ 'Arguments %s and %s are mutually exclusive' % ( self.arguments['no_network'].lvalue, self.arguments['network_configuration'].lvalue)]) self._run()
class server_list(_CycladesInit, OptionalOutput, NameFilter, IDFilter): """List virtual servers accessible by user Use filtering arguments (e.g., --name-like) to manage long server lists """ arguments = dict( detail=FlagArgument('show detailed output', ('-l', '--details')), since=DateArgument( 'show only items modified since date (\'H:M:S YYYY-mm-dd\') ' 'Can look back up to a limit (POLL_TIME) defined on service-side', '--since'), limit=IntArgument( 'limit number of listed virtual servers', ('-n', '--number')), more=FlagArgument( 'output results in pages (-n to set items per page, default 10)', '--more'), enum=FlagArgument('Enumerate results', '--enumerate'), flavor_id=ValueArgument('filter by flavor id', ('--flavor-id')), image_id=ValueArgument('filter by image id', ('--image-id')), user_id=ValueArgument('filter by user id', ('--user-id')), user_name=ValueArgument('filter by user name', ('--user-name')), status=ValueArgument( 'filter by status (ACTIVE, STOPPED, REBOOT, ERROR, etc.)', ('--status')), meta=KeyValueArgument('filter by metadata key=values', ('--metadata')), meta_like=KeyValueArgument( 'print only if in key=value, the value is part of actual value', ('--metadata-like')), ) def _add_user_name(self, servers): uuids = self._uuids2usernames(list(set( [srv['user_id'] for srv in servers]))) for srv in servers: srv['user_id'] += ' (%s)' % uuids[srv['user_id']] return servers def _apply_common_filters(self, servers): common_filters = dict() if self['status']: common_filters['status'] = self['status'] if self['user_id'] or self['user_name']: uuid = self['user_id'] or self._username2uuid(self['user_name']) common_filters['user_id'] = uuid return filter_dicts_by_dict(servers, common_filters) def _filter_by_image(self, servers): iid = self['image_id'] return [srv for srv in servers if srv['image']['id'] == iid] def _filter_by_flavor(self, servers): fid = self['flavor_id'] return [srv for srv in servers if ( '%s' % srv['flavor']['id'] == '%s' % fid)] def _filter_by_metadata(self, servers): new_servers = [] for srv in servers: if 'metadata' not in srv: continue meta = [dict(srv['metadata'])] if self['meta']: meta = filter_dicts_by_dict(meta, self['meta']) if meta and self['meta_like']: meta = filter_dicts_by_dict( meta, self['meta_like'], exact_match=False) if meta: new_servers.append(srv) return new_servers @errors.Generic.all @errors.Cyclades.connection @errors.Cyclades.date def _run(self): withimage = bool(self['image_id']) withflavor = bool(self['flavor_id']) withmeta = bool(self['meta'] or self['meta_like']) withcommons = bool( self['status'] or self['user_id'] or self['user_name']) detail = self['detail'] or ( withimage or withflavor or withmeta or withcommons) ch_since = self.arguments['since'].isoformat if self['since'] else None servers = list(self.client.list_servers(detail, ch_since) or []) servers = self._filter_by_name(servers) servers = self._filter_by_id(servers) servers = self._apply_common_filters(servers) if withimage: servers = self._filter_by_image(servers) if withflavor: servers = self._filter_by_flavor(servers) if withmeta: servers = self._filter_by_metadata(servers) if detail and self['detail']: pass else: for srv in servers: for key in set(srv).difference(['id', 'name']): srv.pop(key) kwargs = dict(with_enumeration=self['enum']) if self['more']: codecinfo = codecs.lookup('utf-8') kwargs['out'] = codecs.StreamReaderWriter( cStringIO.StringIO(), codecinfo.streamreader, codecinfo.streamwriter) kwargs['title'] = () if self['limit']: servers = servers[:self['limit']] self.print_(servers, **kwargs) if self['more']: pager(kwargs['out'].getvalue()) def main(self): super(self.__class__, self)._run() self._run()
class imagecompute_list(_CycladesInit, OptionalOutput, NameFilter, IDFilter): """List images""" arguments = dict( detail=FlagArgument('show detailed output', ('-l', '--details')), limit=IntArgument('limit number listed images', ('-n', '--number')), more=FlagArgument('handle long lists of results', '--more'), enum=FlagArgument('Enumerate results', '--enumerate'), user_id=ValueArgument('filter by user_id', '--user-id'), user_name=ValueArgument('filter by username', '--user-name'), meta=KeyValueArgument( 'filter by metadata key=value (can be repeated)', ('--metadata')), meta_like=KeyValueArgument( 'filter by metadata key=value (can be repeated)', ('--metadata-like')) ) def _filter_by_metadata(self, images): new_images = [] for img in images: meta = [dict(img['metadata'])] if self['meta']: meta = filter_dicts_by_dict(meta, self['meta']) if meta and self['meta_like']: meta = filter_dicts_by_dict( meta, self['meta_like'], exact_match=False) if meta: new_images.append(img) return new_images def _filter_by_user(self, images): uuid = self['user_id'] or self._username2uuid(self['user_name']) return filter_dicts_by_dict(images, dict(user_id=uuid)) def _add_name(self, images, key='user_id'): uuids = self._uuids2usernames( list(set([img[key] for img in images]))) for img in images: img[key] += ' (%s)' % uuids[img[key]] return images @errors.Generic.all @errors.Cyclades.connection def _run(self): withmeta = bool(self['meta'] or self['meta_like']) withuser = bool(self['user_id'] or self['user_name']) detail = self['detail'] or withmeta or withuser images = self.client.list_images(detail) images = self._filter_by_name(images) images = self._filter_by_id(images) if withuser: images = self._filter_by_user(images) if withmeta: images = self._filter_by_metadata(images) if self['detail'] and not self['output_format']: images = self._add_name(self._add_name(images, 'tenant_id')) elif detail and not self['detail']: for img in images: for key in set(img).difference(['id', 'name']): img.pop(key) kwargs = dict(with_enumeration=self['enum']) if self['limit']: images = images[:self['limit']] if self['more']: kwargs['out'] = StringIO() kwargs['title'] = () self.print_(images, **kwargs) if self['more']: pager(kwargs['out'].getvalue()) def main(self): super(self.__class__, self)._run() self._run()
class image_register(_ImageInit, OptionalOutput): """(Re)Register an image file to an Image service The image file must be stored at a pithos repository Some metadata can be set by user (e.g., disk-format) while others are set by the system (e.g., image id). Custom user metadata are termed as "properties". A register command creates a remote meta file at /CONTAINER/IMAGE_PATH.meta Users may download and edit this file and use it to re-register. In case of a meta file, runtime arguments for metadata or properties override meta file settings. """ container_info_cache = {} arguments = dict( checksum=ValueArgument('Set image checksum', '--checksum'), container_format=ValueArgument( 'Set container format', '--container-format'), disk_format=ValueArgument('Set disk format', '--disk-format'), owner_name=ValueArgument('Set user uuid by user name', '--owner-name'), properties=KeyValueArgument( 'Add property (user-specified metadata) in key=value form' '(can be repeated)', ('-p', '--property')), is_public=FlagArgument('Mark image as public', '--public'), size=IntArgument('Set image size in bytes', '--size'), metafile=ValueArgument( 'Load metadata from a json-formated file IMAGE_FILE.meta :' '{"key1": "val1", "key2": "val2", ..., "properties: {...}"}', ('--metafile')), force_upload=FlagArgument( 'Overwrite remote files (image file, metadata file)', ('-f', '--force')), no_metafile_upload=FlagArgument( 'Do not store metadata in remote meta file', ('--no-metafile-upload')), container=ValueArgument( 'Pithos+ container containing the image file', ('-C', '--container')), uuid=ValueArgument('Custom user uuid', '--uuid'), local_image_path=ValueArgument( 'Local image file path to upload and register ' '(still need target file in the form /CONTAINER/REMOTE-PATH )', '--upload-image-file'), progress_bar=ProgressBarArgument( 'Do not use progress bar', '--no-progress-bar', default=False), name=ValueArgument('The name of the new image', '--name'), pithos_location=PithosLocationArgument( 'The Pithos+ image location to put the image at. Format: ' 'pithos://USER_UUID/CONTAINER/IMAGE_PATH or ' '/CONTAINER/IMAGE_PATH', '--location') ) required = ('name', 'pithos_location') def _get_pithos_client(self, locator): pithos = self.get_client(PithosClient, 'pithos') pithos.account, pithos.container = locator.uuid, locator.container return pithos def _load_params_from_file(self, location): params, properties = dict(), dict() pfile = self['metafile'] if pfile: try: for k, v in load_image_meta(pfile).items(): key = k.lower().replace('-', '_') if key == 'properties': for pk, pv in v.items(): properties[pk.upper().replace('-', '_')] = pv elif key == 'name': continue elif key == 'location': if location: continue location = v else: params[key] = v except Exception as e: raiseCLIError(e, 'Invalid json metadata config file') return params, properties, location def _load_params_from_args(self, params, properties): for key in set([ 'checksum', 'container_format', 'disk_format', 'owner', 'size', 'is_public']).intersection(self.arguments): params[key] = self[key] for k, v in self['properties'].items(): properties[k.upper().replace('-', '_')] = v def _assert_remote_file_not_exist(self, pithos, path): if pithos and not self['force_upload']: try: pithos.get_object_info(path) raise CLIError('File already exists', importance=2, details=[ 'A remote file /%s/%s already exists' % ( pithos.container, path), 'Use %s to force upload' % self.arguments[ 'force_upload'].lvalue]) except ClientError as ce: if ce.status != 404: raise @errors.Generic.all @errors.Image.connection def _run(self, name, locator): location, pithos = locator.value, None if self['local_image_path']: with open(self['local_image_path']) as f: pithos = self._get_pithos_client(locator) self._assert_remote_file_not_exist(pithos, locator.path) (pbar, upload_cb) = self._safe_progress_bar('Uploading') if pbar: hash_bar = pbar.clone() hash_cb = hash_bar.get_generator('Calculating hashes') pithos.upload_object( locator.path, f, hash_cb=hash_cb, upload_cb=upload_cb, container_info_cache=self.container_info_cache) pbar.finish() (params, properties, new_loc) = self._load_params_from_file(location) if location != new_loc: locator.value = new_loc self._load_params_from_args(params, properties) if not self['no_metafile_upload']: # check if metafile exists pithos = pithos or self._get_pithos_client(locator) meta_path = '%s.meta' % locator.path self._assert_remote_file_not_exist(pithos, meta_path) # register the image try: r = self.client.register(name, location, params, properties) except ClientError as ce: if ce.status in (400, 404): raise CLIError( 'Nonexistent image file location %s' % location, details=[ '%s' % ce, 'Does the image file %s exist at container %s ?' % ( locator.path, locator.container)] + howto_image_file) raise r['owner'] += ' (%s)' % self._uuid2username(r['owner']) self.print_(r, self.print_dict) # upload the metadata file if not self['no_metafile_upload']: try: meta_headers = pithos.upload_from_string( meta_path, dumps(r, indent=2), sharing=dict(read='*' if params.get('is_public') else ''), container_info_cache=self.container_info_cache) except TypeError: self.error('Failed to dump metafile /%s/%s' % ( locator.container, meta_path)) return if self['output_format']: self.print_json(dict( metafile_location='/%s/%s' % ( locator.container, meta_path), headers=meta_headers)) else: self.error('Metadata file uploaded as /%s/%s (version %s)' % ( locator.container, meta_path, meta_headers['x-object-version'])) def main(self): super(self.__class__, self)._run() locator, pithos = self.arguments['pithos_location'], None locator.setdefault('uuid', self.astakos.user_term('id')) locator.path = locator.path or path.basename( self['local_image_path'] or '') if not locator.path: raise CLIInvalidArgument( 'Missing the image file or object', details=[ 'Pithos+ URI %s does not point to a physical image' % ( locator.value), 'A physical image is necessary.', 'It can be a remote Pithos+ object or a local file.', 'To specify a remote image object:', ' %s [pithos://UUID]/CONTAINER/PATH' % locator.lvalue, 'To specify a local file:', ' %s [pithos://UUID]/CONTAINER[/PATH] %s LOCAL_PATH' % ( locator.lvalue, self.arguments['local_image_path'].lvalue)]) self.arguments['pithos_location'].setdefault( 'uuid', self.astakos.user_term('id')) self._run(self['name'], locator)
class image_list(_ImageInit, OptionalOutput, NameFilter, IDFilter): """List images accessible by user""" arguments = dict( detail=FlagArgument('show detailed output', ('-l', '--details')), container_format=ValueArgument( 'filter by container format', '--container-format'), disk_format=ValueArgument('filter by disk format', '--disk-format'), size_min=IntArgument('filter by minimum size', '--size-min'), size_max=IntArgument('filter by maximum size', '--size-max'), status=ValueArgument('filter by status', '--status'), owner=ValueArgument('filter by owner', '--owner'), owner_name=ValueArgument('filter by owners username', '--owner-name'), order=ValueArgument( 'order by FIELD ( - to reverse order)', '--order', default=''), limit=IntArgument('limit number of listed images', ('-n', '--number')), more=FlagArgument( 'output results in pages (-n to set items per page, default 10)', '--more'), enum=FlagArgument('Enumerate results', '--enumerate'), prop=KeyValueArgument('filter by property key=value', ('--property')), prop_like=KeyValueArgument( 'fliter by property key=value where value is part of actual value', ('--property-like')), image_ID_for_members=ValueArgument( 'List members of an image', '--members-of'), ) def _filter_by_owner(self, images): ouuid = self['owner'] or self._username2uuid(self['owner_name']) return filter_dicts_by_dict(images, dict(owner=ouuid)) def _add_owner_name(self, images): uuids = self._uuids2usernames( list(set([img['owner'] for img in images]))) for img in images: img['owner'] += ' (%s)' % uuids[img['owner']] return images def _filter_by_properties(self, images): new_images = [] for img in images: props = [dict(img['properties'])] if self['prop']: props = filter_dicts_by_dict(props, self['prop']) if props and self['prop_like']: props = filter_dicts_by_dict( props, self['prop_like'], exact_match=False) if props: new_images.append(img) return new_images def _members(self, image_id): members = self.client.list_members(image_id) if not self['output_format']: uuids = [member['member_id'] for member in members] usernames = self._uuids2usernames(uuids) for member in members: member['member_id'] += ' (%s)' % usernames[member['member_id']] self.print_(members, title=('member_id',)) @errors.Generic.all @errors.Cyclades.connection def _run(self): super(self.__class__, self)._run() if self['image_ID_for_members']: return self._members(self['image_ID_for_members']) filters = {} for arg in set([ 'container_format', 'disk_format', 'name', 'size_min', 'size_max', 'status']).intersection(self.arguments): filters[arg] = self[arg] order = self['order'] detail = any([self[x] for x in ( 'detail', 'prop', 'prop_like', 'owner', 'owner_name')]) images = self.client.list_public(detail, filters, order) if self['owner'] or self['owner_name']: images = self._filter_by_owner(images) if self['prop'] or self['prop_like']: images = self._filter_by_properties(images) images = self._filter_by_id(images) images = self._non_exact_name_filter(images) for img in [] if self['output_format'] else images: try: img['size'] = format_size(img['size']) except KeyError: continue if self['detail'] and not self['output_format']: images = self._add_owner_name(images) elif detail and not self['detail']: for img in images: for key in set(img).difference([ 'id', 'name', 'status', 'container_format', 'disk_format', 'size']): img.pop(key) kwargs = dict(with_enumeration=self['enum']) if self['limit']: images = images[:self['limit']] if self['more']: kwargs['out'] = StringIO() kwargs['title'] = () self.print_(images, **kwargs) if self['more']: pager(kwargs['out'].getvalue()) def main(self): super(self.__class__, self)._run() self._run()