From 2a1c94a75375dec5bd2a8a6dace98b411d2ab29e Mon Sep 17 00:00:00 2001 From: jakedahn Date: Tue, 21 Feb 2012 15:02:41 -0800 Subject: [PATCH] Improve usability of CLI. * Fixes bug #936422 * Fixes bug #932223 * Depends on bcwaldon's review: https://review.openstack.org/#change,4305 * This review proposes making changes outlined in this spreadsheet: https://docs.google.com/spreadsheet/ccc?key=0Ak6TA47h_6fwdGZwRE5WWEJBdEhnckpMTG5RcWFjY3c#gid=0 This cleans up the CLI, normalizing commands and arguments, correcting optional and required arguments and flags. * included https://review.openstack.org/4270 here per Brian's request Note that some commands have changed names to conform to noun-verb form: user-update-password -> user-password-update add-user-role -> user-role-add remove-user-role -> user-role-remove ec2-create-credentials -> ec2-credentials-create ec2-list-credentials -> ec2-credentials-list ec2-delete-credentials -> ec2-credentials-delete token -> token-get Change-Id: I8128fa105a1b8002199211f9e475b1a7a6229b8c --- README.rst | 70 ++++++--- keystoneclient/shell.py | 30 ++-- keystoneclient/utils.py | 4 + keystoneclient/v2_0/shell.py | 266 +++++++++++++++++------------------ keystoneclient/v2_0/users.py | 2 +- 5 files changed, 203 insertions(+), 169 deletions(-) diff --git a/README.rst b/README.rst index 9f26cf940..d14cbc41a 100644 --- a/README.rst +++ b/README.rst @@ -66,28 +66,66 @@ can specify the one you want with ``--region_name`` (or You'll find complete documentation on the shell by running ``keystone help``:: - usage: keystone [--username user] [--password password] - [--tenant_name tenant] [--auth_url URL] - ... + usage: keystone [--username USERNAME] [--password PASSWORD] + [--tenant_name TENANT_NAME] [--tenant_id TENANT_ID] + [--auth_url AUTH_URL] [--region_name REGION_NAME] + [--identity_api_version IDENTITY_API_VERSION] [--token TOKEN] + [--endpoint ENDPOINT] + ... Command-line interface to the OpenStack Keystone API. Positional arguments: - add-fixed-ip Add a new fixed IP address to a servers network. - + catalog List service catalog, possibly filtered by service. + ec2-credentials-create + Create EC2-compatibile credentials for user per tenant + ec2-credentials-delete + Delete EC2-compatibile credentials + ec2-credentials-list + List EC2-compatibile credentials for a user + endpoint-get Find endpoint filtered by a specific attribute or + service type + role-create Create new role + role-delete Delete role + role-get Display role details + role-list List all available roles + service-create Add service to Service Catalog + service-delete Delete service from Service Catalog + service-get Display service from Service Catalog + service-list List all services in Service Catalog + tenant-create Create new tenant + tenant-delete Delete tenant + tenant-get Display tenant details + tenant-list List all tenants + tenant-update Update tenant name, description, enabled status + token-get Display the current user token + user-create Create new user + user-delete Delete user + user-list List users + user-password-update + Update user password + user-role-add Add role to user + user-role-remove Remove role from user + user-update Update user's name, email, and enabled status + discover Discover Keystone servers and show authentication + protocols and + help Display help about this program or one of its + subcommands. Optional arguments: - --username USER Defaults to env[OS_USERNAME]. - --password PASSWORD Defaults to env[OS_PASSWORD]. - --tenant_name TENANT_NAME Defaults to env[OS_TENANT_NAME]. - --tenant_id TENANT_ID Defaults to env[OS_TENANT_ID]. - --url AUTH_URL Defaults to env[OS_AUTH_URL]. + --username USERNAME Defaults to env[OS_USERNAME] + --password PASSWORD Defaults to env[OS_PASSWORD] + --tenant_name TENANT_NAME + Defaults to env[OS_TENANT_NAME] + --tenant_id TENANT_ID + Defaults to env[OS_TENANT_ID] + --auth_url AUTH_URL Defaults to env[OS_AUTH_URL] + --region_name REGION_NAME + Defaults to env[OS_REGION_NAME] --identity_api_version IDENTITY_API_VERSION - Defaults to env[OS_IDENTITY_API_VERSION] or 2.0. - --region_name NAME The region name in the Keystone Service - Catalog to use after authentication. - Defaults to env[KEYSTONE_REGION_NAME] or the - first item in the list returned. + Defaults to env[OS_IDENTITY_API_VERSION] or 2.0 + --token TOKEN Defaults to env[SERVICE_TOKEN] + --endpoint ENDPOINT Defaults to env[SERVICE_ENDPOINT] - See "keystone help COMMAND" for help on a specific command. +See "keystone help COMMAND" for help on a specific command. diff --git a/keystoneclient/shell.py b/keystoneclient/shell.py index c7f668039..98f7232ab 100644 --- a/keystoneclient/shell.py +++ b/keystoneclient/shell.py @@ -66,41 +66,41 @@ class OpenStackIdentityShell(object): action='store_true', help=argparse.SUPPRESS) - parser.add_argument('--token', - default=env('SERVICE_TOKEN'), - help='Defaults to env[SERVICE_TOKEN].') - - parser.add_argument('--endpoint', - default=env('SERVICE_ENDPOINT'), - help='Defaults to env[SERVICE_ENDPOINT].') - parser.add_argument('--username', default=env('OS_USERNAME'), - help='Defaults to env[OS_USERNAME].') + help='Defaults to env[OS_USERNAME]') parser.add_argument('--password', default=env('OS_PASSWORD'), - help='Defaults to env[OS_PASSWORD].') + help='Defaults to env[OS_PASSWORD]') parser.add_argument('--tenant_name', default=env('OS_TENANT_NAME'), - help='Defaults to env[OS_TENANT_NAME].') + help='Defaults to env[OS_TENANT_NAME]') parser.add_argument('--tenant_id', default=env('OS_TENANT_ID'), - help='Defaults to env[OS_TENANT_ID].') + help='Defaults to env[OS_TENANT_ID]') parser.add_argument('--auth_url', default=env('OS_AUTH_URL'), - help='Defaults to env[OS_AUTH_URL].') + help='Defaults to env[OS_AUTH_URL]') parser.add_argument('--region_name', default=env('OS_REGION_NAME'), - help='Defaults to env[OS_REGION_NAME].') + help='Defaults to env[OS_REGION_NAME]') parser.add_argument('--identity_api_version', default=env('OS_IDENTITY_API_VERSION', 'KEYSTONE_VERSION'), - help='Defaults to env[OS_IDENTITY_API_VERSION] or 2.0.') + help='Defaults to env[OS_IDENTITY_API_VERSION] or 2.0') + + parser.add_argument('--token', + default=env('SERVICE_TOKEN'), + help='Defaults to env[SERVICE_TOKEN]') + + parser.add_argument('--endpoint', + default=env('SERVICE_ENDPOINT'), + help='Defaults to env[SERVICE_ENDPOINT]') return parser diff --git a/keystoneclient/utils.py b/keystoneclient/utils.py index 2cb385a01..90631e9ed 100644 --- a/keystoneclient/utils.py +++ b/keystoneclient/utils.py @@ -88,3 +88,7 @@ def isunauthenticated(f): set to True, False otherwise. """ return getattr(f, 'unauthenticated', False) + + +def string_to_bool(arg): + return arg.strip().lower() in ('t', 'true', 'yes', '1') diff --git a/keystoneclient/v2_0/shell.py b/keystoneclient/v2_0/shell.py index 80e89adb3..e640163ef 100755 --- a/keystoneclient/v2_0/shell.py +++ b/keystoneclient/v2_0/shell.py @@ -21,108 +21,107 @@ from keystoneclient import utils CLIENT_CLASS = client.Client -@utils.arg('tenant', metavar='', nargs='?', - help='ID of Tenant. (Optional)', default=None) +@utils.arg('tenant', metavar='', nargs='?', default=None, + help='Tenant ID (Optional); lists all users if not specified') def do_user_list(kc, args): + """List users""" users = kc.users.list(tenant_id=args.tenant) - utils.print_list(users, ['id', 'enabled', 'email', 'name', 'tenantId']) + utils.print_list(users, ['id', 'enabled', 'email', 'name']) -@utils.arg('--name', metavar='', nargs='?', - help='Desired username. (unique)') -@utils.arg('--pass', metavar='', nargs='?', - dest='passwd', - help='Desired password.') -@utils.arg('--email', metavar='', nargs='?', - help='Desired email address. (unique)') -@utils.arg('--tenant_id', metavar='', nargs='?', - help='User will join the default tenant as a Member.') -@utils.arg('--enabled', metavar='', nargs='?', default=True, - help='Enable user immediately (Optional, default True)') +@utils.arg('--name', metavar='', required=True, + help='New user name (must be unique)') +@utils.arg('--tenant_id', metavar='', + help='New user default tenant') +@utils.arg('--pass', metavar='', dest='passwd', + help='New user password') +@utils.arg('--email', metavar='', + help='New user email address') +@utils.arg('--enabled', metavar='', default=True, + help='Initial user enabled status (default true)') def do_user_create(kc, args): + """Create new user""" user = kc.users.create(args.name, args.passwd, args.email, tenant_id=args.tenant_id, enabled=args.enabled) utils.print_dict(user._info) -@utils.arg('id', metavar='', help='User ID to update.') -@utils.arg('name', metavar='', nargs='?', - help='New desired user name.') -@utils.arg('email', metavar='', nargs='?', - help='New desired email address.') +@utils.arg('--name', metavar='', + help='Desired new user name') +@utils.arg('--email', metavar='', + help='Desired new email address') +@utils.arg('--enabled', metavar='', + help='Enable or disable user') +@utils.arg('id', metavar='', help='User ID to update') def do_user_update(kc, args): - user = kc.users.update(args.id, name=args.name, email=args.email) - utils.print_dict(user._info) + """Update user's name, email, and enabled status""" + kwargs = {} + if args.name: + kwargs['name'] = args.name + if args.email: + kwargs['email'] = args.email + if args.enabled: + kwargs['enabled'] = utils.string_to_bool(args.enabled) + if not len(kwargs): + print "User not updated, no arguments present." + return -@utils.arg('id', metavar='', nargs='?', help='User ID to enable.') -def do_user_enable(kc, args): try: - kc.users.update_enabled(args.id, True) - print 'User has been enabled.' - except: - print 'Unable to enable user.' + kc.users.update(args.id, **kwargs) + print 'User has been updated.' + except Exception, e: + print 'Unable to update user: %s' % e -@utils.arg('id', metavar='', nargs='?', help='User ID to disable.') -def do_user_disable(kc, args): - try: - kc.users.update_enabled(args.id, False) - print 'User has been disabled.' - except: - print 'Unable to disable user.' +@utils.arg('--pass', metavar='', required=True, + help='Desired new password') +@utils.arg('id', metavar='', help='User ID to update') +def do_user_password_update(kc, args): + """Update user password""" + kc.users.update_password(args.id, args.password) -@utils.arg('id', metavar='', help='User ID to update.') -@utils.arg('password', metavar='', help='New desired password.') -def do_user_update_password(kc, args): - try: - kc.users.update_password(args.id, args.password) - print 'User password has been udpated.' - except: - print 'Unable to update users password.' - - -@utils.arg('id', metavar='', help='User ID to delete.') +@utils.arg('id', metavar='', help='User ID to delete') def do_user_delete(kc, args): - try: - kc.users.delete(args.id) - print 'User has been deleted.' - except: - print 'Unable to delete user.' + """Delete user""" + kc.users.delete(args.id) def do_tenant_list(kc, args): + """List all tenants""" tenants = kc.tenants.list() utils.print_list(tenants, ['id', 'name', 'enabled']) -@utils.arg('id', metavar='', help='Tenant ID to show.') +@utils.arg('id', metavar='', help='Tenant ID to display') def do_tenant_get(kc, args): + """Display tenant details""" tenant = kc.tenants.get(args.id) utils.print_dict(tenant._info) -@utils.arg('--name', metavar='', nargs='?', - help='Desired name of new tenant.') -@utils.arg('--description', metavar='', nargs='?', default=None, - help='Useful description of new tenant (optional, default is None)') -@utils.arg('--enabled', metavar='', nargs='?', default=True, - help='Enable user immediately (Optional, default True)') +@utils.arg('--name', metavar='', required=True, + help='New tenant name (must be unique)') +@utils.arg('--description', metavar='', default=None, + help='Description of new tenant (default is none)') +@utils.arg('--enabled', metavar='', default=True, + help='Initial tenant enabled status (default true)') def do_tenant_create(kc, args): + """Create new tenant""" tenant = kc.tenants.create(args.name, description=args.description, enabled=args.enabled) utils.print_dict(tenant._info) -@utils.arg('--name', metavar='', nargs='?', - help='Desired name of tenant.') -@utils.arg('--description', metavar='', nargs='?', default=None, - help='Desired description of tenant') -@utils.arg('--enabled', metavar='', nargs='?', const=True, - help='Enable/disable tenant') -@utils.arg('id', metavar='', help='Tenant ID to update') +@utils.arg('--name', metavar='', + help='Desired new name of tenant') +@utils.arg('--description', metavar='', default=None, + help='Desired new description of tenant') +@utils.arg('--enabled', metavar='', + help='Enable or disable tenant') +@utils.arg('id', metavar='', help='Tenant ID to update') def do_tenant_update(kc, args): """Update tenant name, description, enabled status""" tenant = kc.tenants.get(args.id) @@ -138,31 +137,24 @@ def do_tenant_update(kc, args): if kwargs == {}: print "Tenant not updated, no arguments present." return - - try: - tenant.update(**kwargs) - print 'Tenant has been updated.' - except Exception, e: - print 'Unable to update tenant: %s' % e + tenant.update(**kwargs) -@utils.arg('id', metavar='', help='Tenant ID to delete') +@utils.arg('id', metavar='', help='Tenant ID to delete') def do_tenant_delete(kc, args): - try: - kc.tenants.delete(args.id) - print 'Tenant has been deleted.' - except: - print 'Unable to delete tenant.' + """Delete tenant""" + kc.tenants.delete(args.id) -@utils.arg('--name', metavar='', - help='Desired name of service. (unique)') -@utils.arg('--type', metavar='', - help='Possible service types: identity, compute, network, \ - image, or object-store.') -@utils.arg('--description', metavar='', nargs='?', - help='Useful description of service.') +@utils.arg('--name', metavar='', required=True, + help='Name of new service (must be unique)') +@utils.arg('--type', metavar='', required=True, + help='Service type (one of: identity, compute, network, ' + 'image, or object-store)') +@utils.arg('--description', metavar='', + help='Description of service') def do_service_create(kc, args): + """Add service to Service Catalog""" service = kc.services.create(args.name, args.type, args.description) @@ -170,98 +162,98 @@ def do_service_create(kc, args): def do_service_list(kc, args): + """List all services in Service Catalog""" services = kc.services.list() utils.print_list(services, ['id', 'name', 'type', 'description']) -@utils.arg('id', metavar='', help='ID of Service to retrieve.') +@utils.arg('id', metavar='', help='Service ID to display') def do_service_get(kc, args): + """Display service from Service Catalog""" service = kc.services.get(args.id) utils.print_dict(service._info) -@utils.arg('id', metavar='', help='ID of Service to delete') +@utils.arg('id', metavar='', help='Service ID to delete') def do_service_delete(kc, args): - try: - kc.services.delete(args.id) - print 'Service has been deleted' - except: - print 'Unable to delete service.' + """Delete service from Service Catalog""" + kc.services.delete(args.id) def do_role_list(kc, args): + """List all available roles""" roles = kc.roles.list() utils.print_list(roles, ['id', 'name']) -@utils.arg('id', metavar='', help='ID of Role to fetch.') +@utils.arg('id', metavar='', help='Role ID to display') def do_role_get(kc, args): + """Display role details""" role = kc.roles.get(args.id) utils.print_dict(role._info) -@utils.arg('--name', metavar='', help='Desired name of new role.') +@utils.arg('--name', metavar='', required=True, + help='Name of new role') def do_role_create(kc, args): + """Create new role""" role = kc.roles.create(args.name) utils.print_dict(role._info) -@utils.arg('id', metavar='', help='ID of Role to delete.') +@utils.arg('id', metavar='', help='Role ID to delete') def do_role_delete(kc, args): - try: - kc.roles.delete(args.id) - print 'Role has been deleted.' - except: - print 'Unable to delete role.' + """Delete role""" + kc.roles.delete(args.id) # TODO(jakedahn): refactor this to allow role, user, and tenant names. -@utils.arg('user_id', metavar='', help='ID of User') -@utils.arg('role_id', metavar='', help='ID of Role') -@utils.arg('tenant', metavar='', - help='ID of Tenant', nargs='?') -def do_add_user_role(kc, args): - kc.roles.add_user_role(args.user_id, args.role_id, args.tenant) +@utils.arg('--user', metavar='', required=True, help='User ID') +@utils.arg('--role', metavar='', required=True, help='Role ID') +@utils.arg('--tenant_id', metavar='', help='Tenant ID') +def do_user_role_add(kc, args): + """Add role to user""" + kc.roles.add_user_role(args.user, args.role, args.tenant_id) # TODO(jakedahn): refactor this to allow role, user, and tenant names. -@utils.arg('user_id', metavar='', help='ID of User') -@utils.arg('role_id', metavar='', help='ID of Role') -@utils.arg('tenant', metavar='', - help='ID of Tenant', nargs='?') -def do_remove_user_role(kc, args): - kc.roles.remove_user_role(args.user_id, args.role_id, args.tenant) +@utils.arg('--user', metavar='', required=True, help='User ID') +@utils.arg('--role', metavar='', required=True, help='Role ID') +@utils.arg('--tenant_id', metavar='', help='Tenant ID') +def do_user_role_remove(kc, args): + """Remove role from user""" + kc.roles.remove_user_role(args.user, args.role, args.tenant_id) -@utils.arg('--tenant_id', metavar='', help='ID of Tenant') -@utils.arg('--user_id', metavar='', help='ID of User') -def do_ec2_create_credentials(kc, args): - credentials = kc.ec2.create(args.user_id, args.tenant_id) +@utils.arg('--user', metavar='', required=True, help='User ID') +@utils.arg('--tenant_id', metavar='', required=True, + help='Tenant ID') +def do_ec2_credentials_create(kc, args): + """Create EC2-compatibile credentials for user per tenant""" + credentials = kc.ec2.create(args.user, args.tenant_id) utils.print_dict(credentials._info) -@utils.arg('user_id', metavar='', help='ID of User') -def do_ec2_list_credentials(kc, args): - credentials = kc.ec2.list(args.user_id) +@utils.arg('--user', metavar='', help='User ID to list') +def do_ec2_credentials_list(kc, args): + """List EC2-compatibile credentials for a user""" + credentials = kc.ec2.list(args.user) for cred in credentials: cred.tenant = kc.tenants.get(cred.tenant_id).name utils.print_list(credentials, ['tenant', 'key', 'secret']) -@utils.arg('user_id', metavar='', help='ID of User') -@utils.arg('key', metavar='', help='Access Key') -def do_ec2_delete_credentials(kc, args): - try: - kc.ec2.delete(args.user_id, args.key) - print 'Deleted EC2 Credentials.' - except: - print 'Unable to delete EC2 Credentials.' +@utils.arg('--user', metavar='', help='User ID') +@utils.arg('--key', metavar='', help='Access Key') +def do_ec2_credentials_delete(kc, args): + """Delete EC2-compatibile credentials""" + kc.ec2.delete(args.user, args.key) -@utils.arg('--service', metavar='', - help='Service type to return', nargs='?', default=None) +@utils.arg('--service', metavar='', default=None, + help='Service type to return') def do_catalog(kc, args): - """List service catalog, possibly filtered by service""" + """List service catalog, possibly filtered by service.""" endpoints = kc.service_catalog.get_endpoints(service_type=args.service) for (service, service_endpoints) in endpoints.iteritems(): if len(service_endpoints) > 0: @@ -270,14 +262,14 @@ def do_catalog(kc, args): utils.print_dict(ep) -@utils.arg('--endpoint_type', metavar='', - help='Endpoint type to select', nargs='?', default='publicURL') -@utils.arg('--service', metavar='', - help='Service type to select', nargs='?', required=True) -@utils.arg('--attr', metavar='', - help='Attribute to match', nargs='?') +@utils.arg('--service', metavar='', required=True, + help='Service type to select') +@utils.arg('--endpoint_type', metavar='', default='publicURL', + help='Endpoint type to select') +@utils.arg('--attr', metavar='', + help='Service attribute to match for selection') @utils.arg('--value', metavar='', - help='Value of attribute to match', nargs='?') + help='Value of attribute to match') def do_endpoint_get(kc, args): """Find endpoint filtered by a specific attribute or service type""" kwargs = { @@ -295,6 +287,6 @@ def do_endpoint_get(kc, args): utils.print_dict({'%s.%s' % (args.service, args.endpoint_type): url}) -def do_token(kc, args): - """Fetch the current user's token""" +def do_token_get(kc, args): + """Display the current user token""" utils.print_dict(kc.service_catalog.get_token()) diff --git a/keystoneclient/v2_0/users.py b/keystoneclient/v2_0/users.py index cd7340f52..9f60c52de 100644 --- a/keystoneclient/v2_0/users.py +++ b/keystoneclient/v2_0/users.py @@ -40,7 +40,7 @@ class UserManager(base.ManagerWithFind): """ Update user data. - Supported arguments include ``name`` and ``email``. + Supported arguments include ``name``, ``email``, and ``enabled``. """ # FIXME(gabriel): "tenantId" seems to be accepted by the API but # fails to actually update the default tenant.