multiSSH3 5.93__tar.gz → 5.94__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.
Potentially problematic release.
This version of multiSSH3 might be problematic. Click here for more details.
- {multissh3-5.93 → multissh3-5.94}/PKG-INFO +1 -1
- {multissh3-5.93 → multissh3-5.94}/README.md +0 -0
- {multissh3-5.93 → multissh3-5.94}/multiSSH3.egg-info/PKG-INFO +1 -1
- {multissh3-5.93 → multissh3-5.94}/multiSSH3.py +77 -35
- {multissh3-5.93 → multissh3-5.94}/setup.py +0 -0
- {multissh3-5.93 → multissh3-5.94}/test/test.py +0 -0
- {multissh3-5.93 → multissh3-5.94}/test/testCurses.py +0 -0
- {multissh3-5.93 → multissh3-5.94}/test/testCursesOld.py +0 -0
- {multissh3-5.93 → multissh3-5.94}/test/testPerfCompact.py +0 -0
- {multissh3-5.93 → multissh3-5.94}/test/testPerfExpand.py +0 -0
- {multissh3-5.93 → multissh3-5.94}/multiSSH3.egg-info/SOURCES.txt +0 -0
- {multissh3-5.93 → multissh3-5.94}/multiSSH3.egg-info/dependency_links.txt +0 -0
- {multissh3-5.93 → multissh3-5.94}/multiSSH3.egg-info/entry_points.txt +0 -0
- {multissh3-5.93 → multissh3-5.94}/multiSSH3.egg-info/requires.txt +0 -0
- {multissh3-5.93 → multissh3-5.94}/multiSSH3.egg-info/top_level.txt +0 -0
- {multissh3-5.93 → multissh3-5.94}/setup.cfg +0 -0
|
File without changes
|
|
@@ -84,10 +84,10 @@ except Exception:
|
|
|
84
84
|
print('Warning: functools.lru_cache is not available, multiSSH3 will run slower without cache.',file=sys.stderr)
|
|
85
85
|
def cache_decorator(func):
|
|
86
86
|
return func
|
|
87
|
-
version = '5.
|
|
87
|
+
version = '5.94'
|
|
88
88
|
VERSION = version
|
|
89
89
|
__version__ = version
|
|
90
|
-
COMMIT_DATE = '2025-10-
|
|
90
|
+
COMMIT_DATE = '2025-10-21'
|
|
91
91
|
|
|
92
92
|
CONFIG_FILE_CHAIN = ['./multiSSH3.config.json',
|
|
93
93
|
'~/multiSSH3.config.json',
|
|
@@ -345,7 +345,16 @@ DEFAULT_ERROR_ONLY = False
|
|
|
345
345
|
DEFAULT_NO_OUTPUT = False
|
|
346
346
|
DEFAULT_RETURN_ZERO = False
|
|
347
347
|
DEFAULT_NO_ENV = False
|
|
348
|
-
DEFAULT_ENV_FILE = '
|
|
348
|
+
DEFAULT_ENV_FILE = ''
|
|
349
|
+
DEFAULT_ENV_FILES = ['/etc/profile.d/hosts.sh',
|
|
350
|
+
'~/.bashrc',
|
|
351
|
+
'~/.zshrc',
|
|
352
|
+
'~/host.env',
|
|
353
|
+
'~/hosts.env',
|
|
354
|
+
'.env',
|
|
355
|
+
'host.env',
|
|
356
|
+
'hosts.env',
|
|
357
|
+
]
|
|
349
358
|
DEFAULT_NO_HISTORY = False
|
|
350
359
|
DEFAULT_HISTORY_FILE = '~/.mssh_history'
|
|
351
360
|
DEFAULT_MAX_CONNECTIONS = 4 * os.cpu_count()
|
|
@@ -386,6 +395,9 @@ if __ERROR_MESSAGES_TO_IGNORE_REGEX:
|
|
|
386
395
|
__ERROR_MESSAGES_TO_IGNORE_REGEX = re.compile(__ERROR_MESSAGES_TO_IGNORE_REGEX)
|
|
387
396
|
else:
|
|
388
397
|
__ERROR_MESSAGES_TO_IGNORE_REGEX = re.compile('|'.join(ERROR_MESSAGES_TO_IGNORE))
|
|
398
|
+
if DEFAULT_ENV_FILE:
|
|
399
|
+
if DEFAULT_ENV_FILE not in DEFAULT_ENV_FILES:
|
|
400
|
+
DEFAULT_ENV_FILES.append(DEFAULT_ENV_FILE)
|
|
389
401
|
|
|
390
402
|
#%% Load mssh Functional Global Variables
|
|
391
403
|
__global_suppress_printout = False
|
|
@@ -393,7 +405,7 @@ __mainReturnCode = 0
|
|
|
393
405
|
__failedHosts = set()
|
|
394
406
|
__wildCharacters = ['*','?','x']
|
|
395
407
|
_no_env = DEFAULT_NO_ENV
|
|
396
|
-
|
|
408
|
+
_env_files = DEFAULT_ENV_FILES
|
|
397
409
|
__globalUnavailableHosts = dict()
|
|
398
410
|
__ipmiiInterfaceIPPrefix = DEFAULT_IPMI_INTERFACE_IP_PREFIX
|
|
399
411
|
__keyPressesIn = [[]]
|
|
@@ -475,35 +487,65 @@ def find_ssh_key_file(searchPath = DEDAULT_SSH_KEY_SEARCH_PATH):
|
|
|
475
487
|
return None
|
|
476
488
|
|
|
477
489
|
@cache_decorator
|
|
478
|
-
def readEnvFromFile(
|
|
490
|
+
def readEnvFromFile():
|
|
479
491
|
'''
|
|
480
492
|
Read the environment variables from env_file
|
|
481
493
|
Returns:
|
|
482
494
|
dict: A dictionary of environment variables
|
|
483
495
|
'''
|
|
484
|
-
global
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
envf =
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
if line.startswith('#') or not line
|
|
496
|
+
global _env_files
|
|
497
|
+
global _no_env
|
|
498
|
+
envfs = _env_files if _env_files else DEFAULT_ENV_FILES
|
|
499
|
+
translator = str.maketrans('&|"', ';;\'')
|
|
500
|
+
replacement_re = re.compile(r'\$(?:[A-Za-z_]\w*|\{[A-Za-z_]\w*\})')
|
|
501
|
+
environemnt = {}
|
|
502
|
+
scrubCounter = 0
|
|
503
|
+
for envf in envfs:
|
|
504
|
+
envf = os.path.expanduser(os.path.expandvars(envf))
|
|
505
|
+
if os.path.exists(envf):
|
|
506
|
+
with open(envf,'r') as f:
|
|
507
|
+
lines = f.readlines()
|
|
508
|
+
for line in lines:
|
|
509
|
+
line = line.strip()
|
|
510
|
+
if not line or line.startswith('#') or '=' not in line:
|
|
499
511
|
continue
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
512
|
+
line = line.translate(translator)
|
|
513
|
+
commands = re.split(r";(?=(?:[^']*'[^']*')*[^']*$)", line)
|
|
514
|
+
for command in commands:
|
|
515
|
+
if not command or command.startswith('#') or '=' not in command or command.startswith('alias '):
|
|
516
|
+
continue
|
|
517
|
+
fields = re.split(r" (?=(?:[^']*'[^']*')*[^']*$)", command)
|
|
518
|
+
for field in fields:
|
|
519
|
+
try:
|
|
520
|
+
if field.startswith('export '):
|
|
521
|
+
field = field.replace('export ', '', 1).strip()
|
|
522
|
+
if not field or field.startswith('#') or '=' not in field:
|
|
523
|
+
continue
|
|
524
|
+
key, _, values = field.partition('=')
|
|
525
|
+
key = key.strip().strip("'")
|
|
526
|
+
values = values.strip().strip("'")
|
|
527
|
+
if '$' in values:
|
|
528
|
+
scrubCounter += 16
|
|
529
|
+
if key and values and key != values:
|
|
530
|
+
environemnt[key] = values
|
|
531
|
+
except Exception:
|
|
532
|
+
continue
|
|
533
|
+
while scrubCounter:
|
|
534
|
+
scrubCounter -= 1
|
|
535
|
+
found = False
|
|
536
|
+
for key, value in environemnt.items():
|
|
537
|
+
if '$' in value:
|
|
538
|
+
for match in replacement_re.findall(value):
|
|
539
|
+
ref_key = match.strip('${}')
|
|
540
|
+
ref_value = environemnt.get(ref_key) if ref_key != key else None
|
|
541
|
+
if not ref_value and not _no_env:
|
|
542
|
+
ref_value = os.environ.get(ref_key)
|
|
543
|
+
if ref_value:
|
|
544
|
+
environemnt[key] = value.replace(match, ref_value)
|
|
545
|
+
found = True
|
|
546
|
+
if not found:
|
|
547
|
+
break
|
|
548
|
+
return environemnt
|
|
507
549
|
|
|
508
550
|
def replace_magic_strings(string,keys,value,case_sensitive=False):
|
|
509
551
|
'''
|
|
@@ -3099,7 +3141,7 @@ def __formCommandArgStr(oneonone = DEFAULT_ONE_ON_ONE, timeout = DEFAULT_TIMEOUT
|
|
|
3099
3141
|
no_env=DEFAULT_NO_ENV,greppable=DEFAULT_GREPPABLE_MODE,skip_hosts = DEFAULT_SKIP_HOSTS,
|
|
3100
3142
|
file_sync = False, error_only = DEFAULT_ERROR_ONLY, identity_file = DEFAULT_IDENTITY_FILE,
|
|
3101
3143
|
copy_id = False, unavailable_host_expiry = DEFAULT_UNAVAILABLE_HOST_EXPIRY, no_history = DEFAULT_NO_HISTORY,
|
|
3102
|
-
history_file = DEFAULT_HISTORY_FILE, env_file =
|
|
3144
|
+
history_file = DEFAULT_HISTORY_FILE, env_file = DEFAULT_ENV_FILES,
|
|
3103
3145
|
repeat = DEFAULT_REPEAT,interval = DEFAULT_INTERVAL,
|
|
3104
3146
|
shortend = False) -> str:
|
|
3105
3147
|
argsList = []
|
|
@@ -3143,8 +3185,8 @@ def __formCommandArgStr(oneonone = DEFAULT_ONE_ON_ONE, timeout = DEFAULT_TIMEOUT
|
|
|
3143
3185
|
argsList.append(f'--unavailable_host_expiry={unavailable_host_expiry}' if not shortend else f'-uhe={unavailable_host_expiry}')
|
|
3144
3186
|
if no_env:
|
|
3145
3187
|
argsList.append('--no_env')
|
|
3146
|
-
if env_file and env_file !=
|
|
3147
|
-
argsList.
|
|
3188
|
+
if env_file and env_file != DEFAULT_ENV_FILES:
|
|
3189
|
+
argsList.extend([f'--env_file="{ef}"' for ef in env_file] if not shortend else [f'-ef="{ef}"' for ef in env_file])
|
|
3148
3190
|
if no_history:
|
|
3149
3191
|
argsList.append('--no_history' if not shortend else '-nh')
|
|
3150
3192
|
if history_file and history_file != DEFAULT_HISTORY_FILE:
|
|
@@ -3167,7 +3209,7 @@ def getStrCommand(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAULT_ONE_O
|
|
|
3167
3209
|
skip_hosts = DEFAULT_SKIP_HOSTS, curses_min_char_len = DEFAULT_CURSES_MINIMUM_CHAR_LEN, curses_min_line_len = DEFAULT_CURSES_MINIMUM_LINE_LEN,
|
|
3168
3210
|
single_window = DEFAULT_SINGLE_WINDOW,file_sync = False,error_only = DEFAULT_ERROR_ONLY, identity_file = DEFAULT_IDENTITY_FILE,
|
|
3169
3211
|
copy_id = False, unavailable_host_expiry = DEFAULT_UNAVAILABLE_HOST_EXPIRY,no_history = DEFAULT_NO_HISTORY,
|
|
3170
|
-
history_file = DEFAULT_HISTORY_FILE, env_file =
|
|
3212
|
+
history_file = DEFAULT_HISTORY_FILE, env_file = DEFAULT_ENV_FILES,
|
|
3171
3213
|
repeat = DEFAULT_REPEAT,interval = DEFAULT_INTERVAL,
|
|
3172
3214
|
shortend = False,tabSeperated = False):
|
|
3173
3215
|
_ = called
|
|
@@ -3579,7 +3621,7 @@ def generate_default_config(args):
|
|
|
3579
3621
|
'DEFAULT_NO_OUTPUT': args.no_output,
|
|
3580
3622
|
'DEFAULT_RETURN_ZERO': args.return_zero,
|
|
3581
3623
|
'DEFAULT_NO_ENV': args.no_env,
|
|
3582
|
-
'
|
|
3624
|
+
'DEFAULT_ENV_FILES': args.env_file,
|
|
3583
3625
|
'DEFAULT_NO_HISTORY': args.no_history,
|
|
3584
3626
|
'DEFAULT_HISTORY_FILE': args.history_file,
|
|
3585
3627
|
'DEFAULT_MAX_CONNECTIONS': args.max_connections if args.max_connections != 4 * os.cpu_count() else None,
|
|
@@ -3668,7 +3710,7 @@ def get_parser():
|
|
|
3668
3710
|
parser.add_argument('-Q',"-no","--no_output", action='store_true', help=f"Do not print the output. (default: {DEFAULT_NO_OUTPUT})", default=DEFAULT_NO_OUTPUT)
|
|
3669
3711
|
parser.add_argument('-Z','-rz','--return_zero', action='store_true', help=f"Return 0 even if there are errors. (default: {DEFAULT_RETURN_ZERO})", default=DEFAULT_RETURN_ZERO)
|
|
3670
3712
|
parser.add_argument('-C','--no_env', action='store_true', help=f'Do not load the command line environment variables. (default: {DEFAULT_NO_ENV})', default=DEFAULT_NO_ENV)
|
|
3671
|
-
parser.add_argument("--env_file",
|
|
3713
|
+
parser.add_argument("--env_file", action='append', help=f"The files to load the mssh file based environment variables from. Can specify multiple. Load first to last. ( Still work with --no_env ) (default: {DEFAULT_ENV_FILES})", default=DEFAULT_ENV_FILES)
|
|
3672
3714
|
parser.add_argument("-m","--max_connections", type=int, help="Max number of connections to use (default: 4 * cpu_count)", default=DEFAULT_MAX_CONNECTIONS)
|
|
3673
3715
|
parser.add_argument("-j","--json", action='store_true', help=F"Output in json format. (default: {DEFAULT_JSON_MODE})", default=DEFAULT_JSON_MODE)
|
|
3674
3716
|
parser.add_argument('-w',"--success_hosts", action='store_true', help=f"Output the hosts that succeeded in summary as well. (default: {DEFAULT_PRINT_SUCCESS_HOSTS})", default=DEFAULT_PRINT_SUCCESS_HOSTS)
|
|
@@ -3783,7 +3825,7 @@ def process_keys(args):
|
|
|
3783
3825
|
def set_global_with_args(args):
|
|
3784
3826
|
global _emo
|
|
3785
3827
|
global __ipmiiInterfaceIPPrefix
|
|
3786
|
-
global
|
|
3828
|
+
global _env_files
|
|
3787
3829
|
global __DEBUG_MODE
|
|
3788
3830
|
global __configs_from_file
|
|
3789
3831
|
global _encoding
|
|
@@ -3794,7 +3836,7 @@ def set_global_with_args(args):
|
|
|
3794
3836
|
global FORCE_TRUECOLOR
|
|
3795
3837
|
_emo = False
|
|
3796
3838
|
__ipmiiInterfaceIPPrefix = args.ipmi_interface_ip_prefix
|
|
3797
|
-
|
|
3839
|
+
_env_files = args.env_file
|
|
3798
3840
|
__DEBUG_MODE = args.debug
|
|
3799
3841
|
_encoding = args.encoding
|
|
3800
3842
|
if args.return_zero:
|
|
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
|