2
0
mirror of https://github.com/xcat2/confluent.git synced 2024-11-26 03:19:48 +00:00
confluent/bin/confetty

421 lines
14 KiB
Plaintext
Raw Normal View History

#!/usr/bin/env python
2013-10-11 00:04:59 +00:00
# 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
SO_PASSCRED = 16
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',
'create',
]
candidates = 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 send_request('retrieve', targpath, server):
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 parseservervalue(serverstring):
if serverstring.find(']:') != -1:
server, port = serverstring[1:].split(']:')
elif serverstring[0] == '[':
server = serverstring[1:-1]
port = 4001
elif -1 != opts.server.find(':'):
server, port = opts.server.split(":")
else:
server = serverstring
port = 4001
return (server, port)
def parse_command(command):
args = shlex.split(command, posix=True)
return args
currchildren = None
def send_request(operation, path, server):
tlvdata.send_tlvdata(server, {'operation': operation, 'path': path})
result = tlvdata.recv_tlvdata(server)
while '_requestdone' not in result:
yield result
result = tlvdata.recv_tlvdata(server)
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":
server.close()
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 send_request('retrieve', 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 send_request('retrieve', targpath, server):
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
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_tlvdata(server, {'operation': 'start', 'path': targpath})
status = tlvdata.recv_tlvdata(server)
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
else:
sys.stderr.write("%s: command not found...\n" % argv[0])
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 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:
server.shutdown(socket.SHUT_RDWR)
server.close()
sys.exit(code)
else:
tlvdata.send_tlvdata(server, {'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
def connect_unix_server(sockpath):
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, SO_PASSCRED, 1)
server.connect(sockpath)
return server
def connect_tls_server(serverstring):
host, port = parseservervalue(serverstring)
for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
socket.SOCK_STREAM):
af, socktype, proto, cononname, sa = res
try:
server = socket.socket(af, socktype, proto)
server.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
except:
server = None
continue
try:
server.settimeout(5)
server.connect(sa)
except:
server.close()
server = None
continue
break
if server is None:
sys.stderr.write("Failed to connect to %s\n" % serverstring)
sys.exit(1)
secserver = ssl.wrap_socket(server)
return secserver
parser = optparse.OptionParser()
parser.add_option("-s", "--server", dest="server",
help="TLS server to connect to", metavar="SERVER:PORT")
parser.add_option("-u", "--unixsocket", dest="unixsock",
help="TLS server to connect to", metavar="UNIXDOMAINSOCKET")
opts, args = parser.parse_args()
if opts.server: # going over a TLS network
server = connect_tls_server(opts.server)
elif opts.unixsock:
server = connect_unix_server(opts.unixsock)
elif os.path.exists("/var/run/confluent/api.sock"):
server = connect_unix_server("/var/run/confluent/api.sock")
#Next stop, reading and writing from whichever of stdin and server goes first.
#see pyghmi code for solconnect.py
banner = tlvdata.recv_tlvdata(server)
authinfo = tlvdata.recv_tlvdata(server)
while authinfo['authpassed'] != 1:
username = raw_input("Name: ")
readline.clear_history()
passphrase = getpass.getpass("Passphrase: ")
tlvdata.send_tlvdata(server,
{'username': username, 'passphrase': passphrase})
authinfo = tlvdata.recv_tlvdata(server)
# 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 /node/%s/console/session" % args[0], server)
while not doexit:
if inconsole:
rdylist, _, _ = select.select((sys.stdin, server), (), (), 60)
for fh in rdylist:
if fh == server:
# this only should get called in the
# case of a console session
# each command should slurp up all relevant
# recv_tlvdata potential
#fh.read()
try:
data = tlvdata.recv_tlvdata(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_tlvdata(server, input)
else:
command = prompt()
do_command(command, server)
consoleonly = True
2013-10-12 19:04:26 +00:00
exit()