parallel command execution for aliases and groups
This commit is contained in:
parent
032b4b1ab0
commit
e11978957f
2
setup.py
2
setup.py
|
@ -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',
|
||||||
|
|
82
sshch/sshch
82
sshch/sshch
|
@ -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)))
|
||||||
|
|
Loading…
Reference in New Issue