| #!/usr/bin/env python |
| |
| # |
| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with |
| # this work for additional information regarding copyright ownership. |
| # The ASF licenses this file to You 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 argparse |
| import json |
| import os |
| import random |
| import re |
| from subprocess import Popen, PIPE, STDOUT |
| import string |
| import sys |
| import traceback |
| import uuid |
| import wskprop |
| if sys.version_info.major >= 3: |
| from urllib.parse import quote_plus |
| else: |
| from urllib import quote_plus |
| 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' |
| DB_WHISK_ACTIVATIONS = 'DB_WHISK_ACTIVATIONS' |
| |
| 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, DB_WHISK_ACTIVATIONS, |
| 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, |
| 'limits': limitsCmd |
| }[args.cmd](args, props) |
| except Exception as e: |
| print('Exception: ', e) |
| print('Informative: ', deferredInfo) |
| traceback.print_exc() |
| 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.add_argument('-u', '--auth', help='the uuid:key to initialize the subject authorization key with') |
| subcmd.add_argument('-ns', '--namespace', help='create key for given namespace instead (defaults to subject id') |
| |
| subcmd = subparser.add_parser('delete', help='delete a user') |
| subcmd.add_argument('subject', help='the subject to delete') |
| subcmd.add_argument('-ns', '--namespace', help='delete key for given namespace only') |
| |
| subcmd = subparser.add_parser('get', help='get authorization key for user') |
| subcmd.add_argument('subject', help='the subject to get key for') |
| subcmd.add_argument('-ns', '--namespace', help='the namespace to get the key for, defaults to subject id') |
| subcmd.add_argument('-a', '--all', help='list all namespaces and their keys', action='store_true') |
| |
| subcmd = subparser.add_parser('whois', help='identify user from an authorization key') |
| subcmd.add_argument('authkey', help='the credentials to look up') |
| |
| subcmd = subparser.add_parser('block', help='block one or more users') |
| subcmd.add_argument('subjects', nargs='+', help='one or more users to block') |
| |
| subcmd = subparser.add_parser('unblock', help='unblock one or more users') |
| subcmd.add_argument('subjects', nargs='+', help='one or more users to unblock') |
| |
| subcmd = subparser.add_parser('list', help='list authorization keys associated with a namespace') |
| subcmd.add_argument('namespace', help='the namespace to lookup') |
| subcmd.add_argument('-p', '--pick', metavar='N', help='show no more than N identities', type=int, default=1) |
| subcmd.add_argument('-a', '--all', help='show all identities', action='store_true') |
| subcmd.add_argument('-k', '--key', help='show only the keys', action='store_true') |
| |
| propmenu = subparsers.add_parser('limits', help='manage namespace-specific limits') |
| subparser = propmenu.add_subparsers(title='available commands', dest='subcmd') |
| |
| subcmd = subparser.add_parser('set', help='set limits for a given namespace') |
| subcmd.add_argument('namespace', help='the namespace to set limits for') |
| subcmd.add_argument('--invocationsPerMinute', help='invocations per minute allowed', type=int) |
| subcmd.add_argument('--firesPerMinute', help='trigger fires per minute allowed', type=int) |
| subcmd.add_argument('--concurrentInvocations', help='concurrent invocations allowed for this namespace', type=int) |
| |
| subcmd = subparser.add_parser('get', help='get limits for a given namespace (if none exist, system defaults apply)') |
| subcmd.add_argument('namespace', help='the namespace to get limits for') |
| |
| subcmd = subparser.add_parser('delete', help='delete limits for a given namespace (system defaults apply)') |
| subcmd.add_argument('namespace', help='the namespace to delete limits for') |
| |
| 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') |
| subcmd.add_argument('components', help='components, one or more of [controllerN, invokerN] where N is the instance', nargs='*', default=['controller0', 'invoker0']) |
| subcmd.add_argument('-t', '--tid', help='retrieve logs for the transaction id') |
| subcmd.add_argument('-g', '--grep', help='retrieve logs that match grep expression') |
| |
| 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) |
| elif args.subcmd == 'list': |
| return listUserCmd(args, props) |
| elif args.subcmd == 'block': |
| return blockUserCmd(args, props) |
| elif args.subcmd == 'unblock': |
| return unblockUserCmd(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 limitsCmd(args, props): |
| if args.subcmd == 'set': |
| return setLimitsCmd(args, props) |
| elif args.subcmd == 'get': |
| return getLimitsCmd(args, props) |
| elif args.subcmd == 'delete': |
| return deleteLimitsCmd(args, props) |
| else: |
| print('unknown command') |
| return 2 |
| |
| def createUserCmd(args, props): |
| subject = args.subject.strip() |
| if len(subject) < 5: |
| print('Subject name must be at least 5 characters') |
| return 2 |
| |
| if args.namespace and args.namespace.strip() == '': |
| print('Namespace must not be empty') |
| return 2 |
| else: |
| desiredNamespace = subject if not args.namespace else args.namespace.strip() |
| |
| if args.auth: |
| try: |
| parts = args.auth.split(':') |
| try: |
| uid = str(uuid.UUID(parts[0], version = 4)) |
| except ValueError: |
| print('authorization id is not a valid UUID') |
| return 2 |
| |
| key = parts[1] |
| if len(key) < 64: |
| print('authorization key must be at least 64 characters long') |
| return 2 |
| except Exception as e: |
| print('failed to determine authorization id and key: %s' % e) |
| return 2 |
| else: |
| uid = str(uuid.uuid4()) |
| key = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(64)) |
| |
| (doc, res) = getDocumentFromDb(props, args.subject, args.verbose) |
| if doc is None: |
| doc = { |
| '_id': subject, |
| 'subject': subject, |
| 'namespaces': [ |
| { |
| 'name': desiredNamespace, |
| 'uuid': uid, |
| 'key': key |
| } |
| ] |
| } |
| else: |
| if not doc.get('blocked'): |
| namespaces = [ns for ns in doc['namespaces'] if ns['name'] == desiredNamespace] |
| if len(namespaces) == 0: |
| doc['namespaces'].append({ |
| 'name': desiredNamespace, |
| 'uuid': uid, |
| 'key': key |
| }) |
| else: |
| print('Namespace already exists') |
| return 1 |
| else: |
| print('The subject you want to edit is blocked') |
| return 1 |
| |
| res = insertIntoDatabase(props, doc, args.verbose) |
| if res.status in [201, 202]: |
| print('%s:%s' % (uid, key)) |
| else: |
| print('Failed to create subject (%s)' % res.read().strip()) |
| return 1 |
| |
| def getUserCmd(args, props): |
| (doc, res) = getDocumentFromDb(props, args.subject, args.verbose) |
| |
| if doc is not None: |
| if args.all is True: |
| # tabulate name of each space and its key |
| for ns in doc['namespaces']: |
| print('%s\t%s:%s' % (ns['name'], ns['uuid'], ns['key'])) |
| return 0 |
| else: |
| # if requesting key for specific namespace, report only that key; |
| # use default namespace if no namespace provided |
| namespaceName = args.namespace if args.namespace is not None else args.subject |
| namespaces = [ns for ns in doc['namespaces'] if ns['name'] == namespaceName] |
| if len(namespaces) == 1: |
| ns = namespaces[0] |
| print('%s:%s' % (ns['uuid'], ns['key'])) |
| return 0 |
| else: |
| print('namespace "%s" not found for "%s"' % (namespaceName, args.subject)) |
| return 1 |
| else: |
| print('Failed to get subject (%s)' % res.read().strip()) |
| return 1 |
| |
| def listUserCmd(args, props): |
| (nslist, res) = getIdentitiesFromNamespace(args, props) |
| |
| if args.pick < 1: |
| print('pick at least 1 identity to show') |
| return 2 |
| |
| if nslist is not None: |
| nslist = nslist if args.all is True else nslist[:args.pick] |
| if len(nslist) > 0: |
| for p in nslist: |
| print('%s:%s%s' % (p['uuid'], p['key'], "\t%s" % p['subject'] if not args.key else "")) |
| return 0 |
| else: |
| print('no identities found for namespace "%s"' % args.namespace) |
| return 0 |
| else: |
| print('Failed to get namespace key (%s)' % res.read().strip()) |
| return 1 |
| |
| def getDocumentFromDb(props, doc, verbose): |
| 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' : doc |
| } |
| |
| headers = { |
| 'Content-Type': 'application/json', |
| } |
| |
| res = request('GET', url, headers=headers, auth='%s:%s' % (username, password), verbose=verbose) |
| if res.status == 200: |
| doc = json.loads(res.read()) |
| return (doc, res) |
| else: |
| return (None, res) |
| |
| def getIdentitiesFromNamespace(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/_design/subjects/_view/identities?key=["%(ns)s"]' % { |
| 'protocol': protocol, |
| 'host' : host, |
| 'port' : port, |
| 'username': username, |
| 'database': database, |
| 'ns' : args.namespace |
| } |
| |
| headers = { |
| 'Content-Type': 'application/json', |
| } |
| |
| res = request('GET', url, headers=headers, auth='%s:%s' % (username, password), verbose=args.verbose) |
| nslist = None |
| if res.status == 200: |
| doc = json.loads(res.read()) |
| nslist = [] |
| if 'rows' in doc and len(doc['rows']) > 0: |
| for row in doc['rows']: |
| if 'id' in row: |
| nslist.append({"subject": row["id"], "uuid": row['value']['uuid'], "key": row['value']['key']}) |
| return (nslist, 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 |
| |
| if args.namespace and args.namespace.strip() == '': |
| print('Namespace must not be empty') |
| return 2 |
| |
| (prev, res) = getDocumentFromDb(props, args.subject, args.verbose) |
| if prev is None: |
| print('Failed to delete subject (%s)' % res.read().strip()) |
| return 1 |
| |
| if not args.namespace: |
| 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.strip(), |
| 'rev' : prev['_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 |
| else: |
| namespaceToDelete = args.namespace.strip() |
| namespaces = [ns for ns in prev['namespaces'] if ns['name'] != namespaceToDelete] |
| if len(prev['namespaces']) == len(namespaces): |
| print('Namespace "%s" does not exist for "%s"' % (namespaceToDelete, prev['_id'])) |
| return 1 |
| else: |
| prev['namespaces'] = namespaces |
| res = insertIntoDatabase(props, prev, args.verbose) |
| if res.status in [201, 202]: |
| print('Namespace deleted') |
| else: |
| print('Failed to remove namespace (%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] |
| |
| authParts = args.authkey.split(':') |
| uuid = authParts[0] |
| key = authParts[1] |
| |
| url = '%(protocol)s://%(host)s:%(port)s/%(database)s/_design/subjects/_view/identities?key=["%(uuid)s","%(key)s"]' % { |
| 'protocol': protocol, |
| 'host' : host, |
| 'port' : port, |
| 'username': username, |
| 'database': database, |
| 'uuid' : uuid, |
| 'key' : key |
| } |
| |
| 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('subject: %s' % row['id']) |
| print('namespace: %s' % row['value']['namespace']) |
| else: |
| print('Subject id is not recognized') |
| return 0 |
| print('Failed to get subject (%s)' % res.read().strip()) |
| return 1 |
| |
| def blockUserCmd(args, props): |
| failed = 0 |
| for subject in args.subjects: |
| subject = subject.strip() |
| if len(subject) > 0: |
| (doc, res) = getDocumentFromDb(props, subject, args.verbose) |
| |
| if doc is not None: |
| doc['blocked'] = True |
| insertRes = insertIntoDatabase(props, doc, args.verbose) |
| if insertRes.status in [201, 202]: |
| print('"%s" blocked successfully' % subject) |
| else: |
| print('Failed to block "%s" (%s)' % (subject, res.read().strip())) |
| failed += 1 |
| else: |
| print('Failed to block "%s" (%s)' % (subject, res.read().strip())) |
| failed += 1 |
| return failed |
| |
| def unblockUserCmd(args, props): |
| failed = 0 |
| for subject in args.subjects: |
| subject = subject.strip() |
| if len(subject) > 0: |
| (doc, res) = getDocumentFromDb(props, subject, args.verbose) |
| |
| if doc is not None: |
| doc['blocked'] = False |
| insertRes = insertIntoDatabase(props, doc, args.verbose) |
| if insertRes.status in [201, 202]: |
| print('"%s" unblocked successfully' % subject) |
| else: |
| print('Failed to unblock "%s" (%s)' % (subject, res.read().strip())) |
| failed += 1 |
| else: |
| print('Failed to unblock "%s" (%s)' % (subject, res.read().strip())) |
| failed += 1 |
| return failed |
| |
| def setLimitsCmd(args, props): |
| argsDict = vars(args) |
| docId = args.namespace + "/limits" |
| (dbDoc, res) = getDocumentFromDb(props, quote_plus(docId), args.verbose) |
| doc = dbDoc or {'_id': docId} |
| |
| limits = ['invocationsPerMinute', 'firesPerMinute', 'concurrentInvocations'] |
| for limit in limits: |
| givenLimit = argsDict.get(limit) |
| toSet = givenLimit if givenLimit != None else doc.get(limit) |
| if toSet != None: |
| doc[limit] = toSet |
| |
| res = insertIntoDatabase(props, doc, args.verbose) |
| if res.status in [201, 202]: |
| print('Limits successfully set for "%s"' % args.namespace) |
| else: |
| print('Failed to set limits (%s)' % res.read().strip()) |
| return 1 |
| |
| def getLimitsCmd(args, props): |
| docId = args.namespace + "/limits" |
| (dbDoc, res) = getDocumentFromDb(props, quote_plus(docId), args.verbose) |
| |
| if dbDoc is not None: |
| limits = ['invocationsPerMinute', 'firesPerMinute', 'concurrentInvocations'] |
| for limit in limits: |
| givenLimit = dbDoc.get(limit) |
| if givenLimit != None: |
| print('%s = %s' % (limit, givenLimit)) |
| else: |
| error = json.loads(res.read()) |
| if error['reason'] == 'missing': |
| print('No limits found, default system limits apply') |
| else: |
| print('Failed to get limits (%s)' % res.read().strip()) |
| return 1 |
| |
| def deleteLimitsCmd(args, props): |
| docId = quote_plus(args.namespace + "/limits") |
| (dbDoc, res) = getDocumentFromDb(props, docId, args.verbose) |
| |
| if dbDoc is None: |
| print('Failed to delete limits (%s)' % res.read().strip()) |
| return 1 |
| |
| 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/%(docid)s?rev=%(rev)s' % { |
| 'protocol': protocol, |
| 'host' : host, |
| 'port' : port, |
| 'database': database, |
| 'docid' : docId, |
| 'rev' : dbDoc['_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('Limits deleted') |
| else: |
| print('Failed to delete limits (%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] |
| elif args.database == 'activations': |
| database = props[DB_WHISK_ACTIVATIONS] |
| 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?reduce=false&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 insertIntoDatabase(props, doc, verbose = False): |
| 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' % { |
| '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=verbose) |
| return res |
| |
| def getLogsCmd(args, props): |
| def getComponentLogs(component): |
| path = '%s/%s/%s_logs.log' % (props[LOGS_DIR], component, component) |
| if args.tid: |
| cmd = 'grep "\[#tid_%s\]" %s' % (args.tid, path) |
| elif args.grep: |
| cmd = 'grep "%s" %s' % (args.grep, path) |
| else: |
| cmd = 'cat %s' % path |
| (output, error) = shell(cmd, verbose = args.verbose) |
| |
| if output: |
| return output |
| if error: |
| sys.stderr.write(error) |
| return '' |
| |
| logs = map(getComponentLogs, args.components) |
| joined = ''.join(logs) |
| |
| if joined: |
| output = joined.strip() |
| parts = output.split('\n') |
| filter = [p for p in parts if p != ''] |
| date = map(extractDate, filter) |
| keyed = zip(date, parts) |
| sort = sorted(keyed, key=lambda t: t[1]) |
| msgs = unzip(sort)[1] |
| print('\n'.join(msgs)) |
| 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) |
| |
| def unzip(iterable): |
| return zip(*iterable) |
| |
| def extractDate(line): |
| matches = re.search(r'\d{4}-[01]{1}\d{1}-[0-3]{1}\d{1}T[0-2]{1}\d{1}:[0-6]{1}\d{1}:[0-6]{1}\d{1}.\d{3}Z', line) |
| if matches is not None: |
| date = matches.group(0) |
| return date |
| else: |
| return None |
| |
| if __name__ == '__main__': |
| main() |