mirror of
https://github.com/xcat2/confluent.git
synced 2024-11-25 19:10:10 +00:00
f7321f4e70
It has been expressed that plural form for collection names are preferred. Additionally, tab completion is nicer if names do not share so much leading characters.
406 lines
13 KiB
Python
Executable File
406 lines
13 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
# ultimately, this should provide an interactive cli for navigating confluent
|
|
# tree and doing console with a socket. <ESC>]0;<string><BELL> can be used to
|
|
# present info such as whether it is in a console or other mode and, if in
|
|
# console, how many other connections are live and looking
|
|
# this means 'wcons' simply needs to make a terminal run and we'll take care of
|
|
# the title while providing more info
|
|
# this also means the socket interface needs to have ways to convey more
|
|
# interesting pieces of data (like concurrent connection count)
|
|
# socket will probably switch to a TLV scheme:
|
|
# 32 bit TL, 8 bits of type code and 24 bit size
|
|
# type codes:
|
|
# 0: string data
|
|
# 1: json data
|
|
# 24 bit size allows the peer to avoid having to do any particular parsing to
|
|
# understand message boundaries (which is a significant burden on the xCAT
|
|
# protocol)
|
|
|
|
# When in a console client mode, will recognize two escape sequences by
|
|
# default:
|
|
# Ctrl-E, c, ?: mimick conserver behavior
|
|
# ctrl-]: go to interactive prompt (telnet escape, but not telnet prompt)
|
|
# esc-( would interfere with normal esc use too much
|
|
# ~ I will not use for now...
|
|
|
|
import fcntl
|
|
import getpass
|
|
import optparse
|
|
import os
|
|
import readline
|
|
import select
|
|
import shlex
|
|
import socket
|
|
import ssl
|
|
import sys
|
|
import termios
|
|
import tty
|
|
|
|
exitcode = 0
|
|
consoleonly = False
|
|
target = "/"
|
|
path = os.path.dirname(os.path.realpath(__file__))
|
|
path = os.path.realpath(os.path.join(path, '..'))
|
|
sys.path.append(path)
|
|
|
|
import confluent.common.tlvdata as tlvdata
|
|
import confluent.common.client as client
|
|
|
|
conserversequence = '\x05c' # ctrl-e, c
|
|
|
|
oldtcattr = termios.tcgetattr(sys.stdin.fileno())
|
|
server = None
|
|
|
|
def prompt():
|
|
try:
|
|
return raw_input(target + ' -> ')
|
|
except KeyboardInterrupt:
|
|
print ""
|
|
return ""
|
|
except EOFError: # ctrl-d
|
|
print("exit")
|
|
return "exit"
|
|
# sys.stdout.write(target + ' -> ')
|
|
# sys.stdout.flush()
|
|
# username = raw_input("Name: ")
|
|
|
|
valid_commands = [
|
|
'start',
|
|
'cd',
|
|
'show',
|
|
'set',
|
|
'unset',
|
|
'create',
|
|
]
|
|
|
|
candidates = None
|
|
session = None
|
|
|
|
def completer(text, state):
|
|
try:
|
|
return rcompleter(text, state)
|
|
except:
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
def rcompleter(text, state):
|
|
global candidates
|
|
global valid_commands
|
|
cline = readline.get_line_buffer()
|
|
if len(text):
|
|
cline = cline[:-len(text)]
|
|
args = shlex.split(cline, posix=True)
|
|
currpos = len(args)
|
|
if currpos and cline[-1] == ' ':
|
|
lastarg = ''
|
|
currpos += 1
|
|
elif currpos:
|
|
lastarg = args[-1]
|
|
if currpos <= 1:
|
|
foundcount = 0
|
|
for cmd in valid_commands:
|
|
if cmd.startswith(text):
|
|
if foundcount == state:
|
|
return cmd
|
|
else:
|
|
foundcount += 1
|
|
candidates = None
|
|
return None
|
|
cmd = args[0]
|
|
if candidates is None:
|
|
candidates = []
|
|
targpath = fullpath_target(lastarg)
|
|
for res in session.read(targpath):
|
|
if 'item' in res: # a link relation
|
|
if type(res['item']) == dict:
|
|
candidates.append(res['item']["href"])
|
|
else:
|
|
for item in res['item']:
|
|
candidates.append(item["href"])
|
|
foundcount = 0
|
|
for elem in candidates:
|
|
if cmd == 'cd' and elem[-1] != '/':
|
|
continue
|
|
if elem.startswith(text):
|
|
if foundcount == state:
|
|
return elem
|
|
else:
|
|
foundcount += 1
|
|
candidates = None
|
|
return None
|
|
|
|
|
|
def parse_command(command):
|
|
args = shlex.split(command, posix=True)
|
|
return args
|
|
|
|
|
|
currchildren = None
|
|
|
|
def do_command(command, server):
|
|
global exitcode
|
|
global target
|
|
global currconsole
|
|
global currchildren
|
|
exitcode = 0
|
|
command = command.lower()
|
|
argv = parse_command(command)
|
|
if len(argv) == 0:
|
|
return
|
|
if argv[0] == 'exit':
|
|
sys.exit(0)
|
|
elif argv[0] == 'cd':
|
|
otarget = target
|
|
if len(argv) > 1:
|
|
target = fullpath_target(argv[1], forcepath=True)
|
|
else: # cd by itself, go 'home'
|
|
target = '/'
|
|
for res in session.read(target, server):
|
|
if 'errorcode' in res:
|
|
exitcode = res['errorcode']
|
|
if 'error' in res:
|
|
sys.stderr.write(target + ': ' + res['error'] + '\n')
|
|
target = otarget
|
|
elif argv[0] in ('cat', 'show', 'ls', 'dir'):
|
|
if len(argv) > 1:
|
|
targpath = fullpath_target(argv[1])
|
|
else:
|
|
targpath = target
|
|
for res in session.read(targpath):
|
|
if 'item' in res: # a link relation
|
|
if type(res['item']) == dict:
|
|
print res['item']["href"]
|
|
else:
|
|
for item in res['item']:
|
|
print item["href"]
|
|
else: # generic attributes to list
|
|
if 'error' in res:
|
|
sys.stderr.write(res['error'] + '\n')
|
|
if 'errorcode' in res:
|
|
exitcode = res['errorcode']
|
|
continue
|
|
for key in res.iterkeys():
|
|
notes = []
|
|
if res[key] is None:
|
|
attrstr = '%s=""' % key
|
|
elif type(res[key]) == list:
|
|
attrstr = '%s=["%s"]' % (key, '","'.join(res[key]))
|
|
elif 'value' in res[key] and res[key]['value'] is not None:
|
|
attrstr = '%s="%s"' % (key, res[key]['value'])
|
|
elif 'value' in res[key] and res[key]['value'] is None:
|
|
attrstr = '%s=""' % key
|
|
else:
|
|
if 'isset' in res[key] and res[key]['isset']:
|
|
attrstr = '%s="********"' % key
|
|
else:
|
|
attrstr = '%s=""' % key
|
|
if res[key] is not None and 'inheritedfrom' in res[key]:
|
|
notes.append('Inherited from %s' % res[key]['inheritedfrom'])
|
|
if res[key] is not None and 'expression' in res[key]:
|
|
notes.append('Derived from expression "%s"' % res[key]['expression'])
|
|
notestr = '(' + ', '.join(notes) + ')'
|
|
output = '{0:<40} {1:>39}'.format(attrstr, notestr)
|
|
print(output)
|
|
elif argv[0] == 'start':
|
|
targpath = fullpath_target(argv[1])
|
|
currconsole = targpath
|
|
tlvdata.send(session.connection, {'operation': 'start', 'path': targpath})
|
|
status = tlvdata.recv(session.connection)
|
|
if 'error' in status:
|
|
if 'errorcode' in status:
|
|
exitcode = status['errorcode']
|
|
sys.stderr.write('Error: ' + status['error'] + '\n')
|
|
return
|
|
print '[console session started]'
|
|
startconsole()
|
|
return
|
|
elif argv[0] == 'set':
|
|
setvalues(argv[1:])
|
|
elif argv[0] in ('unset', 'clear'):
|
|
clearvalues(argv[1], argv[2:])
|
|
else:
|
|
sys.stderr.write("%s: command not found...\n" % argv[0])
|
|
|
|
|
|
def clearvalues(resource, attribs):
|
|
targpath = fullpath_target(resource)
|
|
keydata = {}
|
|
for attrib in attribs:
|
|
keydata[attrib] = None
|
|
for res in session.update(targpath, keydata):
|
|
if 'error' in res:
|
|
if 'errorcode' in res:
|
|
exitcode = res['errorcode']
|
|
sys.stderr.write('Error: ' + res['error'] + '\n')
|
|
|
|
|
|
def setvalues(attribs):
|
|
if '=' in attribs[0]: # going straight to attribute
|
|
resource = attribs[0][:attribs[0].index("=")]
|
|
if '/' in resource:
|
|
lastslash = resource.rindex('/')
|
|
attribs[0] = attribs[0][lastslash + 1:]
|
|
else: # an actual resource
|
|
resource = attribs[0]
|
|
attribs = attribs[1:]
|
|
keydata = {}
|
|
for attrib in attribs:
|
|
if '=' not in attrib:
|
|
sys.stderr.write("Invalid syntax %s" % attrib)
|
|
return
|
|
key = attrib[:attrib.index("=")]
|
|
value = attrib[attrib.index("=") + 1:]
|
|
keydata[key] = value
|
|
targpath = fullpath_target(resource)
|
|
for res in session.update(targpath, keydata):
|
|
if 'error' in res:
|
|
if 'errorcode' in res:
|
|
exitcode = res['errorcode']
|
|
sys.stderr.write('Error: ' + res['error'] + '\n')
|
|
|
|
|
|
|
|
def fullpath_target(path, forcepath=False):
|
|
global target
|
|
if path == '':
|
|
return target
|
|
pathcomponents = path.split("/")
|
|
if pathcomponents[-1] == "": # preserve path
|
|
forcepath=True
|
|
if pathcomponents[0] == "": # absolute path
|
|
ntarget = path
|
|
else:
|
|
targparts = target.split("/")[:-1]
|
|
for component in pathcomponents:
|
|
if component in ('.',''): # ignore these
|
|
continue
|
|
elif component == '..':
|
|
if len(targparts) > 0:
|
|
del targparts[-1]
|
|
else:
|
|
targparts.append(component)
|
|
if forcepath and (len(targparts) == 0 or targparts[-1] != ""):
|
|
targparts.append('')
|
|
ntarget = '/'.join(targparts)
|
|
if forcepath and (len(ntarget) == 0 or ntarget[-1] != '/'):
|
|
ntarget += '/'
|
|
return ntarget
|
|
|
|
def startconsole():
|
|
global inconsole
|
|
tty.setraw(sys.stdin.fileno())
|
|
currfl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
|
|
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, currfl | os.O_NONBLOCK)
|
|
inconsole = True
|
|
|
|
def exit(code=0):
|
|
global consoleonly
|
|
global inconsole
|
|
global currconsole
|
|
currfl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
|
|
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, currfl ^ os.O_NONBLOCK)
|
|
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, oldtcattr)
|
|
if consoleonly:
|
|
sys.exit(code)
|
|
else:
|
|
tlvdata.send(session.connection, {'operation': 'stop', 'path': currconsole})
|
|
inconsole = False
|
|
|
|
def conserver_command(fh, command):
|
|
while not command:
|
|
ready, _, _ = select.select((fh,), (), (), 1)
|
|
if ready:
|
|
command += fh.read()
|
|
if command[0] == '.':
|
|
print("disconnect]\r")
|
|
exit()
|
|
elif command[0] == '?':
|
|
print("help]\r")
|
|
print(". disconnect\r")
|
|
print("<cr> abort command\r")
|
|
elif command[0] == '\x0d':
|
|
print("ignored]\r")
|
|
else: #not a command at all..
|
|
print("unknown -- use '?']\r")
|
|
|
|
|
|
def check_escape_seq(input, fh):
|
|
while conserversequence.startswith(input):
|
|
if input.startswith(conserversequence): # We have full sequence
|
|
sys.stdout.write("[")
|
|
sys.stdout.flush()
|
|
return conserver_command(fh, input[len(conserversequence):])
|
|
ready, _, _ = select.select((fh,), (), (), 3)
|
|
if not ready: # 3 seconds of no typing
|
|
break
|
|
input += fh.read()
|
|
return input
|
|
|
|
|
|
parser = optparse.OptionParser()
|
|
parser.add_option("-s", "--server", dest="server",
|
|
help="Confluent instance to connect to", metavar="SERVER:PORT")
|
|
opts, args = parser.parse_args()
|
|
if opts.server: # going over a TLS network
|
|
session = client.Command(opts.server)
|
|
else:
|
|
session = client.Command()
|
|
|
|
#Next stop, reading and writing from whichever of stdin and server goes first.
|
|
#see pyghmi code for solconnect.py
|
|
|
|
while not session.authenticated:
|
|
username = raw_input("Name: ")
|
|
readline.clear_history()
|
|
passphrase = getpass.getpass("Passphrase: ")
|
|
session.authenticate(username, passphrase)
|
|
# clear on start can help with readable of TUI, but it
|
|
# can be annoying, so for now don't do it.
|
|
# sys.stdout.write('\x1b[H\x1b[J')
|
|
# sys.stdout.flush()
|
|
|
|
readline.parse_and_bind("tab: complete")
|
|
readline.parse_and_bind("set bell-style none")
|
|
readline.set_completer(completer)
|
|
doexit = False
|
|
inconsole = False
|
|
pendingcommand = ""
|
|
if len(args) == 1: # a node name, go straight to trying to console
|
|
consoleonly = True
|
|
do_command("start /nodes/%s/console/session" % args[0], server)
|
|
while not doexit:
|
|
if inconsole:
|
|
rdylist, _, _ = select.select((sys.stdin, session.connection), (), (), 60)
|
|
for fh in rdylist:
|
|
if fh == session.connection:
|
|
# this only should get called in the
|
|
# case of a console session
|
|
# each command should slurp up all relevant
|
|
# recv potential
|
|
#fh.read()
|
|
try:
|
|
data = tlvdata.recv(fh)
|
|
except Exception:
|
|
data = None
|
|
if type(data) == dict:
|
|
print repr(data)
|
|
continue
|
|
if data is not None:
|
|
sys.stdout.write(data)
|
|
sys.stdout.flush()
|
|
else:
|
|
doexit = True
|
|
sys.stdout.write("\r\n[remote disconnected]\r\n")
|
|
break
|
|
else:
|
|
input = fh.read()
|
|
input = check_escape_seq(input, fh)
|
|
if input:
|
|
tlvdata.send(session.connection, input)
|
|
else:
|
|
command = prompt()
|
|
do_command(command, server)
|
|
consoleonly = True
|
|
exit()
|