| #!/usr/bin/env python |
| |
| # |
| # Copyright 2015-2016 IBM Corporation |
| # |
| # 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. |
| # |
| |
| ## |
| # Whisk Admin command line interface |
| ## |
| |
| import os |
| import sys |
| import argparse |
| import json |
| import wskprop |
| import string |
| import random |
| import uuid |
| from subprocess import Popen, PIPE, STDOUT |
| try: |
| import argcomplete |
| except ImportError: |
| argcomplete = False |
| from wskutil import request |
| |
| DB_PROTOCOL = 'DB_PROTOCOL' |
| DB_HOST = 'DB_HOST' |
| DB_PORT = 'DB_PORT' |
| DB_USERNAME = 'DB_USERNAME' |
| DB_PASSWORD = 'DB_PASSWORD' |
| |
| DB_WHISK_AUTHS = 'DB_WHISK_AUTHS' |
| DB_WHISK_ACTIONS = 'DB_WHISK_ACTIONS' |
| |
| LOGS_DIR = 'WHISK_LOGS_DIR' |
| |
| # SCRIPT_DIR is going to be traversing all links and point to tools/cli/wsk |
| CLI_DIR = os.path.dirname(os.path.realpath(sys.argv[0])) |
| # ROOT_DIR is the repository root |
| ROOT_DIR = os.path.join(os.path.join(CLI_DIR, os.pardir), os.pardir) |
| |
| def main(): |
| requiredprops = [ |
| DB_PROTOCOL, DB_HOST, DB_PORT, DB_USERNAME, DB_PASSWORD, |
| DB_WHISK_AUTHS, DB_WHISK_ACTIONS, |
| LOGS_DIR ] |
| whiskprops = wskprop.importPropsIfAvailable(wskprop.propfile(ROOT_DIR)) |
| (valid, props, deferredInfo) = wskprop.checkRequiredProperties(requiredprops, whiskprops) |
| |
| exitCode = 0 if valid else 2 |
| if valid: |
| try: |
| args = parseArgs() |
| if (args.verbose): |
| print deferredInfo |
| exitCode = { |
| 'user' : userCmd, |
| 'db' : dbCmd, |
| 'syslog' : syslogCmd |
| }[args.cmd](args, props) |
| except Exception as e: |
| print 'Exception: ', e |
| print 'Informative: ', deferredInfo |
| exitCode = 1 |
| sys.exit(exitCode) |
| |
| def parseArgs(): |
| parser = argparse.ArgumentParser(description='OpenWhisk admin command line tool') |
| parser.add_argument('-v', '--verbose', help='verbose output', action='store_true') |
| subparsers = parser.add_subparsers(title='available commands', dest='cmd') |
| |
| propmenu = subparsers.add_parser('user', help='manage users') |
| subparser = propmenu.add_subparsers(title='available commands', dest='subcmd') |
| |
| subcmd = subparser.add_parser('create', help='create a user and show authorization key') |
| subcmd.add_argument('subject', help='the subject to create') |
| |
| subcmd = subparser.add_parser('delete', help='delete a user') |
| subcmd.add_argument('subject', help='the subject to delete') |
| |
| subcmd = subparser.add_parser('get', help='get authorization key for user') |
| subcmd.add_argument('subject', help='the subject to get key for') |
| |
| subcmd = subparser.add_parser('whois', help='identify user from UUID (for convenience you can provide the entire authorization key') |
| subcmd.add_argument('uuid', help='the user UUID to lookup') |
| |
| propmenu = subparsers.add_parser('db', help='work with dbs') |
| subparser = propmenu.add_subparsers(title='available commands', dest='subcmd') |
| |
| subcmd = subparser.add_parser('get', help='get contents of database') |
| subcmd.add_argument('database', help='the database name') |
| subcmd.add_argument('-v', '--view', help='the view in the database to get') |
| subcmd.add_argument('--docs', help='include document contents', action='store_true') |
| |
| propmenu = subparsers.add_parser('syslog', help='work with system logs') |
| subparser = propmenu.add_subparsers(title='available commands', dest='subcmd') |
| |
| subcmd = subparser.add_parser('get', help='get logs for a transaction id') |
| subcmd.add_argument('tid', help='the transaction id') |
| subcmd.add_argument('component', help='component, one of [controller, invokerN] where N is invoker index') |
| |
| if argcomplete: |
| argcomplete.autocomplete(parser) |
| return parser.parse_args() |
| |
| def userCmd(args, props): |
| if args.subcmd == 'create': |
| return createUserCmd(args, props) |
| elif args.subcmd == 'delete': |
| return deleteUserCmd(args, props) |
| elif args.subcmd == 'get': |
| return getUserCmd(args, props) |
| elif args.subcmd == 'whois': |
| return whoisUserCmd(args, props) |
| else: |
| print 'unknown command' |
| return 2 |
| |
| def dbCmd(args, props): |
| if args.subcmd == 'get': |
| return getDbCmd(args, props) |
| else: |
| print 'unknown command' |
| return 2 |
| |
| def syslogCmd(args, props): |
| if args.subcmd == 'get': |
| return getLogsCmd(args, props) |
| else: |
| print 'unknown command' |
| return 2 |
| |
| def createUserCmd(args, props): |
| protocol = props[DB_PROTOCOL] |
| host = props[DB_HOST] |
| port = props[DB_PORT] |
| username = props[DB_USERNAME] |
| password = props[DB_PASSWORD] |
| database = props[DB_WHISK_AUTHS] |
| |
| subject = args.subject.strip() |
| if len(subject) < 5: |
| print 'Subject name must be at least 5 characters' |
| return 2 |
| |
| doc = { |
| '_id': args.subject, |
| 'key': ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(64)), |
| 'uuid': str(uuid.uuid4()), |
| 'subject': subject |
| } |
| |
| url = '%(protocol)s://%(host)s:%(port)s/%(database)s' % { |
| 'protocol': protocol, |
| 'host' : host, |
| 'port' : port, |
| 'database': database |
| } |
| body = json.dumps(doc) |
| headers = { |
| 'Content-Type': 'application/json', |
| } |
| |
| res = request('POST', url, body, headers, auth='%s:%s' % (username, password), verbose=args.verbose) |
| if res.status in [201, 202]: |
| print '%s:%s' % (doc['uuid'], doc['key']) |
| else: |
| print 'Failed to create subject (%s)' % res.read().strip() |
| return 1 |
| |
| def getUserCmd(args, props): |
| (doc, res) = getSubjecFromDb(args, props) |
| |
| if doc is not None: |
| print '%s:%s' % (doc['uuid'], doc['key']) |
| return 0 |
| else: |
| print 'Failed to get subject (%s)' % res.read().strip() |
| return 1 |
| |
| def getSubjecFromDb(args, props): |
| protocol = props[DB_PROTOCOL] |
| host = props[DB_HOST] |
| port = props[DB_PORT] |
| username = props[DB_USERNAME] |
| password = props[DB_PASSWORD] |
| database = props[DB_WHISK_AUTHS] |
| |
| url = '%(protocol)s://%(host)s:%(port)s/%(database)s/%(subject)s' % { |
| 'protocol': protocol, |
| 'host' : host, |
| 'port' : port, |
| 'database': database, |
| 'subject' : args.subject |
| } |
| |
| headers = { |
| 'Content-Type': 'application/json', |
| } |
| |
| res = request('GET', url, headers=headers, auth='%s:%s' % (username, password), verbose=args.verbose) |
| if res.status == 200: |
| doc = json.loads(res.read()) |
| return (doc, res) |
| else: |
| return (None, res) |
| |
| def deleteUserCmd(args, props): |
| protocol = props[DB_PROTOCOL] |
| host = props[DB_HOST] |
| port = props[DB_PORT] |
| username = props[DB_USERNAME] |
| password = props[DB_PASSWORD] |
| database = props[DB_WHISK_AUTHS] |
| |
| if args.subject.strip() == '': |
| print 'Subject must not be empty' |
| return 2 |
| |
| (rev, res) = getSubjecFromDb(args, props) |
| if rev is None: |
| print 'Failed to delete subject (%s)' % res.read().strip() |
| return 1 |
| |
| url = '%(protocol)s://%(host)s:%(port)s/%(database)s/%(subject)s?rev=%(rev)s' % { |
| 'protocol': protocol, |
| 'host' : host, |
| 'port' : port, |
| 'database': database, |
| 'subject' : args.subject, |
| 'rev' : rev['_rev'] |
| } |
| |
| headers = { |
| 'Content-Type': 'application/json', |
| } |
| |
| res = request('DELETE', url, headers=headers, auth='%s:%s' % (username, password), verbose=args.verbose) |
| if res.status in [200, 202]: |
| print 'Subject deleted' |
| else: |
| print 'Failed to delete subject (%s)' % res.read().strip() |
| return 1 |
| |
| def whoisUserCmd(args, props): |
| protocol = props[DB_PROTOCOL] |
| host = props[DB_HOST] |
| port = props[DB_PORT] |
| username = props[DB_USERNAME] |
| password = props[DB_PASSWORD] |
| database = props[DB_WHISK_AUTHS] |
| uuid = args.uuid.split(':')[0] |
| |
| url = '%(protocol)s://%(host)s:%(port)s/%(database)s/_design/%(database)s/_view/uuids?key=["%(uuid)s"]' % { |
| 'protocol': protocol, |
| 'host' : host, |
| 'port' : port, |
| 'username': username, |
| 'database': database, |
| 'uuid' : uuid |
| } |
| |
| headers = { |
| 'Content-Type': 'application/json', |
| } |
| |
| res = request('GET', url, headers=headers, auth='%s:%s' % (username, password), verbose=args.verbose) |
| if res.status == 200: |
| doc = json.loads(res.read()) |
| if 'rows' in doc and len(doc['rows']) > 0: |
| for row in doc['rows']: |
| if 'id' in row: |
| print row['id'] |
| else: |
| print 'Subject id is not recognized' |
| return 0 |
| print 'Failed to delete subject (%s)' % res.read().strip() |
| return 1 |
| |
| def getDbCmd(args, props): |
| protocol = props[DB_PROTOCOL] |
| host = props[DB_HOST] |
| port = props[DB_PORT] |
| username = props[DB_USERNAME] |
| password = props[DB_PASSWORD] |
| |
| if args.database == 'subjects': |
| database = props[DB_WHISK_AUTHS] |
| elif args.database == 'whisks': |
| database = props[DB_WHISK_ACTIONS] |
| else: |
| database = args.database |
| |
| if args.view: |
| try: |
| parts = args.view.split('/') |
| designdoc = parts[0] |
| viewname = parts[1] |
| except: |
| print 'view name "%s" is not formatted correctly, should be design/view' % args.view |
| return 2 |
| |
| url = '%(protocol)s://%(host)s:%(port)s/%(database)s%(design)s/%(index)s?include_docs=%(docs)s' % { |
| 'protocol': protocol, |
| 'host' : host, |
| 'port' : port, |
| 'database': database, |
| 'design' : '/_design/' + designdoc +'/_view' if args.view else '', |
| 'index' : viewname if args.view else '_all_docs', |
| 'docs' : 'true' if args.docs else 'false' |
| } |
| |
| headers = { |
| 'Content-Type': 'application/json', |
| } |
| |
| print 'getting contents for %s (%s)' % (database, args.view if args.view else 'primary index') |
| res = request('GET', url, headers=headers, auth='%s:%s' % (username, password), verbose=args.verbose) |
| if res.status == 200: |
| table = json.loads(res.read()) |
| print json.dumps(table, sort_keys=True, indent=4, separators=(',', ': ')) |
| return 0 |
| print 'Failed to get database (%s)' % res.read().strip() |
| return 1 |
| |
| def getLogsCmd(args, props): |
| path = '%s/%s/%s_logs.log' % (props[LOGS_DIR], args.component, args.component) |
| cmd = 'grep "\[#tid_%s\]" %s' % (args.tid, path) |
| (output, error) = shell(cmd, verbose = args.verbose) |
| if output: |
| print output.strip() |
| if error: |
| print error.strip() |
| return 1 |
| else: |
| return 0 |
| |
| def shell(cmd, data=None, verbose=False): |
| if verbose: |
| print cmd |
| if input: |
| p = Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT, stdin=PIPE) |
| out, err = p.communicate(input=data) |
| else: |
| out, err = Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT) |
| p.wait() |
| return (out, err) |
| |
| if __name__ == '__main__': |
| main() |