multiSSH3 5.78__tar.gz → 5.81__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.78 → multissh3-5.81}/PKG-INFO +1 -1
- {multissh3-5.78 → multissh3-5.81}/multiSSH3.egg-info/PKG-INFO +1 -1
- {multissh3-5.78 → multissh3-5.81}/multiSSH3.py +61 -49
- {multissh3-5.78 → multissh3-5.81}/README.md +0 -0
- {multissh3-5.78 → multissh3-5.81}/multiSSH3.egg-info/SOURCES.txt +0 -0
- {multissh3-5.78 → multissh3-5.81}/multiSSH3.egg-info/dependency_links.txt +0 -0
- {multissh3-5.78 → multissh3-5.81}/multiSSH3.egg-info/entry_points.txt +0 -0
- {multissh3-5.78 → multissh3-5.81}/multiSSH3.egg-info/requires.txt +0 -0
- {multissh3-5.78 → multissh3-5.81}/multiSSH3.egg-info/top_level.txt +0 -0
- {multissh3-5.78 → multissh3-5.81}/setup.cfg +0 -0
- {multissh3-5.78 → multissh3-5.81}/setup.py +0 -0
- {multissh3-5.78 → multissh3-5.81}/test/test.py +0 -0
- {multissh3-5.78 → multissh3-5.81}/test/testCurses.py +0 -0
- {multissh3-5.78 → multissh3-5.81}/test/testCursesOld.py +0 -0
- {multissh3-5.78 → multissh3-5.81}/test/testPerfCompact.py +0 -0
- {multissh3-5.78 → multissh3-5.81}/test/testPerfExpand.py +0 -0
|
@@ -55,10 +55,10 @@ except AttributeError:
|
|
|
55
55
|
# If neither is available, use a dummy decorator
|
|
56
56
|
def cache_decorator(func):
|
|
57
57
|
return func
|
|
58
|
-
version = '5.
|
|
58
|
+
version = '5.81'
|
|
59
59
|
VERSION = version
|
|
60
60
|
__version__ = version
|
|
61
|
-
COMMIT_DATE = '2025-
|
|
61
|
+
COMMIT_DATE = '2025-07-15'
|
|
62
62
|
|
|
63
63
|
CONFIG_FILE_CHAIN = ['./multiSSH3.config.json',
|
|
64
64
|
'~/multiSSH3.config.json',
|
|
@@ -934,7 +934,7 @@ def compact_hostnames(Hostnames,verify = True):
|
|
|
934
934
|
if not __global_suppress_printout:
|
|
935
935
|
eprint(f"Error compacting hostnames: {hostSet} -> {compact_hosts}")
|
|
936
936
|
compact_hosts = hostSet
|
|
937
|
-
return compact_hosts
|
|
937
|
+
return sorted(compact_hosts)
|
|
938
938
|
|
|
939
939
|
#%% ------------ Expanding Hostnames ----------------
|
|
940
940
|
@cache_decorator
|
|
@@ -1399,7 +1399,7 @@ def run_command(host, sem, timeout=60,passwds=None, retry_limit = 5):
|
|
|
1399
1399
|
else:
|
|
1400
1400
|
fileArgs = host.files + [f'{host.resolvedName}:{host.command}']
|
|
1401
1401
|
if useScp:
|
|
1402
|
-
formatedCMD = [_binPaths['scp'],'-
|
|
1402
|
+
formatedCMD = [_binPaths['scp'],'-rp'] + localExtraArgs + extraargs +['--']+fileArgs
|
|
1403
1403
|
else:
|
|
1404
1404
|
formatedCMD = [_binPaths['rsync'],'-ahlX','--partial','--inplace', '--info=name'] + rsyncLocalExtraArgs + extraargs +['--']+fileArgs
|
|
1405
1405
|
else:
|
|
@@ -2355,7 +2355,7 @@ def generate_output(hosts, usejson = False, greppable = False):
|
|
|
2355
2355
|
outputs.setdefault(hostPrintOut, set()).add(host['name'])
|
|
2356
2356
|
rtnStr = ''
|
|
2357
2357
|
for output, hostSet in outputs.items():
|
|
2358
|
-
compact_hosts =
|
|
2358
|
+
compact_hosts = compact_hostnames(hostSet)
|
|
2359
2359
|
rtnStr += '*'*80+'\n'
|
|
2360
2360
|
if __global_suppress_printout:
|
|
2361
2361
|
rtnStr += f'Abnormal returncode produced by {",".join(compact_hosts)}:\n'
|
|
@@ -2443,7 +2443,7 @@ def processRunOnHosts(timeout, password, max_connections, hosts, returnUnfinishe
|
|
|
2443
2443
|
# check for the old content, only update if the new content is different
|
|
2444
2444
|
if not os.path.exists(os.path.join(tempfile.gettempdir(),f'__{getpass.getuser()}_multiSSH3_UNAVAILABLE_HOSTS.csv')):
|
|
2445
2445
|
with open(os.path.join(tempfile.gettempdir(),f'__{getpass.getuser()}_multiSSH3_UNAVAILABLE_HOSTS.csv'),'w') as f:
|
|
2446
|
-
f.writelines(f'{host},{expTime}' for host,expTime in unavailableHosts.
|
|
2446
|
+
f.writelines(f'{host},{expTime}' for host,expTime in unavailableHosts.items())
|
|
2447
2447
|
else:
|
|
2448
2448
|
oldDic = {}
|
|
2449
2449
|
try:
|
|
@@ -2493,8 +2493,7 @@ def formHostStr(host) -> str:
|
|
|
2493
2493
|
if 'local_shell' in host:
|
|
2494
2494
|
host.remove('local_shell')
|
|
2495
2495
|
host.add('localhost')
|
|
2496
|
-
|
|
2497
|
-
return host
|
|
2496
|
+
return ','.join(compact_hostnames(host))
|
|
2498
2497
|
|
|
2499
2498
|
@cache_decorator
|
|
2500
2499
|
def __formCommandArgStr(oneonone = DEFAULT_ONE_ON_ONE, timeout = DEFAULT_TIMEOUT,password = DEFAULT_PASSWORD,
|
|
@@ -2568,7 +2567,7 @@ def getStrCommand(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAULT_ONE_O
|
|
|
2568
2567
|
history_file = history_file, env_file = env_file,
|
|
2569
2568
|
repeat = repeat,interval = interval,
|
|
2570
2569
|
shortend = shortend)
|
|
2571
|
-
commands = [command.replace('"', '\\"') for command in commands]
|
|
2570
|
+
commands = [command.replace('"', '\\"').replace('\n', '\\n').replace('\t', '\\t') for command in commands]
|
|
2572
2571
|
commandStr = '"' + '" "'.join(commands) + '"' if commands else ''
|
|
2573
2572
|
filePath = os.path.abspath(__file__)
|
|
2574
2573
|
programName = filePath if filePath else 'mssh'
|
|
@@ -2777,7 +2776,7 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
|
|
|
2777
2776
|
skipHostsDic = expand_hostnames(skipHostStr.split(','))
|
|
2778
2777
|
skipHostSet = set(skipHostsDic).union(skipHostsDic.values())
|
|
2779
2778
|
if skipHostSet:
|
|
2780
|
-
eprint(f"Skipping hosts: \"{' '.join(
|
|
2779
|
+
eprint(f"Skipping hosts: \"{' '.join(compact_hostnames(skipHostSet))}\"")
|
|
2781
2780
|
if copy_id:
|
|
2782
2781
|
if 'ssh-copy-id' in _binPaths:
|
|
2783
2782
|
# we will copy the id to the hosts
|
|
@@ -3008,23 +3007,9 @@ def write_default_config(args,CONFIG_FILE = None):
|
|
|
3008
3007
|
eprint(f'Printing the config file to stdout:')
|
|
3009
3008
|
print(json.dumps(__configs_from_file, indent=4))
|
|
3010
3009
|
|
|
3011
|
-
#%% ------------
|
|
3012
|
-
def
|
|
3013
|
-
global _emo
|
|
3014
|
-
global __global_suppress_printout
|
|
3015
|
-
global __mainReturnCode
|
|
3016
|
-
global __failedHosts
|
|
3017
|
-
global __ipmiiInterfaceIPPrefix
|
|
3010
|
+
#%% ------------ Argument Processing -----------------
|
|
3011
|
+
def get_parser():
|
|
3018
3012
|
global _binPaths
|
|
3019
|
-
global _env_file
|
|
3020
|
-
global __DEBUG_MODE
|
|
3021
|
-
global __configs_from_file
|
|
3022
|
-
global _encoding
|
|
3023
|
-
global __returnZero
|
|
3024
|
-
_emo = False
|
|
3025
|
-
# We handle the signal
|
|
3026
|
-
signal.signal(signal.SIGINT, signal_handler)
|
|
3027
|
-
# We parse the arguments
|
|
3028
3013
|
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}',
|
|
3029
3014
|
epilog=f'Found bins: {list(_binPaths.values())}\n Missing bins: {_binCalled - set(_binPaths.keys())}')
|
|
3030
3015
|
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)
|
|
@@ -3075,18 +3060,20 @@ def main():
|
|
|
3075
3060
|
parser.add_argument('--script', action='store_true', help='Run the command in script mode, short for -SCRIPT or --no_watch --skip_unreachable --no_env --no_history --greppable --error_only')
|
|
3076
3061
|
parser.add_argument('-e','--encoding', type=str, help=f'The encoding to use for the output. (default: {DEFAULT_ENCODING})', default=DEFAULT_ENCODING)
|
|
3077
3062
|
parser.add_argument("-V","--version", action='version', version=f'%(prog)s {version} @ {COMMIT_DATE} with [ {", ".join(_binPaths.keys())} ] by {AUTHOR} ({AUTHOR_EMAIL})')
|
|
3078
|
-
|
|
3079
|
-
# parser.add_argument('-u', '--user', metavar='user', type=str, nargs=1,
|
|
3080
|
-
# help='the user to use to connect to the hosts')
|
|
3081
|
-
#args = parser.parse_args()
|
|
3063
|
+
return parser
|
|
3082
3064
|
|
|
3065
|
+
def process_args(args = None):
|
|
3066
|
+
parser = get_parser()
|
|
3067
|
+
# We handle the signal
|
|
3068
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
3069
|
+
# We parse the arguments
|
|
3083
3070
|
# if python version is 3.7 or higher, use parse_intermixed_args
|
|
3084
3071
|
try:
|
|
3085
|
-
args = parser.parse_intermixed_args()
|
|
3072
|
+
args = parser.parse_intermixed_args(args)
|
|
3086
3073
|
except Exception :
|
|
3087
3074
|
#eprint(f"Error while parsing arguments: {e!r}")
|
|
3088
3075
|
# try to parse the arguments using parse_known_args
|
|
3089
|
-
args, unknown = parser.parse_known_args()
|
|
3076
|
+
args, unknown = parser.parse_known_args(args)
|
|
3090
3077
|
# if there are unknown arguments, we will try to parse them again using parse_args
|
|
3091
3078
|
if unknown:
|
|
3092
3079
|
eprint(f"Warning: Unknown arguments, treating all as commands: {unknown!r}")
|
|
@@ -3099,10 +3086,14 @@ def main():
|
|
|
3099
3086
|
args.no_history = True
|
|
3100
3087
|
args.greppable = True
|
|
3101
3088
|
args.error_only = True
|
|
3102
|
-
|
|
3103
|
-
if args.return_zero:
|
|
3104
|
-
__returnZero = True
|
|
3105
3089
|
|
|
3090
|
+
if args.unavailable_host_expiry <= 0:
|
|
3091
|
+
eprint(f"Warning: The unavailable host expiry time {args.unavailable_host_expiry} is less than 0, setting it to 10 seconds.")
|
|
3092
|
+
args.unavailable_host_expiry = 10
|
|
3093
|
+
return args
|
|
3094
|
+
|
|
3095
|
+
def process_config_file(args):
|
|
3096
|
+
global __configs_from_file
|
|
3106
3097
|
if args.generate_config_file or args.store_config_file:
|
|
3107
3098
|
if args.store_config_file:
|
|
3108
3099
|
configFileToWriteTo = args.store_config_file
|
|
@@ -3124,11 +3115,12 @@ def main():
|
|
|
3124
3115
|
__configs_from_file.update(load_config_file(os.path.expanduser(args.config_file)))
|
|
3125
3116
|
else:
|
|
3126
3117
|
eprint(f"Warning: Config file {args.config_file!r} not found, ignoring it.")
|
|
3118
|
+
return args
|
|
3127
3119
|
|
|
3128
|
-
_env_file = args.env_file
|
|
3129
|
-
__DEBUG_MODE = args.debug
|
|
3130
3120
|
# if there are more than 1 commands, and every command only consists of one word,
|
|
3131
3121
|
# we will ask the user to confirm if they want to run multiple commands or just one command.
|
|
3122
|
+
|
|
3123
|
+
def process_commands(args):
|
|
3132
3124
|
if not args.file and len(args.commands) > 1 and all([len(command.split()) == 1 for command in args.commands]):
|
|
3133
3125
|
eprint(f"Multiple one word command detected, what to do? (1/m/n)")
|
|
3134
3126
|
eprint(f"1: Run 1 command [{' '.join(args.commands)}] on all hosts ( default )")
|
|
@@ -3142,7 +3134,9 @@ def main():
|
|
|
3142
3134
|
eprint(f"\nRunning multiple commands: {', '.join(args.commands)!r} on all hosts")
|
|
3143
3135
|
else:
|
|
3144
3136
|
_exit_with_code(0, "Aborted by user, no commands to run")
|
|
3137
|
+
return args
|
|
3145
3138
|
|
|
3139
|
+
def process_keys(args):
|
|
3146
3140
|
if args.key or args.use_key:
|
|
3147
3141
|
if not args.key:
|
|
3148
3142
|
args.key = find_ssh_key_file()
|
|
@@ -3151,23 +3145,43 @@ def main():
|
|
|
3151
3145
|
args.key = find_ssh_key_file(args.key)
|
|
3152
3146
|
elif not os.path.exists(args.key):
|
|
3153
3147
|
eprint(f"Warning: Identity file {args.key!r} not found. Passing to ssh anyway. Proceed with caution.")
|
|
3148
|
+
return args
|
|
3154
3149
|
|
|
3150
|
+
|
|
3151
|
+
def set_global_with_args(args):
|
|
3152
|
+
global _emo
|
|
3153
|
+
global __ipmiiInterfaceIPPrefix
|
|
3154
|
+
global _env_file
|
|
3155
|
+
global __DEBUG_MODE
|
|
3156
|
+
global __configs_from_file
|
|
3157
|
+
global _encoding
|
|
3158
|
+
global __returnZero
|
|
3159
|
+
_emo = False
|
|
3155
3160
|
__ipmiiInterfaceIPPrefix = args.ipmi_interface_ip_prefix
|
|
3161
|
+
_env_file = args.env_file
|
|
3162
|
+
__DEBUG_MODE = args.debug
|
|
3163
|
+
_encoding = args.encoding
|
|
3164
|
+
if args.return_zero:
|
|
3165
|
+
__returnZero = True
|
|
3156
3166
|
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3167
|
+
#%% ------------ Wrapper Block ----------------
|
|
3168
|
+
def main():
|
|
3169
|
+
global __global_suppress_printout
|
|
3170
|
+
global __mainReturnCode
|
|
3171
|
+
global __failedHosts
|
|
3172
|
+
args = process_args()
|
|
3173
|
+
args = process_config_file(args)
|
|
3174
|
+
args = process_commands(args)
|
|
3175
|
+
args = process_keys(args)
|
|
3176
|
+
set_global_with_args(args)
|
|
3163
3177
|
|
|
3164
3178
|
if args.use_script_timeout:
|
|
3165
3179
|
# set timeout to the default script timeout if timeout is not set
|
|
3166
3180
|
if args.timeout == DEFAULT_CLI_TIMEOUT:
|
|
3167
3181
|
args.timeout = DEFAULT_TIMEOUT
|
|
3168
3182
|
|
|
3169
|
-
|
|
3170
|
-
|
|
3183
|
+
if args.no_output:
|
|
3184
|
+
__global_suppress_printout = True
|
|
3171
3185
|
if not __global_suppress_printout:
|
|
3172
3186
|
cmdStr = getStrCommand(args.hosts,args.commands,
|
|
3173
3187
|
oneonone=args.oneonone,timeout=args.timeout,password=args.password,
|
|
@@ -3199,9 +3213,7 @@ def main():
|
|
|
3199
3213
|
history_file = args.history_file,
|
|
3200
3214
|
)
|
|
3201
3215
|
#print('*'*80)
|
|
3202
|
-
|
|
3203
3216
|
#if not __global_suppress_printout: eprint('-'*80)
|
|
3204
|
-
|
|
3205
3217
|
succeededHosts = set()
|
|
3206
3218
|
for host in hosts:
|
|
3207
3219
|
if host.returncode and host.returncode != 0:
|
|
@@ -3214,12 +3226,12 @@ def main():
|
|
|
3214
3226
|
if __mainReturnCode > 0:
|
|
3215
3227
|
if not __global_suppress_printout:
|
|
3216
3228
|
eprint(f'Complete. Failed hosts (Return Code not 0) count: {__mainReturnCode}')
|
|
3217
|
-
eprint(f'failed_hosts: {",".join(
|
|
3229
|
+
eprint(f'failed_hosts: {",".join(compact_hostnames(__failedHosts))}')
|
|
3218
3230
|
else:
|
|
3219
3231
|
if not __global_suppress_printout: eprint('Complete. All hosts returned 0.')
|
|
3220
3232
|
|
|
3221
3233
|
if args.success_hosts and not __global_suppress_printout:
|
|
3222
|
-
eprint(f'succeeded_hosts: {",".join(
|
|
3234
|
+
eprint(f'succeeded_hosts: {",".join(compact_hostnames(succeededHosts))}')
|
|
3223
3235
|
|
|
3224
3236
|
if threading.active_count() > 1 and not __global_suppress_printout:
|
|
3225
3237
|
eprint(f'Remaining active thread: {threading.active_count()}')
|
|
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
|