mirror of
https://github.com/xcat2/confluent.git
synced 2024-11-22 17:43:14 +00:00
71f6199b2e
Previously, assumption was that the response data would be a dict. This is not always the case. If None is encountered, treat it as a blank attribute.
391 lines
12 KiB
Python
Executable File
391 lines
12 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
|
|
|
|
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
|
|
lastcline = None
|
|
|
|
def completer(text, state):
|
|
try:
|
|
return rcompleter(text, state)
|
|
except:
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
def rcompleter(text, state):
|
|
global candidates
|
|
global lastcline
|
|
global valid_commands
|
|
cline = readline.get_line_buffer()
|
|
if len(text):
|
|
cline = cline[:-len(text)]
|
|
if cline != lastcline:
|
|
lastcline = cline
|
|
candidates = None
|
|
args = shlex.split(cline, posix=True)
|
|
currpos = len(args)
|
|
if currpos and cline[-1] == ' ':
|
|
currpos += 1
|
|
if currpos <= 1:
|
|
foundcount = 0
|
|
for cmd in valid_commands:
|
|
if cmd.startswith(text):
|
|
if foundcount == state:
|
|
return cmd
|
|
else:
|
|
foundcount += 1
|
|
return None
|
|
cmd = args[0]
|
|
if candidates is None:
|
|
candidates = []
|
|
targpath = fullpath_target(args[-1])
|
|
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
|
|
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 target
|
|
global currconsole
|
|
global currchildren
|
|
command = command.lower()
|
|
argv = parse_command(command)
|
|
if len(argv) == 0:
|
|
return
|
|
if argv[0] == "exit":
|
|
server.close()
|
|
sys.exit(0)
|
|
if argv[0] == "cd":
|
|
otarget = target
|
|
target = fullpath_target(argv[1], forcepath=True)
|
|
for res in send_request('retrieve', target, server):
|
|
if 'error' in res:
|
|
print target + ': ' + res['error']
|
|
target = otarget
|
|
if 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():
|
|
if res[key] is None:
|
|
print "%s=" % key
|
|
elif 'value' in res[key]:
|
|
print "%s=%s" % (key, res[key]['value'])
|
|
else:
|
|
if 'isset' in res[key] and res[key]['isset']:
|
|
print "%s=********" % key
|
|
else:
|
|
print "%s=" % key
|
|
if 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:
|
|
print 'Error: ' + status['error']
|
|
return
|
|
print '[console session started]'
|
|
startconsole()
|
|
return
|
|
|
|
|
|
def fullpath_target(path, forcepath=False):
|
|
global target
|
|
pathcomponents = path.split("/")
|
|
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:
|
|
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.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
|
|
exit()
|