blob: 8c5a999197abb63608a1e51fed10461bd5e41ab7 [file] [log] [blame]
#!/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.add_argument('-u', '--auth', help='the uuid:key to initialize the subject authorization key with')
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
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 = {
'_id': args.subject,
'uuid': uid,
'key': key,
'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) = getSubjectFromDb(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 getSubjectFromDb(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) = getSubjectFromDb(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/subjects/_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()