diff options
Diffstat (limited to 'openstackclient')
40 files changed, 620 insertions, 280 deletions
diff --git a/openstackclient/api/api.py b/openstackclient/api/api.py index 67386aaf..90b4e9c3 100644 --- a/openstackclient/api/api.py +++ b/openstackclient/api/api.py @@ -15,8 +15,7 @@ import simplejson as json -from keystoneclient.openstack.common.apiclient \ - import exceptions as ksc_exceptions +from keystoneclient import exceptions as ksc_exceptions from keystoneclient import session as ksc_session from openstackclient.common import exceptions diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index edf4ffdb..6d04b5c9 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -147,6 +147,21 @@ class ShowQuota(show.ShowOne): ) return parser + def get_quota(self, client, parsed_args): + try: + if parsed_args.quota_class: + quota = client.quota_classes.get(parsed_args.project) + elif parsed_args.default: + quota = client.quotas.defaults(parsed_args.project) + else: + quota = client.quotas.get(parsed_args.project) + except Exception as e: + if type(e).__name__ == 'EndpointNotFound': + return {} + else: + raise e + return quota._info + def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) @@ -159,23 +174,12 @@ class ShowQuota(show.ShowOne): # does not exist. If this is determined to be the # intended behaviour of the API we will validate # the argument with Identity ourselves later. - if parsed_args.quota_class: - compute_quota = compute_client.quota_classes.get( - parsed_args.project) - volume_quota = volume_client.quota_classes.get( - parsed_args.project) - elif parsed_args.default: - compute_quota = compute_client.quotas.defaults( - parsed_args.project) - volume_quota = volume_client.quotas.defaults( - parsed_args.project) - else: - compute_quota = compute_client.quotas.get(parsed_args.project) - volume_quota = volume_client.quotas.get(parsed_args.project) + compute_quota_info = self.get_quota(compute_client, parsed_args) + volume_quota_info = self.get_quota(volume_client, parsed_args) info = {} - info.update(compute_quota._info) - info.update(volume_quota._info) + info.update(compute_quota_info) + info.update(volume_quota_info) # Map the internal quota names to the external ones for k, v in itertools.chain( diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index 3725350a..166747d5 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -50,6 +50,7 @@ def make_client(instance): extensions=extensions, http_log_debug=http_log_debug, timings=instance.timing, + region_name=instance._region_name, ) return client diff --git a/openstackclient/compute/v2/availability_zone.py b/openstackclient/compute/v2/availability_zone.py new file mode 100644 index 00000000..648c0ee4 --- /dev/null +++ b/openstackclient/compute/v2/availability_zone.py @@ -0,0 +1,102 @@ +# 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. +# + +"""Compute v2 Availability Zone action implementations""" + +import copy +import logging + +from cliff import lister +from novaclient import exceptions as nova_exceptions +import six + +from openstackclient.common import utils +from openstackclient.i18n import _ # noqa + + +def _xform_availability_zone(az, include_extra): + result = [] + zone_info = {} + if hasattr(az, 'zoneState'): + zone_info['zone_status'] = ('available' if az.zoneState['available'] + else 'not available') + if hasattr(az, 'zoneName'): + zone_info['zone_name'] = az.zoneName + + if not include_extra: + result.append(zone_info) + return result + + if hasattr(az, 'hosts') and az.hosts: + for host, services in six.iteritems(az.hosts): + host_info = copy.deepcopy(zone_info) + host_info['host_name'] = host + + for svc, state in six.iteritems(services): + info = copy.deepcopy(host_info) + info['service_name'] = svc + info['service_status'] = '%s %s %s' % ( + 'enabled' if state['active'] else 'disabled', + ':-)' if state['available'] else 'XXX', + state['updated_at']) + result.append(info) + else: + zone_info['host_name'] = '' + zone_info['service_name'] = '' + zone_info['service_status'] = '' + result.append(zone_info) + return result + + +class ListAvailabilityZone(lister.Lister): + """List availability zones and their status""" + + log = logging.getLogger(__name__ + '.ListAvailabilityZone') + + def get_parser(self, prog_name): + parser = super(ListAvailabilityZone, self).get_parser(prog_name) + parser.add_argument( + '--long', + action='store_true', + default=False, + help=_('List additional fields in output'), + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + + if parsed_args.long: + columns = ('Zone Name', 'Zone Status', + 'Host Name', 'Service Name', 'Service Status') + else: + columns = ('Zone Name', 'Zone Status') + + compute_client = self.app.client_manager.compute + try: + data = compute_client.availability_zones.list() + except nova_exceptions.Forbidden as e: # policy doesn't allow + try: + data = compute_client.availability_zones.list(detailed=False) + except Exception: + raise e + + # Argh, the availability zones are not iterable... + result = [] + for zone in data: + result += _xform_availability_zone(zone, parsed_args.long) + + return (columns, + (utils.get_dict_properties( + s, columns + ) for s in result)) diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 6dd00b1b..6f3788a0 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -34,64 +34,71 @@ class CreateFlavor(show.ShowOne): parser = super(CreateFlavor, self).get_parser(prog_name) parser.add_argument( "name", - metavar="<name>", + metavar="<flavor-name>", help="New flavor name", ) parser.add_argument( "--id", metavar="<id>", default='auto', - help="Unique flavor ID; 'auto' will create a UUID " - "(default: auto)") + help="Unique flavor ID; 'auto' creates a UUID " + "(default: auto)", + ) parser.add_argument( "--ram", type=int, metavar="<size-mb>", default=256, - help="Memory size in MB (default 256M)") + help="Memory size in MB (default 256M)", + ) parser.add_argument( "--disk", type=int, metavar="<size-gb>", default=0, - help="Disk size in GB (default 0G)") + help="Disk size in GB (default 0G)", + ) parser.add_argument( "--ephemeral", type=int, metavar="<size-gb>", + default=0, help="Ephemeral disk size in GB (default 0G)", - default=0) + ) parser.add_argument( "--swap", type=int, metavar="<size-gb>", + default=0, help="Swap space size in GB (default 0G)", - default=0) + ) parser.add_argument( "--vcpus", type=int, metavar="<vcpus>", default=1, - help="Number of vcpus (default 1)") + help="Number of vcpus (default 1)", + ) parser.add_argument( "--rxtx-factor", type=int, metavar="<factor>", + default=1, help="RX/TX factor (default 1)", - default=1) + ) public_group = parser.add_mutually_exclusive_group() public_group.add_argument( "--public", dest="public", action="store_true", default=True, - help="Flavor is accessible to other projects (default)", + help="Flavor is available to other projects (default)", ) public_group.add_argument( "--private", dest="public", action="store_false", - help="Flavor is inaccessible to other projects", + help="Flavor is not available to other projects", ) return parser @@ -168,7 +175,7 @@ class ListFlavor(lister.Lister): class ShowFlavor(show.ShowOne): - """Show flavor details""" + """Display flavor details""" log = logging.getLogger(__name__ + ".ShowFlavor") diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index cd330857..f7ffb1d1 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -23,7 +23,7 @@ from cliff import command from cliff import lister from cliff import show -from keystoneclient.openstack.common.apiclient import exceptions as ksc_exc +from keystoneclient import exceptions as ksc_exc from novaclient.v1_1 import security_group_rules from openstackclient.common import parseractions from openstackclient.common import utils diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 5ab1d5f3..a5d8b0c3 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -511,25 +511,27 @@ class CreateServerImage(show.ShowOne): class DeleteServer(command.Command): - """Delete server command""" + """Delete server(s)""" log = logging.getLogger(__name__ + '.DeleteServer') def get_parser(self, prog_name): parser = super(DeleteServer, self).get_parser(prog_name) parser.add_argument( - 'server', + 'servers', metavar='<server>', - help=_('Server (name or ID)'), + nargs="+", + help=_('Server(s) to delete (name or ID)'), ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute - server = utils.find_resource( - compute_client.servers, parsed_args.server) - compute_client.servers.delete(server.id) + for server in parsed_args.servers: + server_obj = utils.find_resource( + compute_client.servers, server) + compute_client.servers.delete(server_obj.id) return diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py index ed98af26..308241cf 100644 --- a/openstackclient/compute/v2/usage.py +++ b/openstackclient/compute/v2/usage.py @@ -20,6 +20,8 @@ import logging import sys from cliff import lister +from cliff import show +import six from openstackclient.common import utils @@ -60,12 +62,14 @@ class ListUsage(lister.Lister): compute_client = self.app.client_manager.compute columns = ( "tenant_id", + "server_usages", "total_memory_mb_usage", "total_vcpus_usage", "total_local_gb_usage" ) column_headers = ( "Project", + "Servers", "RAM MB-Hours", "CPU Hours", "Disk GB-Hours" @@ -84,7 +88,7 @@ class ListUsage(lister.Lister): else: end = now + datetime.timedelta(days=1) - usage_list = compute_client.usage.list(start, end) + usage_list = compute_client.usage.list(start, end, detailed=True) # Cache the project list project_cache = {} @@ -95,8 +99,8 @@ class ListUsage(lister.Lister): # Just forget it if there's any trouble pass - if len(usage_list) > 0: - sys.stdout.write("Usage from %s to %s:" % ( + if parsed_args.formatter == 'table' and len(usage_list) > 0: + sys.stdout.write("Usage from %s to %s: \n" % ( start.strftime(dateformat), end.strftime(dateformat), )) @@ -106,8 +110,81 @@ class ListUsage(lister.Lister): s, columns, formatters={ 'tenant_id': _format_project, + 'server_usages': lambda x: len(x), 'total_memory_mb_usage': lambda x: float("%.2f" % x), 'total_vcpus_usage': lambda x: float("%.2f" % x), 'total_local_gb_usage': lambda x: float("%.2f" % x), }, ) for s in usage_list)) + + +class ShowUsage(show.ShowOne): + """Show resource usage for a single project. """ + + log = logging.getLogger(__name__ + ".ShowUsage") + + def get_parser(self, prog_name): + parser = super(ShowUsage, self).get_parser(prog_name) + parser.add_argument( + "--project", + metavar="<project>", + default=None, + help="Name or ID of project to show usage for." + ) + parser.add_argument( + "--start", + metavar="<start>", + default=None, + help="Usage range start date, ex 2012-01-20" + " (default: 4 weeks ago)." + ) + parser.add_argument( + "--end", + metavar="<end>", + default=None, + help="Usage range end date, ex 2012-01-20 (default: tomorrow)." + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + identity_client = self.app.client_manager.identity + compute_client = self.app.client_manager.compute + dateformat = "%Y-%m-%d" + now = datetime.datetime.utcnow() + + if parsed_args.start: + start = datetime.datetime.strptime(parsed_args.start, dateformat) + else: + start = now - datetime.timedelta(weeks=4) + + if parsed_args.end: + end = datetime.datetime.strptime(parsed_args.end, dateformat) + else: + end = now + datetime.timedelta(days=1) + + if parsed_args.project: + project = utils.find_resource( + identity_client.projects, + parsed_args.project, + ).id + else: + # Get the project from the current auth + project = self.app.client_manager.auth_ref.project_id + + usage = compute_client.usage.get(project, start, end) + + if parsed_args.formatter == 'table': + sys.stdout.write("Usage from %s to %s on project %s: \n" % ( + start.strftime(dateformat), + end.strftime(dateformat), + project + )) + + info = {} + info['Servers'] = len(usage.server_usages) + info['RAM MB-Hours'] = float("%.2f" % usage.total_memory_mb_usage) + info['CPU Hours'] = float("%.2f" % usage.total_vcpus_usage) + info['Disk GB-Hours'] = float("%.2f" % usage.total_local_gb_usage) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index b2f99425..9b195600 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -21,7 +21,7 @@ import six from cliff import command from cliff import lister from cliff import show -from keystoneclient.openstack.common.apiclient import exceptions as ksc_exc +from keystoneclient import exceptions as ksc_exc from openstackclient.common import parseractions from openstackclient.common import utils @@ -104,16 +104,17 @@ class CreateProject(show.ShowOne): class DeleteProject(command.Command): - """Delete an existing project""" + """Delete project(s)""" log = logging.getLogger(__name__ + '.DeleteProject') def get_parser(self, prog_name): parser = super(DeleteProject, self).get_parser(prog_name) parser.add_argument( - 'project', + 'projects', metavar='<project>', - help=_('Project to delete (name or ID)'), + nargs="+", + help=_('Project(s) to delete (name or ID)'), ) return parser @@ -121,12 +122,12 @@ class DeleteProject(command.Command): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity - project = utils.find_resource( - identity_client.tenants, - parsed_args.project, - ) - - identity_client.tenants.delete(project.id) + for project in parsed_args.projects: + project_obj = utils.find_resource( + identity_client.tenants, + project, + ) + identity_client.tenants.delete(project_obj.id) return diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index cec95095..d03664e0 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -21,7 +21,7 @@ import six from cliff import command from cliff import lister from cliff import show -from keystoneclient.openstack.common.apiclient import exceptions as ksc_exc +from keystoneclient import exceptions as ksc_exc from openstackclient.common import exceptions from openstackclient.common import utils @@ -114,16 +114,17 @@ class CreateRole(show.ShowOne): class DeleteRole(command.Command): - """Delete an existing role""" + """Delete role(s)""" log = logging.getLogger(__name__ + '.DeleteRole') def get_parser(self, prog_name): parser = super(DeleteRole, self).get_parser(prog_name) parser.add_argument( - 'role', + 'roles', metavar='<role>', - help=_('Role to delete (name or ID)'), + nargs="+", + help=_('Role(s) to delete (name or ID)'), ) return parser @@ -131,12 +132,12 @@ class DeleteRole(command.Command): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity - role = utils.find_resource( - identity_client.roles, - parsed_args.role, - ) - - identity_client.roles.delete(role.id) + for role in parsed_args.roles: + role_obj = utils.find_resource( + identity_client.roles, + role, + ) + identity_client.roles.delete(role_obj.id) return diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index 955e19e7..b5bbce3b 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -21,7 +21,7 @@ import six from cliff import command from cliff import lister from cliff import show -from keystoneclient.openstack.common.apiclient import exceptions as ksc_exc +from keystoneclient import exceptions as ksc_exc from openstackclient.common import utils from openstackclient.i18n import _ # noqa @@ -128,16 +128,17 @@ class CreateUser(show.ShowOne): class DeleteUser(command.Command): - """Delete user""" + """Delete user(s)""" log = logging.getLogger(__name__ + '.DeleteUser') def get_parser(self, prog_name): parser = super(DeleteUser, self).get_parser(prog_name) parser.add_argument( - 'user', + 'users', metavar='<user>', - help=_('User to delete (name or ID)'), + nargs="+", + help=_('User(s) to delete (name or ID)'), ) return parser @@ -145,12 +146,12 @@ class DeleteUser(command.Command): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity - user = utils.find_resource( - identity_client.users, - parsed_args.user, - ) - - identity_client.users.delete(user.id) + for user in parsed_args.users: + user_obj = utils.find_resource( + identity_client.users, + user, + ) + identity_client.users.delete(user_obj.id) return diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index 1233fea5..189f0970 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -22,14 +22,14 @@ import sys from cliff import command from cliff import lister from cliff import show -from keystoneclient.openstack.common.apiclient import exceptions as ksc_exc +from keystoneclient import exceptions as ksc_exc from openstackclient.common import utils from openstackclient.i18n import _ # noqa class CreateDomain(show.ShowOne): - """Create domain command""" + """Create new domain""" log = logging.getLogger(__name__ + '.CreateDomain') @@ -42,21 +42,20 @@ class CreateDomain(show.ShowOne): ) parser.add_argument( '--description', - metavar='<domain-description>', + metavar='<description>', help='New domain description', ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', - dest='enabled', action='store_true', - default=True, - help='Enable domain') + help='Enable domain (default)', + ) enable_group.add_argument( '--disable', - dest='enabled', - action='store_false', - help='Disable domain') + action='store_true', + help='Disable domain', + ) parser.add_argument( '--or-show', action='store_true', @@ -68,11 +67,15 @@ class CreateDomain(show.ShowOne): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity + enabled = True + if parsed_args.disable: + enabled = False + try: domain = identity_client.domains.create( name=parsed_args.name, description=parsed_args.description, - enabled=parsed_args.enabled, + enabled=enabled, ) except ksc_exc.Conflict as e: if parsed_args.or_show: @@ -87,7 +90,7 @@ class CreateDomain(show.ShowOne): class DeleteDomain(command.Command): - """Delete domain command""" + """Delete domain""" log = logging.getLogger(__name__ + '.DeleteDomain') @@ -96,7 +99,7 @@ class DeleteDomain(command.Command): parser.add_argument( 'domain', metavar='<domain>', - help='Name or ID of domain to delete', + help='Domain to delete (name or ID)', ) return parser @@ -110,7 +113,7 @@ class DeleteDomain(command.Command): class ListDomain(lister.Lister): - """List domain command""" + """List domains""" log = logging.getLogger(__name__ + '.ListDomain') @@ -126,7 +129,7 @@ class ListDomain(lister.Lister): class SetDomain(command.Command): - """Set domain command""" + """Set domain properties""" log = logging.getLogger(__name__ + '.SetDomain') @@ -135,28 +138,26 @@ class SetDomain(command.Command): parser.add_argument( 'domain', metavar='<domain>', - help='Name or ID of domain to change', + help='Domain to modify (name or ID)', ) parser.add_argument( '--name', - metavar='<new-domain-name>', + metavar='<name>', help='New domain name', ) parser.add_argument( '--description', - metavar='<domain-description>', + metavar='<description>', help='New domain description', ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', - dest='enabled', action='store_true', - help='Enable domain (default)', + help='Enable domain', ) enable_group.add_argument( '--disable', - dest='disabled', action='store_true', help='Disable domain', ) @@ -172,9 +173,10 @@ class SetDomain(command.Command): kwargs['name'] = parsed_args.name if parsed_args.description: kwargs['description'] = parsed_args.description - if parsed_args.enabled: + + if parsed_args.enable: kwargs['enabled'] = True - if parsed_args.disabled: + if parsed_args.disable: kwargs['enabled'] = False if not kwargs: @@ -185,7 +187,7 @@ class SetDomain(command.Command): class ShowDomain(show.ShowOne): - """Show domain command""" + """Show domain details""" log = logging.getLogger(__name__ + '.ShowDomain') @@ -194,7 +196,7 @@ class ShowDomain(show.ShowOne): parser.add_argument( 'domain', metavar='<domain>', - help='Name or ID of domain to display', + help='Domain to display (name or ID)', ) return parser diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index 5d3cf642..fbd8dd72 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -22,7 +22,7 @@ import sys from cliff import command from cliff import lister from cliff import show -from keystoneclient.openstack.common.apiclient import exceptions as ksc_exc +from keystoneclient import exceptions as ksc_exc from openstackclient.common import utils from openstackclient.i18n import _ # noqa @@ -39,12 +39,12 @@ class AddUserToGroup(command.Command): parser.add_argument( 'group', metavar='<group>', - help='Group name or ID that user will be added to', + help='Group that user will be added to (name or ID)', ) parser.add_argument( 'user', metavar='<user>', - help='User name or ID to add to group', + help='User to add to group (name or ID)', ) return parser @@ -68,7 +68,7 @@ class AddUserToGroup(command.Command): class CheckUserInGroup(command.Command): - """Checks that user is in a specific group""" + """Check user in group""" log = logging.getLogger(__name__ + '.CheckUserInGroup') @@ -77,12 +77,12 @@ class CheckUserInGroup(command.Command): parser.add_argument( 'group', metavar='<group>', - help='Group name or ID that user will be added to', + help='Group to check if user belongs to (name or ID)', ) parser.add_argument( 'user', metavar='<user>', - help='User name or ID to add to group', + help='User to check (name or ID)', ) return parser @@ -106,7 +106,7 @@ class CheckUserInGroup(command.Command): class CreateGroup(show.ShowOne): - """Create group command""" + """Create new group""" log = logging.getLogger(__name__ + '.CreateGroup') @@ -118,11 +118,11 @@ class CreateGroup(show.ShowOne): help='New group name') parser.add_argument( '--description', - metavar='<group-description>', + metavar='<description>', help='New group description') parser.add_argument( '--domain', - metavar='<group-domain>', + metavar='<domain>', help='References the domain ID or name which owns the group') parser.add_argument( '--or-show', @@ -159,16 +159,17 @@ class CreateGroup(show.ShowOne): class DeleteGroup(command.Command): - """Delete group command""" + """Delete group(s)""" log = logging.getLogger(__name__ + '.DeleteGroup') def get_parser(self, prog_name): parser = super(DeleteGroup, self).get_parser(prog_name) parser.add_argument( - 'group', + 'groups', metavar='<group>', - help='Name or ID of group to delete') + nargs="+", + help='Group(s) to delete (name or ID)') parser.add_argument( '--domain', metavar='<domain>', @@ -180,16 +181,18 @@ class DeleteGroup(command.Command): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity + domain = None if parsed_args.domain: domain = common.find_domain(identity_client, parsed_args.domain) - group = utils.find_resource(identity_client.groups, - parsed_args.group, - domain_id=domain.id) - else: - group = utils.find_resource(identity_client.groups, - parsed_args.group) - - identity_client.groups.delete(group.id) + for group in parsed_args.groups: + if domain is not None: + group_obj = utils.find_resource(identity_client.groups, + group, + domain_id=domain.id) + else: + group_obj = utils.find_resource(identity_client.groups, + group) + identity_client.groups.delete(group_obj.id) return @@ -265,12 +268,12 @@ class RemoveUserFromGroup(command.Command): parser.add_argument( 'group', metavar='<group>', - help='Group name or ID that user will be removed from', + help='Group that user will be removed from (name or ID)', ) parser.add_argument( 'user', metavar='<user>', - help='User name or ID to remove from group', + help='User to remove from group (name or ID)', ) return parser @@ -294,7 +297,7 @@ class RemoveUserFromGroup(command.Command): class SetGroup(command.Command): - """Set group command""" + """Set group properties""" log = logging.getLogger(__name__ + '.SetGroup') @@ -303,18 +306,18 @@ class SetGroup(command.Command): parser.add_argument( 'group', metavar='<group>', - help='Name or ID of group to change') + help='Group to modify (name or ID)') parser.add_argument( '--name', - metavar='<new-group-name>', + metavar='<name>', help='New group name') parser.add_argument( '--domain', - metavar='<group-domain>', - help='New domain name or ID that will now own the group') + metavar='<domain>', + help='New domain that will now own the group (name or ID)') parser.add_argument( '--description', - metavar='<group-description>', + metavar='<description>', help='New group description') return parser @@ -338,7 +341,7 @@ class SetGroup(command.Command): class ShowGroup(show.ShowOne): - """Show group command""" + """Show group details""" log = logging.getLogger(__name__ + '.ShowGroup') @@ -347,7 +350,7 @@ class ShowGroup(show.ShowOne): parser.add_argument( 'group', metavar='<group>', - help='Name or ID of group to display', + help='Group to display (name or ID)', ) parser.add_argument( '--domain', diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 2c2d408e..28eb4277 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -21,7 +21,7 @@ import six from cliff import command from cliff import lister from cliff import show -from keystoneclient.openstack.common.apiclient import exceptions as ksc_exc +from keystoneclient import exceptions as ksc_exc from openstackclient.common import parseractions from openstackclient.common import utils @@ -117,16 +117,17 @@ class CreateProject(show.ShowOne): class DeleteProject(command.Command): - """Delete an existing project""" + """Delete project(s)""" log = logging.getLogger(__name__ + '.DeleteProject') def get_parser(self, prog_name): parser = super(DeleteProject, self).get_parser(prog_name) parser.add_argument( - 'project', + 'projects', metavar='<project>', - help='Project to delete (name or ID)', + nargs="+", + help='Project(s) to delete (name or ID)', ) parser.add_argument( '--domain', @@ -139,16 +140,18 @@ class DeleteProject(command.Command): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity + domain = None if parsed_args.domain: domain = common.find_domain(identity_client, parsed_args.domain) - project = utils.find_resource(identity_client.projects, - parsed_args.project, - domain_id=domain.id) - else: - project = utils.find_resource(identity_client.projects, - parsed_args.project) - - identity_client.projects.delete(project.id) + for project in parsed_args.projects: + if domain is not None: + project_obj = utils.find_resource(identity_client.projects, + project, + domain_id=domain.id) + else: + project_obj = utils.find_resource(identity_client.projects, + project) + identity_client.projects.delete(project_obj.id) return diff --git a/openstackclient/identity/v3/region.py b/openstackclient/identity/v3/region.py index cce3417d..5fb73913 100644 --- a/openstackclient/identity/v3/region.py +++ b/openstackclient/identity/v3/region.py @@ -35,22 +35,22 @@ class CreateRegion(show.ShowOne): # seems like poor UX, we will only support user-defined IDs. parser.add_argument( 'region', - metavar='<region>', - help=_('New region'), + metavar='<region-id>', + help=_('New region ID'), ) parser.add_argument( '--parent-region', - metavar='<parent-region>', - help=_('The parent region of new region'), + metavar='<region-id>', + help=_('Parent region ID'), ) parser.add_argument( '--description', - metavar='<region-description>', + metavar='<description>', help=_('New region description'), ) parser.add_argument( '--url', - metavar='<region-url>', + metavar='<url>', help=_('New region url'), ) @@ -82,8 +82,8 @@ class DeleteRegion(command.Command): parser = super(DeleteRegion, self).get_parser(prog_name) parser.add_argument( 'region', - metavar='<region>', - help=_('Region to delete'), + metavar='<region-id>', + help=_('Region ID to delete'), ) return parser @@ -104,8 +104,8 @@ class ListRegion(lister.Lister): parser = super(ListRegion, self).get_parser(prog_name) parser.add_argument( '--parent-region', - metavar='<parent-region>', - help=_('Filter by parent region'), + metavar='<region-id>', + help=_('Filter by parent region ID'), ) return parser @@ -137,22 +137,22 @@ class SetRegion(command.Command): parser = super(SetRegion, self).get_parser(prog_name) parser.add_argument( 'region', - metavar='<region>', - help=_('Region to change'), + metavar='<region-id>', + help=_('Region ID to modify'), ) parser.add_argument( '--parent-region', - metavar='<parent-region>', - help=_('New parent region of the region'), + metavar='<region-id>', + help=_('New parent region ID'), ) parser.add_argument( '--description', - metavar='<region-description>', + metavar='<description>', help=_('New region description'), ) parser.add_argument( '--url', - metavar='<region-url>', + metavar='<url>', help=_('New region url'), ) return parser @@ -179,7 +179,7 @@ class SetRegion(command.Command): class ShowRegion(show.ShowOne): - """Show region""" + """Display region details""" log = logging.getLogger(__name__ + '.ShowRegion') @@ -187,8 +187,8 @@ class ShowRegion(show.ShowOne): parser = super(ShowRegion, self).get_parser(prog_name) parser.add_argument( 'region', - metavar='<region>', - help=_('Region to display'), + metavar='<region-id>', + help=_('Region ID to display'), ) return parser diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 017ffd76..d680278e 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -22,7 +22,7 @@ import sys from cliff import command from cliff import lister from cliff import show -from keystoneclient.openstack.common.apiclient import exceptions as ksc_exc +from keystoneclient import exceptions as ksc_exc from openstackclient.common import utils from openstackclient.i18n import _ # noqa @@ -177,16 +177,17 @@ class CreateRole(show.ShowOne): class DeleteRole(command.Command): - """Delete an existing role""" + """Delete role(s)""" log = logging.getLogger(__name__ + '.DeleteRole') def get_parser(self, prog_name): parser = super(DeleteRole, self).get_parser(prog_name) parser.add_argument( - 'role', + 'roles', metavar='<role>', - help='Role to delete (name or ID)', + nargs="+", + help='Role(s) to delete (name or ID)', ) return parser @@ -194,12 +195,12 @@ class DeleteRole(command.Command): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity - role = utils.find_resource( - identity_client.roles, - parsed_args.role, - ) - - identity_client.roles.delete(role.id) + for role in parsed_args.roles: + role_obj = utils.find_resource( + identity_client.roles, + role, + ) + identity_client.roles.delete(role_obj.id) return diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py index 5cc97e8d..f053b608 100644 --- a/openstackclient/identity/v3/role_assignment.py +++ b/openstackclient/identity/v3/role_assignment.py @@ -21,7 +21,7 @@ from openstackclient.common import utils class ListRoleAssignment(lister.Lister): - """Lists role assignments according to the given filters""" + """List role assignments""" log = logging.getLogger(__name__ + '.ListRoleAssignment') @@ -36,29 +36,29 @@ class ListRoleAssignment(lister.Lister): parser.add_argument( '--role', metavar='<role>', - help='Name or ID of role to filter', + help='Role to filter (name or ID)', ) user_or_group = parser.add_mutually_exclusive_group() user_or_group.add_argument( '--user', metavar='<user>', - help='Name or ID of user to filter', + help='User to filter (name or ID)', ) user_or_group.add_argument( '--group', metavar='<group>', - help='Name or ID of group to filter', + help='Group to filter (name or ID)', ) domain_or_project = parser.add_mutually_exclusive_group() domain_or_project.add_argument( '--domain', metavar='<domain>', - help='Name or ID of domain to filter', + help='Domain to filter (name or ID)', ) domain_or_project.add_argument( '--project', metavar='<project>', - help='Name or ID of project to filter', + help='Project to filter (name or ID)', ) return parser diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index 4f622269..f4c5d426 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -43,6 +43,11 @@ class CreateService(show.ShowOne): metavar='<service-name>', help='New service name', ) + parser.add_argument( + '--description', + metavar='<description>', + help='New service description', + ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', @@ -67,6 +72,7 @@ class CreateService(show.ShowOne): service = identity_client.services.create( name=parsed_args.name, type=parsed_args.type, + description=parsed_args.description, enabled=enabled, ) @@ -137,6 +143,11 @@ class SetService(command.Command): metavar='<service-name>', help='New service name', ) + parser.add_argument( + '--description', + metavar='<description>', + help='New service description', + ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', @@ -156,6 +167,7 @@ class SetService(command.Command): if (not parsed_args.name and not parsed_args.type + and not parsed_args.description and not parsed_args.enable and not parsed_args.disable): return @@ -167,6 +179,8 @@ class SetService(command.Command): kwargs['type'] = parsed_args.type if parsed_args.name: kwargs['name'] = parsed_args.name + if parsed_args.description: + kwargs['description'] = parsed_args.description if parsed_args.enable: kwargs['enabled'] = True if parsed_args.disable: diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index d7ccff53..a60c8c83 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -22,7 +22,7 @@ import six from cliff import command from cliff import lister from cliff import show -from keystoneclient.openstack.common.apiclient import exceptions as ksc_exc +from keystoneclient import exceptions as ksc_exc from openstackclient.common import utils from openstackclient.i18n import _ # noqa @@ -138,16 +138,17 @@ class CreateUser(show.ShowOne): class DeleteUser(command.Command): - """Delete user""" + """Delete user(s)""" log = logging.getLogger(__name__ + '.DeleteUser') def get_parser(self, prog_name): parser = super(DeleteUser, self).get_parser(prog_name) parser.add_argument( - 'user', + 'users', metavar='<user>', - help='User to delete (name or ID)', + nargs="+", + help='User(s) to delete (name or ID)', ) parser.add_argument( '--domain', @@ -160,16 +161,18 @@ class DeleteUser(command.Command): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity + domain = None if parsed_args.domain: domain = common.find_domain(identity_client, parsed_args.domain) - user = utils.find_resource(identity_client.users, - parsed_args.user, - domain_id=domain.id) - else: - user = utils.find_resource(identity_client.users, - parsed_args.user) - - identity_client.users.delete(user.id) + for user in parsed_args.users: + if domain is not None: + user_obj = utils.find_resource(identity_client.users, + user, + domain_id=domain.id) + else: + user_obj = utils.find_resource(identity_client.users, + user) + identity_client.users.delete(user_obj.id) return diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 32dd388c..ca1eead4 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -262,16 +262,17 @@ class CreateImage(show.ShowOne): class DeleteImage(command.Command): - """Delete an image""" + """Delete image(s)""" log = logging.getLogger(__name__ + ".DeleteImage") def get_parser(self, prog_name): parser = super(DeleteImage, self).get_parser(prog_name) parser.add_argument( - "image", + "images", metavar="<image>", - help="Name or ID of image to delete", + nargs="+", + help="Image(s) to delete (name or ID)", ) return parser @@ -279,11 +280,12 @@ class DeleteImage(command.Command): self.log.debug("take_action(%s)", parsed_args) image_client = self.app.client_manager.image - image = utils.find_resource( - image_client.images, - parsed_args.image, - ) - image_client.images.delete(image.id) + for image in parsed_args.images: + image_obj = utils.find_resource( + image_client.images, + image, + ) + image_client.images.delete(image_obj.id) class ListImage(lister.Lister): diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index c12ff11a..63351c6d 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -27,16 +27,17 @@ from openstackclient.common import utils class DeleteImage(command.Command): - """Delete an image""" + """Delete image(s)""" log = logging.getLogger(__name__ + ".DeleteImage") def get_parser(self, prog_name): parser = super(DeleteImage, self).get_parser(prog_name) parser.add_argument( - "image", + "images", metavar="<image>", - help="Name or ID of image to delete", + nargs="+", + help="Image(s) to delete (name or ID)", ) return parser @@ -44,11 +45,12 @@ class DeleteImage(command.Command): self.log.debug("take_action(%s)", parsed_args) image_client = self.app.client_manager.image - image = utils.find_resource( - image_client.images, - parsed_args.image, - ) - image_client.images.delete(image.id) + for image in parsed_args.images: + image_obj = utils.find_resource( + image_client.images, + image, + ) + image_client.images.delete(image_obj.id) class ListImage(lister.Lister): diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index f34666ba..0d68f82d 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -86,26 +86,27 @@ class CreateNetwork(show.ShowOne): class DeleteNetwork(command.Command): - """Delete a network""" + """Delete network(s)""" log = logging.getLogger(__name__ + '.DeleteNetwork') def get_parser(self, prog_name): parser = super(DeleteNetwork, self).get_parser(prog_name) parser.add_argument( - 'identifier', + 'networks', metavar="<network>", - help=("Name or identifier of network to delete") + nargs="+", + help=("Network(s) to delete (name or ID)") ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.network - _id = common.find(client, 'network', 'networks', - parsed_args.identifier) delete_method = getattr(client, "delete_network") - delete_method(_id) + for network in parsed_args.networks: + _id = common.find(client, 'network', 'networks', network) + delete_method(_id) return diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py index ead3df45..b75888e4 100644 --- a/openstackclient/object/v1/container.py +++ b/openstackclient/object/v1/container.py @@ -27,7 +27,7 @@ from openstackclient.common import utils class CreateContainer(lister.Lister): - """Create a container""" + """Create new container""" log = logging.getLogger(__name__ + '.CreateContainer') @@ -35,9 +35,9 @@ class CreateContainer(lister.Lister): parser = super(CreateContainer, self).get_parser(prog_name) parser.add_argument( 'containers', - metavar='<container>', + metavar='<container-name>', nargs="+", - help='Container name(s) to create', + help='New container name(s)', ) return parser @@ -60,7 +60,7 @@ class CreateContainer(lister.Lister): class DeleteContainer(command.Command): - """Delete a container""" + """Delete container""" log = logging.getLogger(__name__ + '.DeleteContainer') @@ -70,7 +70,7 @@ class DeleteContainer(command.Command): 'containers', metavar='<container>', nargs="+", - help='Container name(s) to delete', + help='Container(s) to delete', ) return parser @@ -157,7 +157,7 @@ class ListContainer(lister.Lister): class SaveContainer(command.Command): - """Save the contents of a container locally""" + """Save container contents locally""" log = logging.getLogger(__name__ + ".SaveContainer") @@ -166,7 +166,7 @@ class SaveContainer(command.Command): parser.add_argument( 'container', metavar='<container>', - help='Container name to save', + help='Container to save', ) return parser @@ -179,7 +179,7 @@ class SaveContainer(command.Command): class ShowContainer(show.ShowOne): - """Show container information""" + """Show container details""" log = logging.getLogger(__name__ + '.ShowContainer') @@ -188,7 +188,7 @@ class ShowContainer(show.ShowOne): parser.add_argument( 'container', metavar='<container>', - help='Container name to display', + help='Container to display', ) return parser diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py index cbe9da2f..781dd047 100644 --- a/openstackclient/object/v1/object.py +++ b/openstackclient/object/v1/object.py @@ -27,7 +27,7 @@ from openstackclient.common import utils class CreateObject(lister.Lister): - """Upload an object to a container""" + """Upload object to container""" log = logging.getLogger(__name__ + '.CreateObject') @@ -36,13 +36,13 @@ class CreateObject(lister.Lister): parser.add_argument( 'container', metavar='<container>', - help='Container to store new object', + help='Container for new object', ) parser.add_argument( 'objects', - metavar='<object-name>', + metavar='<filename>', nargs="+", - help='Local path of object(s) to upload', + help='Local filename(s) to upload', ) return parser @@ -66,7 +66,7 @@ class CreateObject(lister.Lister): class DeleteObject(command.Command): - """Delete an object within a container""" + """Delete object from container""" log = logging.getLogger(__name__ + '.DeleteObject') @@ -75,11 +75,11 @@ class DeleteObject(command.Command): parser.add_argument( 'container', metavar='<container>', - help='Container that stores the object to delete', + help='Delete object(s) from <container>', ) parser.add_argument( 'objects', - metavar='<object-name>', + metavar='<object>', nargs="+", help='Object(s) to delete', ) @@ -104,8 +104,8 @@ class ListObject(lister.Lister): parser = super(ListObject, self).get_parser(prog_name) parser.add_argument( "container", - metavar="<container-name>", - help="List contents of container-name", + metavar="<container>", + help="Container to list", ) parser.add_argument( "--prefix", @@ -188,7 +188,7 @@ class ListObject(lister.Lister): class SaveObject(command.Command): - """Save an object locally""" + """Save object locally""" log = logging.getLogger(__name__ + ".SaveObject") @@ -197,17 +197,17 @@ class SaveObject(command.Command): parser.add_argument( "--file", metavar="<filename>", - help="Downloaded object filename [defaults to object name]", + help="Destination filename (defaults to object name)", ) parser.add_argument( 'container', metavar='<container>', - help='Container name that has the object', + help='Download <object> from <container>', ) parser.add_argument( "object", metavar="<object>", - help="Name of the object to save", + help="Object to save", ) return parser @@ -222,7 +222,7 @@ class SaveObject(command.Command): class ShowObject(show.ShowOne): - """Show object information""" + """Show object details""" log = logging.getLogger(__name__ + '.ShowObject') @@ -231,12 +231,12 @@ class ShowObject(show.ShowOne): parser.add_argument( 'container', metavar='<container>', - help='Container name for object to display', + help='Display <object> from <container>', ) parser.add_argument( 'object', metavar='<object>', - help='Object name to display', + help='Object to display', ) return parser diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 19c13440..d51beef3 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -232,7 +232,7 @@ class TestServerDelete(TestServer): compute_fakes.server_id, ] verifylist = [ - ('server', compute_fakes.server_id), + ('servers', [compute_fakes.server_id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/identity/v2_0/test_project.py b/openstackclient/tests/identity/v2_0/test_project.py index 833b5d84..0c5ef77f 100644 --- a/openstackclient/tests/identity/v2_0/test_project.py +++ b/openstackclient/tests/identity/v2_0/test_project.py @@ -15,7 +15,7 @@ import copy -from keystoneclient.openstack.common.apiclient import exceptions as ksc_exc +from keystoneclient import exceptions as ksc_exc from openstackclient.identity.v2_0 import project from openstackclient.tests import fakes @@ -326,7 +326,7 @@ class TestProjectDelete(TestProject): identity_fakes.project_id, ] verifylist = [ - ('project', identity_fakes.project_id), + ('projects', [identity_fakes.project_id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/identity/v2_0/test_role.py b/openstackclient/tests/identity/v2_0/test_role.py index 67467747..2e3a2863 100644 --- a/openstackclient/tests/identity/v2_0/test_role.py +++ b/openstackclient/tests/identity/v2_0/test_role.py @@ -16,7 +16,7 @@ import copy import mock -from keystoneclient.openstack.common.apiclient import exceptions as ksc_exc +from keystoneclient import exceptions as ksc_exc from openstackclient.common import exceptions from openstackclient.identity.v2_0 import role @@ -234,7 +234,7 @@ class TestRoleDelete(TestRole): identity_fakes.role_name, ] verifylist = [ - ('role', identity_fakes.role_name), + ('roles', [identity_fakes.role_name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/identity/v2_0/test_user.py b/openstackclient/tests/identity/v2_0/test_user.py index a025dd7d..ccdf240e 100644 --- a/openstackclient/tests/identity/v2_0/test_user.py +++ b/openstackclient/tests/identity/v2_0/test_user.py @@ -16,7 +16,7 @@ import copy import mock -from keystoneclient.openstack.common.apiclient import exceptions as ksc_exc +from keystoneclient import exceptions as ksc_exc from openstackclient.identity.v2_0 import user from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes @@ -443,7 +443,7 @@ class TestUserDelete(TestUser): identity_fakes.user_id, ] verifylist = [ - ('user', identity_fakes.user_id), + ('users', [identity_fakes.user_id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index 88716e97..3afb0cd9 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -147,11 +147,13 @@ ROLE = { service_id = 's-123' service_name = 'Texaco' service_type = 'gas' +service_description = 'oil brand' SERVICE = { 'id': service_id, 'name': service_name, 'type': service_type, + 'description': service_description, 'enabled': True, 'links': base_url + 'services/' + service_id, } diff --git a/openstackclient/tests/identity/v3/test_domain.py b/openstackclient/tests/identity/v3/test_domain.py index 8dad5bcc..cfec10e7 100644 --- a/openstackclient/tests/identity/v3/test_domain.py +++ b/openstackclient/tests/identity/v3/test_domain.py @@ -46,7 +46,6 @@ class TestDomainCreate(TestDomain): identity_fakes.domain_name, ] verifylist = [ - ('enabled', True), ('name', identity_fakes.domain_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -81,7 +80,6 @@ class TestDomainCreate(TestDomain): ] verifylist = [ ('description', 'new desc'), - ('enabled', True), ('name', identity_fakes.domain_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -115,7 +113,7 @@ class TestDomainCreate(TestDomain): identity_fakes.domain_name, ] verifylist = [ - ('enabled', True), + ('enable', True), ('name', identity_fakes.domain_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -149,7 +147,7 @@ class TestDomainCreate(TestDomain): identity_fakes.domain_name, ] verifylist = [ - ('enabled', False), + ('disable', True), ('name', identity_fakes.domain_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -333,7 +331,7 @@ class TestDomainSet(TestDomain): identity_fakes.domain_id, ] verifylist = [ - ('enabled', True), + ('enable', True), ('domain', identity_fakes.domain_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -356,7 +354,7 @@ class TestDomainSet(TestDomain): identity_fakes.domain_id, ] verifylist = [ - ('disabled', True), + ('disable', True), ('domain', identity_fakes.domain_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/identity/v3/test_project.py b/openstackclient/tests/identity/v3/test_project.py index 1060a277..d16f9732 100644 --- a/openstackclient/tests/identity/v3/test_project.py +++ b/openstackclient/tests/identity/v3/test_project.py @@ -353,7 +353,7 @@ class TestProjectDelete(TestProject): identity_fakes.project_id, ] verifylist = [ - ('project', identity_fakes.project_id), + ('projects', [identity_fakes.project_id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/identity/v3/test_role.py b/openstackclient/tests/identity/v3/test_role.py index 3d2a402b..1a9e6aa7 100644 --- a/openstackclient/tests/identity/v3/test_role.py +++ b/openstackclient/tests/identity/v3/test_role.py @@ -271,7 +271,7 @@ class TestRoleDelete(TestRole): identity_fakes.role_name, ] verifylist = [ - ('role', identity_fakes.role_name), + ('roles', [identity_fakes.role_name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/identity/v3/test_service.py b/openstackclient/tests/identity/v3/test_service.py index 57db77b1..5e4dc585 100644 --- a/openstackclient/tests/identity/v3/test_service.py +++ b/openstackclient/tests/identity/v3/test_service.py @@ -51,6 +51,7 @@ class TestServiceCreate(TestService): ] verifylist = [ ('name', identity_fakes.service_name), + ('description', None), ('enable', False), ('disable', False), ('type', identity_fakes.service_type), @@ -64,12 +65,50 @@ class TestServiceCreate(TestService): self.services_mock.create.assert_called_with( name=identity_fakes.service_name, type=identity_fakes.service_type, + description=None, enabled=True, ) - collist = ('enabled', 'id', 'name', 'type') + collist = ('description', 'enabled', 'id', 'name', 'type') self.assertEqual(columns, collist) datalist = ( + identity_fakes.service_description, + True, + identity_fakes.service_id, + identity_fakes.service_name, + identity_fakes.service_type, + ) + self.assertEqual(data, datalist) + + def test_service_create_description(self): + arglist = [ + '--description', identity_fakes.service_description, + identity_fakes.service_type, + ] + verifylist = [ + ('name', None), + ('description', identity_fakes.service_description), + ('enable', False), + ('disable', False), + ('type', identity_fakes.service_type), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # ServiceManager.create(name=, type=, enabled=, **kwargs) + self.services_mock.create.assert_called_with( + name=None, + type=identity_fakes.service_type, + description=identity_fakes.service_description, + enabled=True, + ) + + collist = ('description', 'enabled', 'id', 'name', 'type') + self.assertEqual(columns, collist) + datalist = ( + identity_fakes.service_description, True, identity_fakes.service_id, identity_fakes.service_name, @@ -84,6 +123,7 @@ class TestServiceCreate(TestService): ] verifylist = [ ('name', None), + ('description', None), ('enable', True), ('disable', False), ('type', identity_fakes.service_type), @@ -97,12 +137,14 @@ class TestServiceCreate(TestService): self.services_mock.create.assert_called_with( name=None, type=identity_fakes.service_type, + description=None, enabled=True, ) - collist = ('enabled', 'id', 'name', 'type') + collist = ('description', 'enabled', 'id', 'name', 'type') self.assertEqual(columns, collist) datalist = ( + identity_fakes.service_description, True, identity_fakes.service_id, identity_fakes.service_name, @@ -117,6 +159,7 @@ class TestServiceCreate(TestService): ] verifylist = [ ('name', None), + ('description', None), ('enable', False), ('disable', True), ('type', identity_fakes.service_type), @@ -130,12 +173,14 @@ class TestServiceCreate(TestService): self.services_mock.create.assert_called_with( name=None, type=identity_fakes.service_type, + description=None, enabled=False, ) - collist = ('enabled', 'id', 'name', 'type') + collist = ('description', 'enabled', 'id', 'name', 'type') self.assertEqual(columns, collist) datalist = ( + identity_fakes.service_description, True, identity_fakes.service_id, identity_fakes.service_name, @@ -239,6 +284,7 @@ class TestServiceSet(TestService): verifylist = [ ('type', None), ('name', None), + ('description', None), ('enable', False), ('disable', False), ('service', identity_fakes.service_name), @@ -256,6 +302,7 @@ class TestServiceSet(TestService): verifylist = [ ('type', identity_fakes.service_type), ('name', None), + ('description', None), ('enable', False), ('disable', False), ('service', identity_fakes.service_name), @@ -283,6 +330,7 @@ class TestServiceSet(TestService): verifylist = [ ('type', None), ('name', identity_fakes.service_name), + ('description', None), ('enable', False), ('disable', False), ('service', identity_fakes.service_name), @@ -302,6 +350,34 @@ class TestServiceSet(TestService): **kwargs ) + def test_service_set_description(self): + arglist = [ + '--description', identity_fakes.service_description, + identity_fakes.service_name, + ] + verifylist = [ + ('type', None), + ('name', None), + ('description', identity_fakes.service_description), + ('enable', False), + ('disable', False), + ('service', identity_fakes.service_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.run(parsed_args) + self.assertEqual(result, 0) + + # Set expected values + kwargs = { + 'description': identity_fakes.service_description, + } + # ServiceManager.update(service, name=, type=, enabled=, **kwargs) + self.services_mock.update.assert_called_with( + identity_fakes.service_id, + **kwargs + ) + def test_service_set_enable(self): arglist = [ '--enable', @@ -310,6 +386,7 @@ class TestServiceSet(TestService): verifylist = [ ('type', None), ('name', None), + ('description', None), ('enable', True), ('disable', False), ('service', identity_fakes.service_name), @@ -337,6 +414,7 @@ class TestServiceSet(TestService): verifylist = [ ('type', None), ('name', None), + ('description', None), ('enable', False), ('disable', True), ('service', identity_fakes.service_name), @@ -388,9 +466,10 @@ class TestServiceShow(TestService): identity_fakes.service_name, ) - collist = ('enabled', 'id', 'name', 'type') + collist = ('description', 'enabled', 'id', 'name', 'type') self.assertEqual(columns, collist) datalist = ( + identity_fakes.service_description, True, identity_fakes.service_id, identity_fakes.service_name, diff --git a/openstackclient/tests/identity/v3/test_user.py b/openstackclient/tests/identity/v3/test_user.py index af58b230..dd517e19 100644 --- a/openstackclient/tests/identity/v3/test_user.py +++ b/openstackclient/tests/identity/v3/test_user.py @@ -469,7 +469,7 @@ class TestUserDelete(TestUser): identity_fakes.user_id, ] verifylist = [ - ('user', identity_fakes.user_id), + ('users', [identity_fakes.user_id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py index a0566930..4d21ac48 100644 --- a/openstackclient/tests/image/v1/test_image.py +++ b/openstackclient/tests/image/v1/test_image.py @@ -288,7 +288,7 @@ class TestImageDelete(TestImage): image_fakes.image_id, ] verifylist = [ - ('image', image_fakes.image_id), + ('images', [image_fakes.image_id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 3e9eeebb..bc61a89f 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -51,7 +51,7 @@ class TestImageDelete(TestImage): image_fakes.image_id, ] verifylist = [ - ('image', image_fakes.image_id), + ('images', [image_fakes.image_id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 468db5e0..b893229f 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -120,7 +120,7 @@ class TestDeleteNetwork(common.TestNetworkBase): FAKE_NAME, ] verifylist = [ - ('identifier', FAKE_NAME), + ('networks', [FAKE_NAME]), ] lister = mock.Mock(return_value={RESOURCES: [RECORD]}) self.app.client_manager.network.list_networks = lister diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py index 992fa7be..8c16ba25 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/backup.py @@ -73,25 +73,27 @@ class CreateBackup(show.ShowOne): class DeleteBackup(command.Command): - """Delete backup command""" + """Delete backup(s)""" log = logging.getLogger(__name__ + '.DeleteBackup') def get_parser(self, prog_name): parser = super(DeleteBackup, self).get_parser(prog_name) parser.add_argument( - 'backup', + 'backups', metavar='<backup>', - help='Name or ID of backup to delete', + nargs="+", + help='Backup(s) to delete (name or ID)', ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume - backup_id = utils.find_resource(volume_client.backups, - parsed_args.backup).id - volume_client.backups.delete(backup_id) + for backup in parsed_args.backups: + backup_id = utils.find_resource(volume_client.backups, + backup).id + volume_client.backups.delete(backup_id) return diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index 9cc3c4c1..c9e1baca 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -74,25 +74,27 @@ class CreateSnapshot(show.ShowOne): class DeleteSnapshot(command.Command): - """Delete snapshot command""" + """Delete snapshot(s)""" log = logging.getLogger(__name__ + '.DeleteSnapshot') def get_parser(self, prog_name): parser = super(DeleteSnapshot, self).get_parser(prog_name) parser.add_argument( - 'snapshot', + 'snapshots', metavar='<snapshot>', - help='Name or ID of snapshot to delete', + nargs="+", + help='Snapshot(s) to delete (name or ID)', ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume - snapshot_id = utils.find_resource(volume_client.volume_snapshots, - parsed_args.snapshot).id - volume_client.volume_snapshots.delete(snapshot_id) + for snapshot in parsed_args.snapshots: + snapshot_id = utils.find_resource(volume_client.volume_snapshots, + snapshot).id + volume_client.volume_snapshots.delete(snapshot_id) return diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 84c216d3..d82f6b4b 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -155,35 +155,37 @@ class CreateVolume(show.ShowOne): class DeleteVolume(command.Command): - """Delete a volume""" + """Delete volume(s)""" log = logging.getLogger(__name__ + '.DeleteVolume') def get_parser(self, prog_name): parser = super(DeleteVolume, self).get_parser(prog_name) parser.add_argument( - 'volume', + 'volumes', metavar='<volume>', - help='Volume to delete (name or ID)', + nargs="+", + help='Volume(s) to delete (name or ID)', ) parser.add_argument( '--force', dest='force', action='store_true', default=False, - help='Attempt forced removal of a volume, regardless of state', + help='Attempt forced removal of volume(s), regardless of state', ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume - volume = utils.find_resource( - volume_client.volumes, parsed_args.volume) - if parsed_args.force: - volume_client.volumes.force_delete(volume.id) - else: - volume_client.volumes.delete(volume.id) + for volume in parsed_args.volumes: + volume_obj = utils.find_resource( + volume_client.volumes, volume) + if parsed_args.force: + volume_client.volumes.force_delete(volume_obj.id) + else: + volume_client.volumes.delete(volume_obj.id) return @@ -221,6 +223,25 @@ class ListVolume(lister.Lister): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + compute_client = self.app.client_manager.compute + + def _format_attach(attachments): + """Return a formatted string of a volume's attached instances + + :param volume: a volume.attachments field + :rtype: a string of formatted instances + """ + + msg = '' + for attachment in attachments: + server = attachment['server_id'] + if server in server_cache.keys(): + server = server_cache[server].name + device = attachment['device'] + msg += 'Attached to %s on %s ' % (server, device) + return msg + if parsed_args.long: columns = ( 'ID', @@ -229,7 +250,7 @@ class ListVolume(lister.Lister): 'Size', 'Volume Type', 'Bootable', - 'Attached to', + 'Attachments', 'Metadata', ) column_headers = ( @@ -239,7 +260,7 @@ class ListVolume(lister.Lister): 'Size', 'Type', 'Bootable', - 'Attached', + 'Attached to', 'Properties', ) else: @@ -248,28 +269,38 @@ class ListVolume(lister.Lister): 'Display Name', 'Status', 'Size', - 'Attached to', + 'Attachments', ) column_headers = ( 'ID', 'Display Name', 'Status', 'Size', - 'Attached', + 'Attached to', ) + + # Cache the server list + server_cache = {} + try: + for s in compute_client.servers.list(): + server_cache[s.id] = s + except Exception: + # Just forget it if there's any trouble + pass + search_opts = { 'all_tenants': parsed_args.all_projects, 'display_name': parsed_args.name, 'status': parsed_args.status, } - volume_client = self.app.client_manager.volume data = volume_client.volumes.list(search_opts=search_opts) return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Metadata': utils.format_dict}, + formatters={'Metadata': utils.format_dict, + 'Attachments': _format_attach}, ) for s in data)) |
