493 lines
18 KiB
Python
493 lines
18 KiB
Python
#!/usr/bin/env python
|
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright 2010 United States Government as represented by the
|
|
# Administrator of the National Aeronautics and Space Administration.
|
|
# Copyright 2011 OpenStack LLC.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
"""
|
|
Keystone Identity Server - CLI Management Interface
|
|
"""
|
|
|
|
import sys
|
|
import logging
|
|
import optparse # deprecated in 2.7, in favor of argparse
|
|
|
|
from keystone import version
|
|
import keystone.backends as db
|
|
from keystone.backends.sqlalchemy import migration
|
|
from keystone.common import config
|
|
from keystone.logic.types import fault
|
|
from keystone.manage import api
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# CLI feature set
|
|
OBJECTS = ['user', 'tenant', 'role', 'service',
|
|
'endpointTemplates', 'token', 'endpoint', 'credentials', 'database']
|
|
ACTIONS = ['add', 'list', 'disable', 'delete', 'grant', 'revoke',
|
|
'sync', 'downgrade', 'upgrade', 'version_control', 'version',
|
|
'goto']
|
|
|
|
|
|
# Messages
|
|
OBJECT_NOT_SPECIFIED = 'No object type specified for first argument'
|
|
ACTION_NOT_SPECIFIED = 'No action specified for second argument'
|
|
ID_NOT_SPECIFIED = 'No ID specified for third argument'
|
|
SUPPORTED_OBJECTS = "Supported objects: %s" % (", ".join(OBJECTS))
|
|
SUPPORTED_ACTIONS = "Supported actions: %s" % (", ".join(ACTIONS))
|
|
ACTION_NOT_SUPPORTED = 'Action not supported for %s'
|
|
|
|
|
|
class RaisingOptionParser(optparse.OptionParser):
|
|
def error(self, msg):
|
|
self.print_usage(sys.stderr)
|
|
raise optparse.OptParseError(msg)
|
|
|
|
|
|
def parse_args(args=None):
|
|
usage = """
|
|
Usage: keystone-manage [options] type action [id [attributes]]
|
|
type : %s
|
|
action : %s
|
|
id : name or id
|
|
attributes : depending on type...
|
|
users : password, tenant
|
|
tokens : user, tenant, expiration
|
|
|
|
role list [tenant] will list roles granted on that tenant
|
|
database [sync | downgrade | upgrade | version_control | version]
|
|
|
|
options
|
|
-c | --config-file : config file to use
|
|
-d | --debug : debug mode
|
|
|
|
Example: keystone-manage user add Admin P@ssw0rd
|
|
""" % (", ".join(OBJECTS), ", ".join(ACTIONS))
|
|
|
|
# Initialize a parser for our configuration paramaters
|
|
parser = RaisingOptionParser(usage, version='%%prog %s'
|
|
% version.version())
|
|
config.add_common_options(parser)
|
|
config.add_log_options(parser)
|
|
|
|
# Parse command-line and load config
|
|
(options, args) = config.parse_options(parser, args)
|
|
_config_file, conf = config.load_paste_config('admin', options, args)
|
|
|
|
config.setup_logging(options, conf)
|
|
|
|
if not args or args[0] != 'database':
|
|
db.configure_backends(conf.global_conf)
|
|
|
|
return args
|
|
|
|
|
|
def get_options(args=None):
|
|
# Initialize a parser for our configuration paramaters
|
|
parser = RaisingOptionParser()
|
|
config.add_common_options(parser)
|
|
config.add_log_options(parser)
|
|
|
|
# Parse command-line and load config
|
|
(options, args) = config.parse_options(parser, list(args))
|
|
|
|
_config_file, conf = config.load_paste_config('admin', options, args)
|
|
conf.global_conf.update(conf.local_conf)
|
|
|
|
return conf.global_conf
|
|
|
|
|
|
def process(*args):
|
|
# Check arguments
|
|
if len(args) == 0:
|
|
raise optparse.OptParseError(OBJECT_NOT_SPECIFIED)
|
|
else:
|
|
object_type = args[0]
|
|
if object_type not in OBJECTS:
|
|
raise optparse.OptParseError(SUPPORTED_OBJECTS)
|
|
|
|
if len(args) == 1:
|
|
raise optparse.OptParseError(ACTION_NOT_SPECIFIED)
|
|
else:
|
|
action = args[1]
|
|
if action not in ACTIONS:
|
|
raise optparse.OptParseError(SUPPORTED_ACTIONS)
|
|
|
|
if action not in ['list', 'sync', 'version_control', 'version']:
|
|
if len(args) == 2:
|
|
raise optparse.OptParseError(ID_NOT_SPECIFIED)
|
|
else:
|
|
object_id = args[2]
|
|
|
|
# Helper functions
|
|
def require_args(args, min, msg):
|
|
"""Ensure there are at least `min` arguments"""
|
|
if len(args) < min:
|
|
raise optparse.OptParseError(msg)
|
|
|
|
optional_arg = (lambda args, x:
|
|
len(args) > x and str(args[x]).strip() or None)
|
|
|
|
if object_type == 'database':
|
|
options = get_options(args)
|
|
|
|
# Execute command
|
|
if (object_type, action) == ('user', 'add'):
|
|
require_args(args, 4, 'No password specified for fourth argument')
|
|
if api.add_user(name=object_id, password=args[3],
|
|
tenant=optional_arg(args, 4)):
|
|
print "SUCCESS: User %s created." % object_id
|
|
|
|
elif (object_type, action) == ('user', 'list'):
|
|
print Table('Users', ['id', 'name', 'enabled', 'tenant'],
|
|
api.list_users())
|
|
|
|
elif (object_type, action) == ('user', 'disable'):
|
|
if api.disable_user(name=object_id):
|
|
print "SUCCESS: User %s disabled." % object_id
|
|
|
|
elif object_type == 'user':
|
|
raise optparse.OptParseError(ACTION_NOT_SUPPORTED % ('users'))
|
|
|
|
elif (object_type, action) == ('tenant', 'add'):
|
|
if api.add_tenant(name=object_id):
|
|
print "SUCCESS: Tenant %s created." % object_id
|
|
|
|
elif (object_type, action) == ('tenant', 'list'):
|
|
print Table('Tenants', ['id', 'name', 'enabled'], api.list_tenants())
|
|
|
|
elif (object_type, action) == ('tenant', 'disable'):
|
|
if api.disable_tenant(name=object_id):
|
|
print "SUCCESS: Tenant %s disabled." % object_id
|
|
|
|
elif object_type == 'tenant':
|
|
raise optparse.OptParseError(ACTION_NOT_SUPPORTED % ('tenants'))
|
|
|
|
elif (object_type, action) == ('role', 'add'):
|
|
if api.add_role(name=object_id, service_name=optional_arg(args, 3)):
|
|
print "SUCCESS: Role %s created successfully." % object_id
|
|
|
|
elif (object_type, action) == ('role', 'list'):
|
|
tenant = optional_arg(args, 2)
|
|
if tenant:
|
|
# print with users
|
|
print Table('Role assignments for tenant %s' % tenant,
|
|
['User', 'Role'],
|
|
api.list_roles(tenant=tenant))
|
|
else:
|
|
# print without tenants
|
|
print Table('Roles', ['id', 'name', 'service_id', 'description'],
|
|
api.list_roles())
|
|
|
|
elif (object_type, action) == ('role', 'grant'):
|
|
require_args(args, 4, "Missing arguments: role grant 'role' 'user' "
|
|
"'tenant (optional)'")
|
|
tenant = optional_arg(args, 4)
|
|
if api.grant_role(object_id, args[3], tenant):
|
|
print("SUCCESS: Granted %s the %s role on %s." %
|
|
(args[3], object_id, tenant))
|
|
|
|
elif object_type == 'role':
|
|
raise optparse.OptParseError(ACTION_NOT_SUPPORTED % ('roles'))
|
|
|
|
elif (object_type, action) == ('endpointTemplates', 'add'):
|
|
require_args(args, 9, "Missing arguments: endpointTemplates add "
|
|
"'region' 'service_name' 'publicURL' 'adminURL' 'internalURL' "
|
|
"'enabled' 'global'")
|
|
version_id = optional_arg(args, 9)
|
|
version_list = optional_arg(args, 10)
|
|
version_info = optional_arg(args, 11)
|
|
if api.add_endpoint_template(region=args[2], service=args[3],
|
|
public_url=args[4], admin_url=args[5], internal_url=args[6],
|
|
enabled=args[7], is_global=args[8],
|
|
version_id=version_id, version_list=version_list,
|
|
version_info=version_info):
|
|
print("SUCCESS: Created EndpointTemplates for %s pointing to %s." %
|
|
(args[3], args[4]))
|
|
|
|
elif (object_type, action) == ('endpointTemplates', 'list'):
|
|
tenant = optional_arg(args, 2)
|
|
if tenant:
|
|
print Table('Endpoints for tenant %s' % tenant,
|
|
['id', 'service', 'region', 'Public URL'],
|
|
api.list_tenant_endpoints(tenant))
|
|
else:
|
|
print Table('All EndpointTemplates', ['id', 'service', 'type',
|
|
'region', 'enabled', 'is_global', 'Public URL',
|
|
'Admin URL'],
|
|
api.list_endpoint_templates())
|
|
|
|
elif (object_type, action) == ('endpoint', 'add'):
|
|
require_args(args, 4, "Missing arguments: endPoint add tenant "
|
|
"endPointTemplate")
|
|
if api.add_endpoint(tenant=args[2], endpoint_template=args[3]):
|
|
print("SUCCESS: Endpoint %s added to tenant %s." %
|
|
(args[3], args[2]))
|
|
|
|
elif object_type == 'endpoint':
|
|
raise optparse.OptParseError(ACTION_NOT_SUPPORTED % ('endpoints'))
|
|
|
|
elif (object_type, action) == ('token', 'add'):
|
|
require_args(args, 6, 'Creating a token requires a token id, user, '
|
|
'tenant, and expiration')
|
|
if api.add_token(token=object_id, user=args[3], tenant=args[4],
|
|
expires=args[5]):
|
|
print "SUCCESS: Token %s created." % (object_id,)
|
|
|
|
elif (object_type, action) == ('token', 'list'):
|
|
print Table('Tokens', ('token', 'user', 'expiration', 'tenant'),
|
|
api.list_tokens())
|
|
|
|
elif (object_type, action) == ('token', 'delete'):
|
|
if api.delete_token(token=object_id):
|
|
print 'SUCCESS: Token %s deleted.' % (object_id,)
|
|
|
|
elif object_type == 'token':
|
|
raise optparse.OptParseError(ACTION_NOT_SUPPORTED % ('tokens'))
|
|
|
|
elif (object_type, action) == ('service', 'add'):
|
|
require_args(args, 4, "Missing arguments: service add <name> " \
|
|
"[type] [desc] [owner_id]"
|
|
"type")
|
|
type = optional_arg(args, 3)
|
|
desc = optional_arg(args, 4)
|
|
owner_id = optional_arg(args, 5)
|
|
|
|
if api.add_service(name=object_id, type=type, desc=desc,
|
|
owner_id=owner_id):
|
|
print "SUCCESS: Service %s created successfully." % (object_id,)
|
|
|
|
elif (object_type, action) == ('service', 'list'):
|
|
print Table('Services',
|
|
('id', 'name', 'type', 'owner_id', 'description'),
|
|
api.list_services())
|
|
|
|
elif object_type == 'service':
|
|
raise optparse.OptParseError(ACTION_NOT_SUPPORTED % ('services'))
|
|
|
|
elif (object_type, action) == ('credentials', 'add'):
|
|
require_args(args, 6, 'Creating a credentials requires a type, key, '
|
|
'secret, and tenant_id (id is user_id)')
|
|
if api.add_credentials(user=object_id, type=args[3], key=args[4],
|
|
secrete=args[5], tenant=optional_arg(args, 6)):
|
|
print "SUCCESS: Credentials %s created." % object_id
|
|
|
|
elif object_type == 'credentials':
|
|
raise optparse.OptParseError(ACTION_NOT_SUPPORTED % ('credentials'))
|
|
|
|
elif (object_type, action) == ('database', 'sync'):
|
|
require_args(args, 1, 'Syncing database requires a version #')
|
|
backend_names = options.get('backends', None)
|
|
if backend_names:
|
|
if 'keystone.backends.sqlalchemy' in backend_names.split(','):
|
|
do_db_sync(options['keystone.backends.sqlalchemy'],
|
|
args)
|
|
else:
|
|
raise optparse.OptParseError(
|
|
'SQL alchemy backend not specified in config')
|
|
|
|
elif (object_type, action) == ('database', 'upgrade'):
|
|
require_args(args, 1, 'Upgrading database requires a version #')
|
|
backend_names = options.get('backends', None)
|
|
if backend_names:
|
|
if 'keystone.backends.sqlalchemy' in backend_names.split(','):
|
|
do_db_upgrade(options['keystone.backends.sqlalchemy'],
|
|
args)
|
|
else:
|
|
raise optparse.OptParseError(
|
|
'SQL alchemy backend not specified in config')
|
|
|
|
elif (object_type, action) == ('database', 'downgrade'):
|
|
require_args(args, 1, 'Downgrading database requires a version #')
|
|
backend_names = options.get('backends', None)
|
|
if backend_names:
|
|
if 'keystone.backends.sqlalchemy' in backend_names.split(','):
|
|
do_db_downgrade(options['keystone.backends.sqlalchemy'],
|
|
args)
|
|
else:
|
|
raise optparse.OptParseError(
|
|
'SQL alchemy backend not specified in config')
|
|
|
|
elif (object_type, action) == ('database', 'version_control'):
|
|
backend_names = options.get('backends', None)
|
|
if backend_names:
|
|
if 'keystone.backends.sqlalchemy' in backend_names.split(','):
|
|
do_db_version_control(options['keystone.backends.sqlalchemy'])
|
|
else:
|
|
raise optparse.OptParseError(
|
|
'SQL alchemy backend not specified in config')
|
|
|
|
elif (object_type, action) == ('database', 'version'):
|
|
backend_names = options.get('backends', None)
|
|
if backend_names:
|
|
if 'keystone.backends.sqlalchemy' in backend_names.split(','):
|
|
do_db_version(options['keystone.backends.sqlalchemy'])
|
|
else:
|
|
raise optparse.OptParseError(
|
|
'SQL alchemy backend not specified in config')
|
|
|
|
elif (object_type, action) == ('database', 'goto'):
|
|
require_args(args, 1, 'Jumping database versions requires a '
|
|
'version #')
|
|
backend_names = options.get('backends', None)
|
|
if backend_names:
|
|
if 'keystone.backends.sqlalchemy' in backend_names.split(','):
|
|
do_db_goto_version(options['keystone.backends.sqlalchemy'],
|
|
version=args[2])
|
|
else:
|
|
raise optparse.OptParseError(
|
|
'SQL alchemy backend not specified in config')
|
|
|
|
else:
|
|
# Command recognized but not handled: should never reach this
|
|
raise NotImplementedError()
|
|
|
|
|
|
#
|
|
# Database Migration commands (from Glance-manage)
|
|
#
|
|
def do_db_version(options):
|
|
"""Print database's current migration level"""
|
|
print migration.db_version(options)
|
|
|
|
|
|
def do_db_goto_version(options, version):
|
|
"""Override the database's current migration level"""
|
|
if migration.db_goto_version(options, version):
|
|
msg = ('Jumped to version=%s (without performing intermediate '
|
|
'migrations)') % version
|
|
print msg
|
|
|
|
|
|
def do_db_upgrade(options, args):
|
|
"""Upgrade the database's migration level"""
|
|
try:
|
|
db_version = args[2]
|
|
except IndexError:
|
|
db_version = None
|
|
|
|
print "Upgrading database to version %s" % db_version
|
|
migration.upgrade(options, version=db_version)
|
|
|
|
|
|
def do_db_downgrade(options, args):
|
|
"""Downgrade the database's migration level"""
|
|
try:
|
|
db_version = args[2]
|
|
except IndexError:
|
|
raise Exception("downgrade requires a version argument")
|
|
|
|
migration.downgrade(options, version=db_version)
|
|
|
|
|
|
def do_db_version_control(options):
|
|
"""Place a database under migration control"""
|
|
migration.version_control(options)
|
|
print "Database now under version control"
|
|
|
|
|
|
def do_db_sync(options, args):
|
|
"""Place a database under migration control and upgrade"""
|
|
try:
|
|
db_version = args[2]
|
|
except IndexError:
|
|
db_version = None
|
|
migration.db_sync(options, version=db_version)
|
|
|
|
|
|
class Table:
|
|
"""Prints data in table for console output
|
|
|
|
Syntax print Table("This is the title",
|
|
["Header1","Header2","H3"],
|
|
[[1,2,3],["Hi","everybody","How are you??"],[None,True,[1,2]]])
|
|
|
|
"""
|
|
def __init__(self, title=None, headers=None, rows=None):
|
|
self.title = title
|
|
self.headers = headers if headers is not None else []
|
|
self.rows = rows if rows is not None else []
|
|
self.nrows = len(self.rows)
|
|
self.fieldlen = []
|
|
|
|
ncols = len(headers)
|
|
|
|
for h in range(ncols):
|
|
max = 0
|
|
for row in rows:
|
|
if len(str(row[h])) > max:
|
|
max = len(str(row[h]))
|
|
self.fieldlen.append(max)
|
|
|
|
for i in range(len(headers)):
|
|
if len(str(headers[i])) > self.fieldlen[i]:
|
|
self.fieldlen[i] = len(str(headers[i]))
|
|
|
|
self.width = sum(self.fieldlen) + (ncols - 1) * 3 + 4
|
|
|
|
def __str__(self):
|
|
bar = "-" * self.width
|
|
if self.title:
|
|
title = "| " + self.title + " " * \
|
|
(self.width - 3 - (len(self.title))) + "|"
|
|
out = [bar, title, bar]
|
|
else:
|
|
out = []
|
|
header = ""
|
|
for i in range(len(self.headers)):
|
|
header += "| %s" % (str(self.headers[i])) + " " * \
|
|
(self.fieldlen[i] - len(str(self.headers[i]))) + " "
|
|
header += "|"
|
|
out.append(header)
|
|
out.append(bar)
|
|
for i in self.rows:
|
|
line = ""
|
|
for j in range(len(i)):
|
|
line += "| %s" % (str(i[j])) + " " * \
|
|
(self.fieldlen[j] - len(str(i[j]))) + " "
|
|
out.append(line + "|")
|
|
|
|
out.append(bar)
|
|
return "\r\n".join(out)
|
|
|
|
|
|
def main(args=None):
|
|
try:
|
|
process(*parse_args(args))
|
|
except (optparse.OptParseError, fault.DatabaseMigrationError) as exc:
|
|
print >> sys.stderr, exc
|
|
sys.exit(2)
|
|
except Exception as exc:
|
|
try:
|
|
info = exc.args[1]
|
|
except IndexError:
|
|
print "ERROR: %s" % (exc,)
|
|
logger.error(str(exc))
|
|
else:
|
|
print "ERROR: %s: %s" % (exc.args[0], info)
|
|
logger.error(exc.args[0], exc_info=info)
|
|
raise exc
|
|
|
|
|
|
if __name__ == '__main__':
|
|
try:
|
|
main()
|
|
except Exception as exc:
|
|
sys.exit(1)
|