parallel command execution for aliases and groups

This commit is contained in:
ivan 2022-06-11 11:46:47 +03:00
parent 032b4b1ab0
commit e11978957f
2 changed files with 66 additions and 18 deletions

View File

@ -13,7 +13,7 @@ def main():
long_description='SSH connection and aliases manager with curses and command line interface', long_description='SSH connection and aliases manager with curses and command line interface',
long_description_content_type='text/x-rst', long_description_content_type='text/x-rst',
license='DWTWL 2.55', license='DWTWL 2.55',
version='1.05', version='1.09.1',
py_modules=['sshch'], py_modules=['sshch'],
scripts=['sshch/sshch'], scripts=['sshch/sshch'],
keywords='sshch ssh aliases curses manager', keywords='sshch ssh aliases curses manager',

View File

@ -7,7 +7,6 @@ try:
import configparser import configparser
except ImportError: except ImportError:
import ConfigParser as configparser # Python 2.x import import ConfigParser as configparser # Python 2.x import
from os import path from os import path
from sys import argv from sys import argv
from math import ceil from math import ceil
@ -18,9 +17,10 @@ import base64
import time import time
import curses import curses
from curses import textpad, panel from curses import textpad, panel
from threading import Thread
# https://gitlab.com/zlax/sshch # https://gitlab.com/zlax/sshch
version = "1.09" version = "1.09.1"
# expand groups by default # expand groups by default
expand_default = True expand_default = True
# path to conf dir and file, default: ~/.config/sshch.conf # path to conf dir and file, default: ~/.config/sshch.conf
@ -37,6 +37,10 @@ class GroupTree(object):
self.parent = [] self.parent = []
def ThreadConnect(alias, command):
ConnectAlias(alias, command, True)
def AddNewAlias(alias): def AddNewAlias(alias):
if not conf.has_section(alias): if not conf.has_section(alias):
conf.add_section(alias) conf.add_section(alias)
@ -91,7 +95,7 @@ def ConvertPassword(password):
return password_string return password_string
def ConnectAlias(alias, command=False): def ConnectAlias(alias, command=False, threading=False):
exec_string = "" exec_string = ""
if conf.has_option(alias, "password"): if conf.has_option(alias, "password"):
password = base64.b32decode(base64.b16decode( password = base64.b32decode(base64.b16decode(
@ -102,6 +106,8 @@ def ConnectAlias(alias, command=False):
if command: if command:
exec_string = exec_string + " " + command exec_string = exec_string + " " + command
subprocess.Popen(exec_string, shell=True).communicate()[0] subprocess.Popen(exec_string, shell=True).communicate()[0]
if threading:
print ("... "+alias+" session output finished.")
def HoldConnection(alias): def HoldConnection(alias):
@ -200,7 +206,7 @@ def CMDRemove(alias):
print("error: '" + alias + "' alias or group does not exists.") print("error: '" + alias + "' alias or group does not exists.")
def CMDConnect(aliases, command=False): def CMDConnect(aliases, command=False, threading=False):
groups = [] groups = []
connectaliases = [] connectaliases = []
for a in conf.sections(): for a in conf.sections():
@ -214,11 +220,17 @@ def CMDConnect(aliases, command=False):
else: else:
connectaliases.append(alias) connectaliases.append(alias)
connectaliases = setSeq(connectaliases) connectaliases = setSeq(connectaliases)
threads = {}
for alias in connectaliases: for alias in connectaliases:
if conf.has_section(alias): if conf.has_section(alias):
print("Connecting to " + alias + "...") print("Connecting to " + alias + "...")
ConnectAlias(alias, command) if threading:
print("... " + alias + " session finished.") threads[alias] = Thread(target=ThreadConnect, args=(alias, command))
threads[alias].start()
else:
ConnectAlias(alias, command)
if not threading:
print("... " + alias + " session finished.")
else: else:
print("error: '" + alias + "' alias does not exists") print("error: '" + alias + "' alias does not exists")
@ -228,6 +240,8 @@ def CMDList(option, opt, value, parser):
def setSeq(seq): def setSeq(seq):
# py2&3 fastest way to remove duplicates from a list:
# https://www.rupython.com/x432-4-496.html
seen = set() seen = set()
seen_add = seen.add seen_add = seen.add
return [x for x in seq if not (x in seen or seen_add(x))] return [x for x in seq if not (x in seen or seen_add(x))]
@ -288,12 +302,14 @@ def GroupTreeRecursion(level, group, treelist, resultalias, resultstring, expand
if expandlist == True or group in expandlist: if expandlist == True or group in expandlist:
resultstring.append(' '*(level-1)+">> "+group) resultstring.append(' '*(level-1)+">> "+group)
for g in treelist[group].children: for g in treelist[group].children:
resultalias, resultstring = GroupTreeRecursion(level+1, g, treelist, resultalias, resultstring, expandlist, previousgroups) resultalias, resultstring = GroupTreeRecursion(level+1, g,
treelist, resultalias, resultstring, expandlist, previousgroups)
for ga in treelist[group].aliases: for ga in treelist[group].aliases:
if conf.has_option(ga, "exec_string"): if conf.has_option(ga, "exec_string"):
resultalias.append(ga) resultalias.append(ga)
result = "".join([' '*(level-1)+" ~ ", str(ga), " (", (conf.get(ga, "exec_string") if result = "".join([' '*(level-1)+" ~ ", str(ga), " (",
conf.has_option(ga, "exec_string") else ""), ")", (conf.get(ga, "exec_string") if conf.has_option(ga,
"exec_string") else ""), ")",
(" [password]" if conf.has_option(ga, "password") else "")]) (" [password]" if conf.has_option(ga, "password") else "")])
resultstring.append(result) resultstring.append(result)
else: else:
@ -306,7 +322,8 @@ def GetTreeList(strings=True, expandlist=True):
resultstring = [] resultstring = []
aliases, groups, rootaliases, rootgroups, treelist = GroupChildAliases(False) aliases, groups, rootaliases, rootgroups, treelist = GroupChildAliases(False)
for g in rootgroups: for g in rootgroups:
resultalias, resultstring = GroupTreeRecursion(1, g, treelist, resultalias, resultstring, expandlist, []) resultalias, resultstring = GroupTreeRecursion(1, g, treelist,
resultalias, resultstring, expandlist, [])
for a in rootaliases: for a in rootaliases:
resultalias.append(a) resultalias.append(a)
result = "".join([str(a), " (", (conf.get(a, "exec_string") if result = "".join([str(a), " (", (conf.get(a, "exec_string") if
@ -324,7 +341,7 @@ def CMDFullList(option, opt, value, parser):
print (p) print (p)
def CursesConnect(screen, aliases, command=False): def CursesConnect(screen, aliases, command=False, threading=False):
curses.endwin() curses.endwin()
groups = [] groups = []
connectaliases = [] connectaliases = []
@ -339,10 +356,16 @@ def CursesConnect(screen, aliases, command=False):
else: else:
connectaliases.append(alias) connectaliases.append(alias)
connectaliases = setSeq(connectaliases) connectaliases = setSeq(connectaliases)
threads = {}
for alias in connectaliases: for alias in connectaliases:
print("Connecting to " + alias + "...") print("Connecting to " + alias + "...")
ConnectAlias(alias, command) if threading:
print("... " + alias + " session finished.") threads[alias] = Thread(target=ThreadConnect, args=(alias, command))
threads[alias].start()
else:
ConnectAlias(alias, command)
if not threading:
print("... " + alias + " session finished.")
print("Press 'enter' to continue.") print("Press 'enter' to continue.")
screen.getch() screen.getch()
@ -457,7 +480,10 @@ def CMDOptions():
help="add new group for aliases") help="add new group for aliases")
opts.add_option('-c', '--command', action="store", type="string", opts.add_option('-c', '--command', action="store", type="string",
dest="command", metavar="command", default=False, dest="command", metavar="command", default=False,
help="execute command for aliases or group") help="execute command for aliases and groups")
opts.add_option('-t', '--thread', action="store", type="string",
dest="thread", metavar="command", default=False,
help="parallel command execution for aliases and groups")
opts.add_option('-k', '--keep', action="store", type="string", opts.add_option('-k', '--keep', action="store", type="string",
dest="keep", metavar="alias", default=False, dest="keep", metavar="alias", default=False,
help="hold connection with specified alias") help="hold connection with specified alias")
@ -483,7 +509,9 @@ def CMDOptions():
CMDRemove(options.remove) CMDRemove(options.remove)
if options.keep: if options.keep:
HoldConnection(options.keep) HoldConnection(options.keep)
if alias: if options.thread:
CMDConnect(alias, options.thread, True)
elif alias:
CMDConnect(alias, options.command) CMDConnect(alias, options.command)
# curses template from: https://stackoverflow.com/a/30828805/6224462 # curses template from: https://stackoverflow.com/a/30828805/6224462
@ -499,6 +527,7 @@ def CursesMain():
" 'space'/'insert' - select\n", " 'space'/'insert' - select\n",
" 'r'/'F8' - remove selected alias/aliases\n", " 'r'/'F8' - remove selected alias/aliases\n",
" 'c'/'F3' - execute specific command with selected alias/aliases\n", " 'c'/'F3' - execute specific command with selected alias/aliases\n",
" 't'/'F11' - parallel execute command with selected aliases and groups",
" 'k'/'F7' - hold connection with selected alias\n", " 'k'/'F7' - hold connection with selected alias\n",
" 'enter'/'F9' - connect to selected alias/aliases,\n", " 'enter'/'F9' - connect to selected alias/aliases,\n",
" expand/collapse group\n", " expand/collapse group\n",
@ -734,6 +763,25 @@ def CursesMain():
CursesConnect(screen, selected, CursesConnect(screen, selected,
command_string.replace("\n", "").rstrip()) command_string.replace("\n", "").rstrip())
curses.curs_set(0) curses.curs_set(0)
if (key_pressed == ord('t') or key_pressed == ord(
'T') or key_pressed == curses.KEY_F11) and row_num != 0:
selected = []
for i in range(1, row_num + 1):
if selected_strings[i] == "*":
selected.append(strings[i - 1])
if not len(selected) > 0:
selected.append(strings[position - 1].split()[0].strip())
curses.curs_set(1)
command_textpad = CursesTextpad(screen, 3, width - 8,
(height // 2) - 1, 4,
"".join([
"Enter specific command to execute with selected ",
"alias/aliases:"]
), "", normal_text, highlight_text)
command_string = command_textpad.edit(CursesTextpadConfirm)
CursesConnect(screen, selected,
command_string.replace("\n", "").rstrip(),True)
curses.curs_set(0)
if (key_pressed == ord('k') or key_pressed == ord('K') or if (key_pressed == ord('k') or key_pressed == ord('K') or
key_pressed == (curses.KEY_F7)) and row_num != 0: key_pressed == (curses.KEY_F7)) and row_num != 0:
groups = [] groups = []
@ -823,12 +871,12 @@ def CursesMain():
page = page - 1 page = page - 1
position = max_row + (max_row * (page - 1)) position = max_row + (max_row * (page - 1))
if key_pressed == curses.KEY_LEFT or (key_pressed == if key_pressed == curses.KEY_LEFT or (key_pressed ==
curses.KEY_PPAGE): curses.KEY_PPAGE):
if page > 1: if page > 1:
page = page - 1 page = page - 1
position = 1 + (max_row * (page - 1)) position = 1 + (max_row * (page - 1))
if key_pressed == curses.KEY_RIGHT or (key_pressed == if key_pressed == curses.KEY_RIGHT or (key_pressed ==
curses.KEY_NPAGE): curses.KEY_NPAGE):
if page < pages: if page < pages:
page = page + 1 page = page + 1
position = (1 + (max_row * (page - 1))) position = (1 + (max_row * (page - 1)))