Browse Source

python jumpcli

master
牛批的一批 5 years ago
parent
commit
9a5f69f919
  1. 1
      .gitignore
  2. 5
      client/jumpcli.go
  3. 297
      client/jumpcli.py
  4. 1
      client/requirements.txt
  5. 24
      examples/cmd-menu.py
  6. 2
      server/manager_user.sh

1
.gitignore

@ -1,3 +1,4 @@
jumpserver jumpserver
.idea .idea
.idea/ .idea/
examples/

5
client/jumpcli.go

@ -85,6 +85,7 @@ func (t *SSHTerminal) updateTerminalSize() {
continue continue
} }
// 调整窗体大小
t.Session.WindowChange(currTermHeight, currTermWidth) t.Session.WindowChange(currTermHeight, currTermWidth)
if err != nil { if err != nil {
fmt.Printf("Unable to send window-change reqest: %s.", err) fmt.Printf("Unable to send window-change reqest: %s.", err)
@ -108,12 +109,14 @@ func (t *SSHTerminal) interactiveSession() error {
}() }()
fd := int(os.Stdin.Fd()) fd := int(os.Stdin.Fd())
// raw模式 cbreak
state, err := terminal.MakeRaw(fd) state, err := terminal.MakeRaw(fd)
if err != nil { if err != nil {
return err return err
} }
defer terminal.Restore(fd, state) defer terminal.Restore(fd, state)
// 获取当前窗体大小
termWidth, termHeight, err := terminal.GetSize(fd) termWidth, termHeight, err := terminal.GetSize(fd)
if err != nil { if err != nil {
return err return err
@ -124,6 +127,7 @@ func (t *SSHTerminal) interactiveSession() error {
termType = "xterm-256color" termType = "xterm-256color"
} }
// 设置tty
err = t.Session.RequestPty(termType, termHeight, termWidth, ssh.TerminalModes{ err = t.Session.RequestPty(termType, termHeight, termWidth, ssh.TerminalModes{
ssh.ECHO: 1, ssh.ECHO: 1,
ssh.TTY_OP_ISPEED: 14400, ssh.TTY_OP_ISPEED: 14400,
@ -133,6 +137,7 @@ func (t *SSHTerminal) interactiveSession() error {
return err return err
} }
// 动态调整窗体大小
t.updateTerminalSize() t.updateTerminalSize()
t.stdin, err = t.Session.StdinPipe() t.stdin, err = t.Session.StdinPipe()

297
client/jumpcli.py

@ -0,0 +1,297 @@
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import paramiko
import os
import sys
import curses
import termios
import tty
import socket
import signal
import getpass
import platform
import sqlite3
KEYS_ENTER = (curses.KEY_ENTER, ord('\n'), ord('\r'))
KEYS_UP = (curses.KEY_UP, ord('k'))
KEYS_DOWN = (curses.KEY_DOWN, ord('j'))
KEYS_SELECT = (curses.KEY_RIGHT, ord(' '))
# 数据库文件
gSqlite3File = "/usr/local/jumpserver/jumpserver.db"
# ssh_private_path ssh私钥路径
system_type = platform.system()
if system_type == "Darwin":
ssh_private_path = "/Users/%s/.ssh/id_rsa"
elif system_type == "Linux":
ssh_private_path = "/home/%s/.ssh/id_rsa"
else:
exit(1)
class Picker(object):
def __init__(self, options, title=None, indicator='*', default_index=0, multiselect=False, multi_select=False,
min_selection_count=0, options_map_func=None):
if len(options) == 0:
raise ValueError('options should not be an empty list')
self.options = options
self.title = title
self.indicator = indicator
self.multiselect = multiselect or multi_select
self.min_selection_count = min_selection_count
self.options_map_func = options_map_func
self.all_selected = []
if default_index >= len(options):
raise ValueError('default_index should be less than the length of options')
if multiselect and min_selection_count > len(options):
raise ValueError(
'min_selection_count is bigger than the available options, you will not be able to make any selection')
if options_map_func is not None and not callable(options_map_func):
raise ValueError('options_map_func must be a callable function')
self.index = default_index
self.custom_handlers = {}
def register_custom_handler(self, key, func):
self.custom_handlers[key] = func
def move_up(self):
self.index -= 1
if self.index < 0:
self.index = len(self.options) - 1
def move_down(self):
self.index += 1
if self.index >= len(self.options):
self.index = 0
def mark_index(self):
if self.multiselect:
if self.index in self.all_selected:
self.all_selected.remove(self.index)
else:
self.all_selected.append(self.index)
def get_selected(self):
"""return the current selected option as a tuple: (option, index)
or as a list of tuples (in case multiselect==True)
"""
if self.multiselect:
return_tuples = []
for selected in self.all_selected:
return_tuples.append((self.options[selected], selected))
return return_tuples
else:
return self.options[self.index], self.index
def get_title_lines(self):
if self.title:
return self.title.split('\n') + ['']
return []
def get_option_lines(self):
lines = []
for index, option in enumerate(self.options):
# pass the option through the options map of one was passed in
if self.options_map_func:
option = self.options_map_func(option)
if index == self.index:
prefix = self.indicator
else:
prefix = len(self.indicator) * ' '
if self.multiselect and index in self.all_selected:
format = curses.color_pair(1)
line = ('{0} {1}'.format(prefix, option), format)
else:
line = '{0} {1}'.format(prefix, option)
lines.append(line)
return lines
def get_lines(self):
title_lines = self.get_title_lines()
option_lines = self.get_option_lines()
lines = title_lines + option_lines
current_line = self.index + len(title_lines) + 1
return lines, current_line
def draw(self):
"""draw the curses ui on the screen, handle scroll if needed"""
self.screen.clear()
x, y = 1, 1 # start point
max_y, max_x = self.screen.getmaxyx()
max_rows = max_y - y # the max rows we can draw
lines, current_line = self.get_lines()
# calculate how many lines we should scroll, relative to the top
scroll_top = getattr(self, 'scroll_top', 0)
if current_line <= scroll_top:
scroll_top = 0
elif current_line - scroll_top > max_rows:
scroll_top = current_line - max_rows
self.scroll_top = scroll_top
lines_to_draw = lines[scroll_top:scroll_top + max_rows]
for line in lines_to_draw:
if type(line) is tuple:
self.screen.addnstr(y, x, line[0], max_x - 2, line[1])
else:
self.screen.addnstr(y, x, line, max_x - 2)
y += 1
self.screen.refresh()
def run_loop(self):
while True:
self.draw()
c = self.screen.getch()
if c in KEYS_UP:
self.move_up()
elif c in KEYS_DOWN:
self.move_down()
elif c in KEYS_ENTER:
if self.multiselect and len(self.all_selected) < self.min_selection_count:
continue
return self.get_selected()
elif c in KEYS_SELECT and self.multiselect:
self.mark_index()
elif c in self.custom_handlers:
ret = self.custom_handlers[c](self)
if ret:
return ret
elif c == ord('q'):
exit(0)
def config_curses(self):
try:
# use the default colors of the terminal
curses.use_default_colors()
# hide the cursor
curses.curs_set(0)
# add some color for multi_select
# @todo make colors configurable
curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_WHITE)
except:
# Curses failed to initialize color support, eg. when TERM=vt100
curses.initscr()
def _start(self, screen):
self.screen = screen
self.config_curses()
return self.run_loop()
def start(self):
return curses.wrapper(self._start)
def updateWindowHandler(signum):
if signum == signal.SIGWINCH:
width, height = os.get_terminal_size()
os.terminal_size((width, height))
def posix_shell(chan):
import select
oldtty = termios.tcgetattr(sys.stdin)
signal.signal(signal.SIGWINCH, updateWindowHandler)
try:
tty.setraw(sys.stdin.fileno(), termios.TCIOFLUSH)
# tty.setcbreak(sys.stdin.fileno())
chan.settimeout(0.0)
while True:
r, w, e = select.select([chan, sys.stdin], [], [])
if chan in r:
try:
data = str(chan.recv(1024), encoding='utf-8')
if len(data) == 0:
break
sys.stdout.write(data)
sys.stdout.flush()
except socket.timeout:
pass
if sys.stdin in r:
ch = sys.stdin.read(1)
if len(ch) == 0:
break
chan.send(ch)
finally:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty)
def NewTerminal(host, port, user):
# 建立ssh连接
ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(
host,
port=port,
username=user,
key_filename=ssh_private_path % user,
compress=False)
# 建立交互式shell连接
width, height = os.get_terminal_size()
channel = ssh.invoke_shell("xterm-256color", width, height)
# 建立交互式管道
posix_shell(channel)
# 关闭连接
channel.close()
ssh.close()
# 初始化表
def connect_db():
# 连接数据库
conn = sqlite3.connect(gSqlite3File)
if conn == None:
print("sqlite3.connect " + gSqlite3File + "failed!")
exit(1)
return conn
def get_hosts(user):
db = connect_db()
hosts = db.execute("select name,ip,port from hosts where isdelete=0 and name in (select hostname from hostuser where username='%s') order by id" % user).fetchall()
resp = []
for host in hosts:
resp.append("%s:%s:%s" % (host[0], host[1], host[2]))
if len(resp) == 0:
print(user + " no valid hosts")
exit(1)
return resp
def main():
user = getpass.getuser()
print("current user: " + user)
title = "ssh hosts select:"
menu = get_hosts(user)
while True:
option, index = Picker(menu, title).start()
arr = option.split(":")
if len(arr) == 3:
host = arr[1]
port = int(arr[2])
NewTerminal(host, port, user)
if __name__ == '__main__':
main()

1
client/requirements.txt

@ -0,0 +1 @@
paramiko==2.7.1

24
examples/cmd-menu.py

@ -0,0 +1,24 @@
# -*- coding: UTF-8 -*-
from pick import pick
import sshserver
title = 'Please choose your favorite programming language: '
# options = ['quit', 'Java', 'JavaScript', 'Python', 'PHP', 'C++', 'Erlang', 'Haskell']
options = ['ckqas:192.168.1.44:22', 'ckqas129:192.168.1.129:22']
# option, index = pick(options, title)
# print(option, index)
if __name__ == '__main__':
while True:
option, index = pick(options, title)
if option == "quit":
exit(0)
arr = option.split(":")
if len(arr) == 3:
host = arr[1]
port = int(arr[2])
sshserver.NewTerminal(host, port, "daniel")

2
server/manager_user.sh

@ -82,7 +82,7 @@ del_user() {
# #
unsudo_user unsudo_user
userdel $cmdName && rm -r $USER_HOME_DIR
userdel $cmdName #&& rm -r $USER_HOME_DIR
if [ $? -eq 0 ];then if [ $? -eq 0 ];then
echo "${cmdName} is delete successful" echo "${cmdName} is delete successful"
else else

Loading…
Cancel
Save