multiSSH3 5.50__tar.gz → 5.52__tar.gz
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.
- {multissh3-5.50 → multissh3-5.52}/PKG-INFO +1 -1
- {multissh3-5.50 → multissh3-5.52}/multiSSH3.egg-info/PKG-INFO +1 -1
- {multissh3-5.50 → multissh3-5.52}/multiSSH3.py +90 -49
- {multissh3-5.50 → multissh3-5.52}/README.md +0 -0
- {multissh3-5.50 → multissh3-5.52}/multiSSH3.egg-info/SOURCES.txt +0 -0
- {multissh3-5.50 → multissh3-5.52}/multiSSH3.egg-info/dependency_links.txt +0 -0
- {multissh3-5.50 → multissh3-5.52}/multiSSH3.egg-info/entry_points.txt +0 -0
- {multissh3-5.50 → multissh3-5.52}/multiSSH3.egg-info/requires.txt +0 -0
- {multissh3-5.50 → multissh3-5.52}/multiSSH3.egg-info/top_level.txt +0 -0
- {multissh3-5.50 → multissh3-5.52}/setup.cfg +0 -0
- {multissh3-5.50 → multissh3-5.52}/setup.py +0 -0
- {multissh3-5.50 → multissh3-5.52}/test/test.py +0 -0
- {multissh3-5.50 → multissh3-5.52}/test/testCurses.py +0 -0
- {multissh3-5.50 → multissh3-5.52}/test/testCursesOld.py +0 -0
- {multissh3-5.50 → multissh3-5.52}/test/testPerfCompact.py +0 -0
- {multissh3-5.50 → multissh3-5.52}/test/testPerfExpand.py +0 -0
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.6"
|
|
4
|
+
# dependencies = [
|
|
5
|
+
# "argparse",
|
|
6
|
+
# "ipaddress",
|
|
7
|
+
# ]
|
|
8
|
+
# ///
|
|
2
9
|
__curses_available = False
|
|
3
10
|
__resource_lib_available = False
|
|
4
11
|
try:
|
|
@@ -47,10 +54,10 @@ except AttributeError:
|
|
|
47
54
|
# If neither is available, use a dummy decorator
|
|
48
55
|
def cache_decorator(func):
|
|
49
56
|
return func
|
|
50
|
-
version = '5.
|
|
57
|
+
version = '5.52'
|
|
51
58
|
VERSION = version
|
|
52
59
|
__version__ = version
|
|
53
|
-
COMMIT_DATE = '2025-01
|
|
60
|
+
COMMIT_DATE = '2025-03-01'
|
|
54
61
|
|
|
55
62
|
CONFIG_FILE_CHAIN = ['./multiSSH3.config.json',
|
|
56
63
|
'~/multiSSH3.config.json',
|
|
@@ -483,49 +490,69 @@ def replace_magic_strings(string,keys,value,case_sensitive=False):
|
|
|
483
490
|
string = re.sub(re.escape(key),value,string,flags=re.IGNORECASE)
|
|
484
491
|
return string
|
|
485
492
|
|
|
486
|
-
def pretty_format_table(data):
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
493
|
+
def pretty_format_table(data, delimiter = '\t',header = None):
|
|
494
|
+
version = 1.1
|
|
495
|
+
if not data:
|
|
496
|
+
return ''
|
|
497
|
+
if type(data) == str:
|
|
498
|
+
data = data.strip('\n').split('\n')
|
|
499
|
+
data = [line.split(delimiter) for line in data]
|
|
500
|
+
elif isinstance(data, dict):
|
|
501
|
+
# flatten the 2D dict to a list of lists
|
|
502
|
+
if isinstance(next(iter(data.values())), dict):
|
|
503
|
+
tempData = [['key'] + list(next(iter(data.values())).keys())]
|
|
504
|
+
tempData.extend( [[key] + list(value.values()) for key, value in data.items()])
|
|
505
|
+
data = tempData
|
|
506
|
+
else:
|
|
507
|
+
# it is a dict of lists
|
|
508
|
+
data = [[key] + list(value) for key, value in data.items()]
|
|
509
|
+
elif type(data) != list:
|
|
510
|
+
data = list(data)
|
|
511
|
+
# format the list into 2d list of list of strings
|
|
512
|
+
if isinstance(data[0], dict):
|
|
513
|
+
tempData = [data[0].keys()]
|
|
514
|
+
tempData.extend([list(item.values()) for item in data])
|
|
515
|
+
data = tempData
|
|
516
|
+
data = [[str(item) for item in row] for row in data]
|
|
517
|
+
num_cols = len(data[0])
|
|
518
|
+
col_widths = [0] * num_cols
|
|
519
|
+
# Calculate the maximum width of each column
|
|
520
|
+
for c in range(num_cols):
|
|
521
|
+
#col_widths[c] = max(len(row[c]) for row in data)
|
|
522
|
+
# handle ansii escape sequences
|
|
523
|
+
col_widths[c] = max(len(re.sub(r'\x1b\[[0-?]*[ -/]*[@-~]','',row[c])) for row in data)
|
|
524
|
+
# Build the row format string
|
|
525
|
+
row_format = ' | '.join('{{:<{}}}'.format(width) for width in col_widths)
|
|
526
|
+
# Print the header
|
|
527
|
+
if not header:
|
|
528
|
+
header = data[0]
|
|
529
|
+
outTable = []
|
|
530
|
+
outTable.append(row_format.format(*header))
|
|
531
|
+
outTable.append('-+-'.join('-' * width for width in col_widths))
|
|
532
|
+
for row in data[1:]:
|
|
533
|
+
# if the row is empty, print an divider
|
|
534
|
+
if not any(row):
|
|
535
|
+
outTable.append('-+-'.join('-' * width for width in col_widths))
|
|
536
|
+
else:
|
|
537
|
+
outTable.append(row_format.format(*row))
|
|
538
|
+
else:
|
|
539
|
+
# pad / truncate header to appropriate length
|
|
540
|
+
if isinstance(header,str):
|
|
541
|
+
header = header.split(delimiter)
|
|
542
|
+
if len(header) < num_cols:
|
|
543
|
+
header += ['']*(num_cols-len(header))
|
|
544
|
+
elif len(header) > num_cols:
|
|
545
|
+
header = header[:num_cols]
|
|
546
|
+
outTable = []
|
|
547
|
+
outTable.append(row_format.format(*header))
|
|
548
|
+
outTable.append('-+-'.join('-' * width for width in col_widths))
|
|
549
|
+
for row in data:
|
|
550
|
+
# if the row is empty, print an divider
|
|
551
|
+
if not any(row):
|
|
552
|
+
outTable.append('-+-'.join('-' * width for width in col_widths))
|
|
553
|
+
else:
|
|
554
|
+
outTable.append(row_format.format(*row))
|
|
555
|
+
return '\n'.join(outTable) + '\n'
|
|
529
556
|
|
|
530
557
|
# ------------ Compacting Hostnames ----------------
|
|
531
558
|
def __tokenize_hostname(hostname):
|
|
@@ -1359,7 +1386,7 @@ def run_command(host, sem, timeout=60,passwds=None, retry_limit = 5):
|
|
|
1359
1386
|
# Monitor the subprocess and terminate it after the timeout
|
|
1360
1387
|
host.lastUpdateTime = time.time()
|
|
1361
1388
|
timeoutLineAppended = False
|
|
1362
|
-
sleep_interval = 1.0e-
|
|
1389
|
+
sleep_interval = 1.0e-7 # 100 nanoseconds
|
|
1363
1390
|
while proc.poll() is None: # while the process is still running
|
|
1364
1391
|
if timeout > 0:
|
|
1365
1392
|
if time.time() - host.lastUpdateTime > timeout:
|
|
@@ -2264,13 +2291,27 @@ def print_output(hosts,usejson = False,quiet = False,greppable = False):
|
|
|
2264
2291
|
def processRunOnHosts(timeout, password, max_connections, hosts, returnUnfinished, nowatch, json, called, greppable,unavailableHosts,willUpdateUnreachableHosts,curses_min_char_len = DEFAULT_CURSES_MINIMUM_CHAR_LEN, curses_min_line_len = DEFAULT_CURSES_MINIMUM_LINE_LEN,single_window = DEFAULT_SINGLE_WINDOW):
|
|
2265
2292
|
global __globalUnavailableHosts
|
|
2266
2293
|
global _no_env
|
|
2294
|
+
sleep_interval = 1.0e-7 # 0.1 microseconds
|
|
2267
2295
|
threads = start_run_on_hosts(hosts, timeout=timeout,password=password,max_connections=max_connections)
|
|
2268
|
-
if __curses_available and not nowatch and threads and not returnUnfinished
|
|
2269
|
-
|
|
2296
|
+
if __curses_available and not nowatch and threads and not returnUnfinished and sys.stdout.isatty() and os.get_terminal_size() and os.get_terminal_size().columns > 10:
|
|
2297
|
+
total_sleeped = 0
|
|
2298
|
+
while any([host.returncode is None for host in hosts]):
|
|
2299
|
+
time.sleep(sleep_interval) # avoid busy-waiting
|
|
2300
|
+
total_sleeped += sleep_interval
|
|
2301
|
+
if sleep_interval < 0.001:
|
|
2302
|
+
sleep_interval *= 2
|
|
2303
|
+
elif sleep_interval < 0.01:
|
|
2304
|
+
sleep_interval *= 1.1
|
|
2305
|
+
if total_sleeped > 0.1:
|
|
2306
|
+
break
|
|
2307
|
+
if any([host.returncode is None for host in hosts]):
|
|
2308
|
+
curses.wrapper(curses_print, hosts, threads, min_char_len = curses_min_char_len, min_line_len = curses_min_line_len, single_window = single_window)
|
|
2270
2309
|
if not returnUnfinished:
|
|
2271
2310
|
# wait until all hosts have a return code
|
|
2272
2311
|
while any([host.returncode is None for host in hosts]):
|
|
2273
|
-
time.sleep(
|
|
2312
|
+
time.sleep(sleep_interval) # avoid busy-waiting
|
|
2313
|
+
if sleep_interval < 0.01:
|
|
2314
|
+
sleep_interval *= 1.1
|
|
2274
2315
|
for thread in threads:
|
|
2275
2316
|
thread.join(timeout=3)
|
|
2276
2317
|
# update the unavailable hosts and global unavailable hosts
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|