multiSSH3 5.67__py3-none-any.whl → 5.69__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of multiSSH3 might be problematic. Click here for more details.
- multiSSH3.py +45 -39
- {multissh3-5.67.dist-info → multissh3-5.69.dist-info}/METADATA +1 -1
- multissh3-5.69.dist-info/RECORD +6 -0
- {multissh3-5.67.dist-info → multissh3-5.69.dist-info}/WHEEL +1 -1
- multissh3-5.67.dist-info/RECORD +0 -6
- {multissh3-5.67.dist-info → multissh3-5.69.dist-info}/entry_points.txt +0 -0
- {multissh3-5.67.dist-info → multissh3-5.69.dist-info}/top_level.txt +0 -0
multiSSH3.py
CHANGED
|
@@ -54,10 +54,10 @@ except AttributeError:
|
|
|
54
54
|
# If neither is available, use a dummy decorator
|
|
55
55
|
def cache_decorator(func):
|
|
56
56
|
return func
|
|
57
|
-
version = '5.
|
|
57
|
+
version = '5.69'
|
|
58
58
|
VERSION = version
|
|
59
59
|
__version__ = version
|
|
60
|
-
COMMIT_DATE = '2025-05-
|
|
60
|
+
COMMIT_DATE = '2025-05-09'
|
|
61
61
|
|
|
62
62
|
CONFIG_FILE_CHAIN = ['./multiSSH3.config.json',
|
|
63
63
|
'~/multiSSH3.config.json',
|
|
@@ -349,7 +349,7 @@ __failedHosts = set()
|
|
|
349
349
|
__wildCharacters = ['*','?','x']
|
|
350
350
|
_no_env = DEFAULT_NO_ENV
|
|
351
351
|
_env_file = DEFAULT_ENV_FILE
|
|
352
|
-
__globalUnavailableHosts =
|
|
352
|
+
__globalUnavailableHosts = dict()
|
|
353
353
|
__ipmiiInterfaceIPPrefix = DEFAULT_IPMI_INTERFACE_IP_PREFIX
|
|
354
354
|
__keyPressesIn = [[]]
|
|
355
355
|
_emo = False
|
|
@@ -389,6 +389,7 @@ if __curses_available:
|
|
|
389
389
|
#%% ------------ Exportable Help Functions ----------------
|
|
390
390
|
# check if command sshpass is available
|
|
391
391
|
_binPaths = {}
|
|
392
|
+
_binCalled = set(['sshpass', 'ssh', 'scp', 'ipmitool','rsync','sh','ssh-copy-id'])
|
|
392
393
|
def check_path(program_name):
|
|
393
394
|
global __configs_from_file
|
|
394
395
|
global _binPaths
|
|
@@ -403,7 +404,7 @@ def check_path(program_name):
|
|
|
403
404
|
return True
|
|
404
405
|
return False
|
|
405
406
|
|
|
406
|
-
[check_path(program) for program in
|
|
407
|
+
[check_path(program) for program in _binCalled]
|
|
407
408
|
|
|
408
409
|
def find_ssh_key_file(searchPath = DEDAULT_SSH_KEY_SEARCH_PATH):
|
|
409
410
|
'''
|
|
@@ -2234,7 +2235,7 @@ def generate_output(hosts, usejson = False, greppable = False):
|
|
|
2234
2235
|
hostPrintOut = f" Command:\n {host['command']}\n"
|
|
2235
2236
|
hostPrintOut += " stdout:\n "+'\n '.join(host['stdout'])
|
|
2236
2237
|
if host['stderr']:
|
|
2237
|
-
if host['stderr'][0].strip().startswith('ssh: connect to host '):
|
|
2238
|
+
if host['stderr'][0].strip().startswith('ssh: connect to host ') and host['stderr'][0].strip().endswith('Connection refused'):
|
|
2238
2239
|
host['stderr'][0] = 'SSH not reachable!'
|
|
2239
2240
|
elif host['stderr'][-1].strip().endswith('Connection timed out'):
|
|
2240
2241
|
host['stderr'][-1] = 'SSH connection timed out!'
|
|
@@ -2286,7 +2287,7 @@ def print_output(hosts,usejson = False,quiet = False,greppable = False):
|
|
|
2286
2287
|
|
|
2287
2288
|
#%% ------------ Run / Process Hosts Block ----------------
|
|
2288
2289
|
def processRunOnHosts(timeout, password, max_connections, hosts, returnUnfinished, no_watch, json, called, greppable,
|
|
2289
|
-
unavailableHosts,willUpdateUnreachableHosts,curses_min_char_len = DEFAULT_CURSES_MINIMUM_CHAR_LEN,
|
|
2290
|
+
unavailableHosts:dict,willUpdateUnreachableHosts,curses_min_char_len = DEFAULT_CURSES_MINIMUM_CHAR_LEN,
|
|
2290
2291
|
curses_min_line_len = DEFAULT_CURSES_MINIMUM_LINE_LEN,single_window = DEFAULT_SINGLE_WINDOW,
|
|
2291
2292
|
unavailable_host_expiry = DEFAULT_UNAVAILABLE_HOST_EXPIRY):
|
|
2292
2293
|
global __globalUnavailableHosts
|
|
@@ -2316,45 +2317,47 @@ def processRunOnHosts(timeout, password, max_connections, hosts, returnUnfinishe
|
|
|
2316
2317
|
thread.join(timeout=3)
|
|
2317
2318
|
# update the unavailable hosts and global unavailable hosts
|
|
2318
2319
|
if willUpdateUnreachableHosts:
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2320
|
+
availableHosts = set()
|
|
2321
|
+
for host in hosts:
|
|
2322
|
+
if host.stderr and ('No route to host' in host.stderr[0].strip() or 'Connection timed out' in host.stderr[0].strip() or (host.stderr[-1].strip().startswith('Timeout!') and host.returncode == 124)):
|
|
2323
|
+
unavailableHosts[host.name] = int(time.monotonic())
|
|
2324
|
+
__globalUnavailableHosts[host.name] = int(time.monotonic())
|
|
2325
|
+
else:
|
|
2326
|
+
availableHosts.add(host.name)
|
|
2327
|
+
if host.name in unavailableHosts:
|
|
2328
|
+
del unavailableHosts[host.name]
|
|
2329
|
+
if host.name in __globalUnavailableHosts:
|
|
2330
|
+
del __globalUnavailableHosts[host.name]
|
|
2323
2331
|
if __DEBUG_MODE:
|
|
2324
2332
|
print(f'Unreachable hosts: {unavailableHosts}')
|
|
2325
|
-
__globalUnavailableHosts.update(unavailableHosts)
|
|
2326
|
-
|
|
2327
|
-
# os.environ['__multiSSH3_UNAVAILABLE_HOSTS'] = ','.join(unavailableHosts)
|
|
2328
|
-
# create a temporary file to store the unavailable hosts
|
|
2329
2333
|
try:
|
|
2330
2334
|
# check for the old content, only update if the new content is different
|
|
2331
|
-
if not os.path.exists(os.path.join(tempfile.gettempdir(),'
|
|
2332
|
-
with open(os.path.join(tempfile.gettempdir(),'
|
|
2333
|
-
f.
|
|
2335
|
+
if not os.path.exists(os.path.join(tempfile.gettempdir(),f'__{getpass.getuser()}_multiSSH3_UNAVAILABLE_HOSTS.csv')):
|
|
2336
|
+
with open(os.path.join(tempfile.gettempdir(),f'__{getpass.getuser()}_multiSSH3_UNAVAILABLE_HOSTS.csv'),'w') as f:
|
|
2337
|
+
f.writelines(f'{host},{expTime}' for host,expTime in unavailableHosts.values())
|
|
2334
2338
|
else:
|
|
2335
2339
|
oldDic = {}
|
|
2336
2340
|
try:
|
|
2337
|
-
with open(os.path.join(tempfile.gettempdir(),'
|
|
2341
|
+
with open(os.path.join(tempfile.gettempdir(),f'__{getpass.getuser()}_multiSSH3_UNAVAILABLE_HOSTS.csv'),'r') as f:
|
|
2338
2342
|
for line in f:
|
|
2339
2343
|
line = line.strip()
|
|
2340
2344
|
if line and ',' in line and len(line.split(',')) >= 2 and line.split(',')[0] and line.split(',')[1].isdigit():
|
|
2341
|
-
|
|
2345
|
+
hostname = line.split(',')[0]
|
|
2346
|
+
expireTime = int(line.split(',')[1])
|
|
2347
|
+
if expireTime < time.monotonic() and hostname not in availableHosts:
|
|
2348
|
+
oldDic[hostname] = expireTime
|
|
2342
2349
|
except:
|
|
2343
2350
|
pass
|
|
2344
|
-
for key in list(oldDic.keys()):
|
|
2345
|
-
if key in reachableHosts or time.monotonic() < oldDic[key] or time.monotonic() - oldDic[key] > unavailable_host_expiry:
|
|
2346
|
-
del oldDic[key]
|
|
2347
2351
|
# add new entries
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
oldDic[host] = int(time.monotonic ())
|
|
2351
|
-
with open(os.path.join(tempfile.gettempdir(),'__multiSSH3_UNAVAILABLE_HOSTS.csv.new'),'w') as f:
|
|
2352
|
+
oldDic.update(unavailableHosts)
|
|
2353
|
+
with open(os.path.join(tempfile.gettempdir(),getpass.getuser()+'__multiSSH3_UNAVAILABLE_HOSTS.csv.new'),'w') as f:
|
|
2352
2354
|
for key, value in oldDic.items():
|
|
2353
2355
|
f.write(f'{key},{value}\n')
|
|
2354
|
-
os.replace(os.path.join(tempfile.gettempdir(),'__multiSSH3_UNAVAILABLE_HOSTS.csv.new'),os.path.join(tempfile.gettempdir(),'
|
|
2355
|
-
|
|
2356
|
+
os.replace(os.path.join(tempfile.gettempdir(),getpass.getuser()+'__multiSSH3_UNAVAILABLE_HOSTS.csv.new'),os.path.join(tempfile.gettempdir(),f'__{getpass.getuser()}_multiSSH3_UNAVAILABLE_HOSTS.csv'))
|
|
2356
2357
|
except Exception as e:
|
|
2357
2358
|
eprint(f'Error writing to temporary file: {e!r}')
|
|
2359
|
+
import traceback
|
|
2360
|
+
eprint(traceback.format_exc())
|
|
2358
2361
|
|
|
2359
2362
|
# print the output, if the output of multiple hosts are the same, we aggragate them
|
|
2360
2363
|
if not called:
|
|
@@ -2565,27 +2568,29 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
|
|
|
2565
2568
|
record_command_history(locals())
|
|
2566
2569
|
if error_only:
|
|
2567
2570
|
__global_suppress_printout = True
|
|
2568
|
-
if os.path.exists(os.path.join(tempfile.gettempdir(),'
|
|
2571
|
+
if os.path.exists(os.path.join(tempfile.gettempdir(),f'__{getpass.getuser()}_multiSSH3_UNAVAILABLE_HOSTS.csv')):
|
|
2569
2572
|
if unavailable_host_expiry <= 0:
|
|
2570
2573
|
unavailable_host_expiry = 10
|
|
2571
2574
|
try:
|
|
2572
2575
|
readed = False
|
|
2573
|
-
if 0 < time.time() - os.path.getmtime(os.path.join(tempfile.gettempdir(),'
|
|
2576
|
+
if 0 < time.time() - os.path.getmtime(os.path.join(tempfile.gettempdir(),f'__{getpass.getuser()}_multiSSH3_UNAVAILABLE_HOSTS.csv')) < unavailable_host_expiry:
|
|
2574
2577
|
|
|
2575
|
-
with open(os.path.join(tempfile.gettempdir(),'
|
|
2578
|
+
with open(os.path.join(tempfile.gettempdir(),f'__{getpass.getuser()}_multiSSH3_UNAVAILABLE_HOSTS.csv'),'r') as f:
|
|
2576
2579
|
for line in f:
|
|
2577
2580
|
line = line.strip()
|
|
2578
2581
|
if line and ',' in line and len(line.split(',')) >= 2 and line.split(',')[0] and line.split(',')[1].isdigit():
|
|
2579
|
-
|
|
2580
|
-
|
|
2582
|
+
hostname = line.split(',')[0]
|
|
2583
|
+
expireTime = int(line.split(',')[1])
|
|
2584
|
+
if expireTime < time.monotonic() and expireTime + unavailable_host_expiry > time.monotonic():
|
|
2585
|
+
__globalUnavailableHosts[hostname] = expireTime
|
|
2581
2586
|
readed = True
|
|
2582
2587
|
if readed and not __global_suppress_printout:
|
|
2583
|
-
eprint(f"Read unavailable hosts from the file {os.path.join(tempfile.gettempdir(),'
|
|
2588
|
+
eprint(f"Read unavailable hosts from the file {os.path.join(tempfile.gettempdir(),f'__{getpass.getuser()}_multiSSH3_UNAVAILABLE_HOSTS.csv')}")
|
|
2584
2589
|
except Exception as e:
|
|
2585
|
-
eprint(f"Warning: Unable to read the unavailable hosts from the file {os.path.join(tempfile.gettempdir(),'
|
|
2590
|
+
eprint(f"Warning: Unable to read the unavailable hosts from the file {os.path.join(tempfile.gettempdir(),f'__{getpass.getuser()}_multiSSH3_UNAVAILABLE_HOSTS.csv')!r}")
|
|
2586
2591
|
eprint(str(e))
|
|
2587
2592
|
elif '__multiSSH3_UNAVAILABLE_HOSTS' in readEnvFromFile():
|
|
2588
|
-
__globalUnavailableHosts.update(readEnvFromFile()['__multiSSH3_UNAVAILABLE_HOSTS'].split(','))
|
|
2593
|
+
__globalUnavailableHosts.update({host: int(time.monotonic()) for host in readEnvFromFile()['__multiSSH3_UNAVAILABLE_HOSTS'].split(',') if host})
|
|
2589
2594
|
if not max_connections:
|
|
2590
2595
|
max_connections = 4 * os.cpu_count()
|
|
2591
2596
|
elif max_connections == 0:
|
|
@@ -2618,7 +2623,7 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
|
|
|
2618
2623
|
if skipUnreachable:
|
|
2619
2624
|
unavailableHosts = __globalUnavailableHosts
|
|
2620
2625
|
else:
|
|
2621
|
-
unavailableHosts =
|
|
2626
|
+
unavailableHosts = dict()
|
|
2622
2627
|
# set global input to empty
|
|
2623
2628
|
__keyPressesIn = [[]]
|
|
2624
2629
|
__global_suppress_printout = True
|
|
@@ -2627,7 +2632,7 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
|
|
|
2627
2632
|
if skipUnreachable:
|
|
2628
2633
|
unavailableHosts = __globalUnavailableHosts
|
|
2629
2634
|
else:
|
|
2630
|
-
unavailableHosts =
|
|
2635
|
+
unavailableHosts = dict()
|
|
2631
2636
|
skipUnreachable = True
|
|
2632
2637
|
if quiet:
|
|
2633
2638
|
__global_suppress_printout = True
|
|
@@ -2906,7 +2911,8 @@ def main():
|
|
|
2906
2911
|
# We handle the signal
|
|
2907
2912
|
signal.signal(signal.SIGINT, signal_handler)
|
|
2908
2913
|
# We parse the arguments
|
|
2909
|
-
parser = argparse.ArgumentParser(description=f'Run a command on multiple hosts, Use #HOST# or #HOSTNAME# to replace the host name in the command. Config file chain: {CONFIG_FILE_CHAIN!r}'
|
|
2914
|
+
parser = argparse.ArgumentParser(description=f'Run a command on multiple hosts, Use #HOST# or #HOSTNAME# to replace the host name in the command. Config file chain: {CONFIG_FILE_CHAIN!r}',
|
|
2915
|
+
epilog=f'Found bins: {list(_binPaths.values())}\n Missing bins: {_binCalled - set(_binPaths.keys())}')
|
|
2910
2916
|
parser.add_argument('hosts', metavar='hosts', type=str, nargs='?', help=f'Hosts to run the command on, use "," to seperate hosts. (default: {DEFAULT_HOSTS})',default=DEFAULT_HOSTS)
|
|
2911
2917
|
parser.add_argument('commands', metavar='commands', type=str, nargs='*',default=None,help='the command to run on the hosts / the destination of the files #HOST# or #HOSTNAME# will be replaced with the host name.')
|
|
2912
2918
|
parser.add_argument('-u','--username', type=str,help=f'The general username to use to connect to the hosts. Will get overwrote by individual username@host if specified. (default: {DEFAULT_USERNAME})',default=DEFAULT_USERNAME)
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
multiSSH3.py,sha256=Fh69JiPpBe27zs05RmXa8z1CQmaNXh7hVIZl_RN2iGY,144513
|
|
2
|
+
multissh3-5.69.dist-info/METADATA,sha256=2PFFZiQ4Jck3s1L0DjzHtNerxXPGaxe3maZ8_UOonhU,18093
|
|
3
|
+
multissh3-5.69.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
|
|
4
|
+
multissh3-5.69.dist-info/entry_points.txt,sha256=xi2rWWNfmHx6gS8Mmx0rZL2KZz6XWBYP3DWBpWAnnZ0,143
|
|
5
|
+
multissh3-5.69.dist-info/top_level.txt,sha256=tUwttxlnpLkZorSsroIprNo41lYSxjd2ASuL8-EJIJw,10
|
|
6
|
+
multissh3-5.69.dist-info/RECORD,,
|
multissh3-5.67.dist-info/RECORD
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
multiSSH3.py,sha256=NW0UP3cCzAK6hPDvj5mFc3vc2lTNF0jfqJM_HTdqxKE,144034
|
|
2
|
-
multissh3-5.67.dist-info/METADATA,sha256=CLiHx9VQdxFmnI92-h7aPaMFawBDCuGRC_fnndAkjc8,18093
|
|
3
|
-
multissh3-5.67.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
|
|
4
|
-
multissh3-5.67.dist-info/entry_points.txt,sha256=xi2rWWNfmHx6gS8Mmx0rZL2KZz6XWBYP3DWBpWAnnZ0,143
|
|
5
|
-
multissh3-5.67.dist-info/top_level.txt,sha256=tUwttxlnpLkZorSsroIprNo41lYSxjd2ASuL8-EJIJw,10
|
|
6
|
-
multissh3-5.67.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|