multiSSH3 5.75__py3-none-any.whl → 5.77__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 +39 -15
- {multissh3-5.75.dist-info → multissh3-5.77.dist-info}/METADATA +1 -1
- multissh3-5.77.dist-info/RECORD +6 -0
- multissh3-5.75.dist-info/RECORD +0 -6
- {multissh3-5.75.dist-info → multissh3-5.77.dist-info}/WHEEL +0 -0
- {multissh3-5.75.dist-info → multissh3-5.77.dist-info}/entry_points.txt +0 -0
- {multissh3-5.75.dist-info → multissh3-5.77.dist-info}/top_level.txt +0 -0
multiSSH3.py
CHANGED
|
@@ -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.77'
|
|
59
59
|
VERSION = version
|
|
60
60
|
__version__ = version
|
|
61
|
-
COMMIT_DATE = '2025-06-
|
|
61
|
+
COMMIT_DATE = '2025-06-25'
|
|
62
62
|
|
|
63
63
|
CONFIG_FILE_CHAIN = ['./multiSSH3.config.json',
|
|
64
64
|
'~/multiSSH3.config.json',
|
|
@@ -78,6 +78,24 @@ def eprint(*args, **kwargs):
|
|
|
78
78
|
print(f"Error: Cannot print to stderr: {e}")
|
|
79
79
|
print(*args, **kwargs)
|
|
80
80
|
|
|
81
|
+
def _exit_with_code(code, message=None):
|
|
82
|
+
'''
|
|
83
|
+
Exit the program with a specific code and print a message
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
code (int): The exit code
|
|
87
|
+
message (str, optional): The message to print. Defaults to None.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
None
|
|
91
|
+
'''
|
|
92
|
+
global __returnZero
|
|
93
|
+
if message:
|
|
94
|
+
eprint('Exiting: '+ message)
|
|
95
|
+
if __returnZero:
|
|
96
|
+
code = 0
|
|
97
|
+
sys.exit(code)
|
|
98
|
+
|
|
81
99
|
def signal_handler(sig, frame):
|
|
82
100
|
'''
|
|
83
101
|
Handle the Ctrl C signal
|
|
@@ -98,7 +116,7 @@ def signal_handler(sig, frame):
|
|
|
98
116
|
# wait for 0.1 seconds to allow the threads to exit
|
|
99
117
|
time.sleep(0.1)
|
|
100
118
|
os.system(f'pkill -ef {os.path.basename(__file__)}')
|
|
101
|
-
|
|
119
|
+
_exit_with_code(1, 'Exiting immediately due to Ctrl C')
|
|
102
120
|
|
|
103
121
|
# def input_with_timeout_and_countdown(timeout, prompt='Please enter your selection'):
|
|
104
122
|
# """
|
|
@@ -303,6 +321,7 @@ DEFAULT_CURSES_MINIMUM_LINE_LEN = 1
|
|
|
303
321
|
DEFAULT_SINGLE_WINDOW = False
|
|
304
322
|
DEFAULT_ERROR_ONLY = False
|
|
305
323
|
DEFAULT_NO_OUTPUT = False
|
|
324
|
+
DEFAULT_RETURN_ZERO = False
|
|
306
325
|
DEFAULT_NO_ENV = False
|
|
307
326
|
DEFAULT_ENV_FILE = '/etc/profile.d/hosts.sh'
|
|
308
327
|
DEFAULT_NO_HISTORY = False
|
|
@@ -362,6 +381,7 @@ __curses_current_color_index = 10
|
|
|
362
381
|
__max_connections_nofile_limit_supported = 0
|
|
363
382
|
__thread_start_delay = 0
|
|
364
383
|
_encoding = DEFAULT_ENCODING
|
|
384
|
+
__returnZero = DEFAULT_RETURN_ZERO
|
|
365
385
|
if __resource_lib_available:
|
|
366
386
|
# Get the current limits
|
|
367
387
|
_, __system_nofile_limit = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
@@ -2525,6 +2545,7 @@ def getStrCommand(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAULT_ONE_O
|
|
|
2525
2545
|
history_file = history_file, env_file = env_file,
|
|
2526
2546
|
repeat = repeat,interval = interval,
|
|
2527
2547
|
shortend = shortend)
|
|
2548
|
+
commands = [command.replace('"', '\\"') for command in commands]
|
|
2528
2549
|
commandStr = '"' + '" "'.join(commands) + '"' if commands else ''
|
|
2529
2550
|
filePath = os.path.abspath(__file__)
|
|
2530
2551
|
programName = filePath if filePath else 'mssh'
|
|
@@ -2761,7 +2782,7 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
|
|
|
2761
2782
|
else:
|
|
2762
2783
|
eprint(f"Warning: ssh-copy-id not found in {_binPaths} , skipping copy id to the hosts")
|
|
2763
2784
|
if not commands:
|
|
2764
|
-
|
|
2785
|
+
_exit_with_code(0, "Copy id finished, no commands to run")
|
|
2765
2786
|
if files and not commands:
|
|
2766
2787
|
# if files are specified but not target dir, we default to file sync mode
|
|
2767
2788
|
file_sync = True
|
|
@@ -2778,8 +2799,7 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
|
|
|
2778
2799
|
except:
|
|
2779
2800
|
pathSet.update(glob.glob(file,recursive=True))
|
|
2780
2801
|
if not pathSet:
|
|
2781
|
-
|
|
2782
|
-
sys.exit(66)
|
|
2802
|
+
_exit_with_code(66, f'No source files at {files!r} are found after resolving globs!')
|
|
2783
2803
|
else:
|
|
2784
2804
|
pathSet = set(files)
|
|
2785
2805
|
if file_sync:
|
|
@@ -2796,7 +2816,7 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
|
|
|
2796
2816
|
eprint("Error: the number of commands must be the same as the number of hosts")
|
|
2797
2817
|
eprint(f"Number of commands: {len(commands)}")
|
|
2798
2818
|
eprint(f"Number of hosts: {len(set(targetHostDic) - set(skipHostSet))}")
|
|
2799
|
-
|
|
2819
|
+
_exit_with_code(255, "Number of commands and hosts do not match")
|
|
2800
2820
|
if not __global_suppress_printout:
|
|
2801
2821
|
eprint('-'*80)
|
|
2802
2822
|
eprint("Running in one on one mode")
|
|
@@ -2911,6 +2931,7 @@ def generate_default_config(args):
|
|
|
2911
2931
|
'DEFAULT_SINGLE_WINDOW': args.single_window,
|
|
2912
2932
|
'DEFAULT_ERROR_ONLY': args.error_only,
|
|
2913
2933
|
'DEFAULT_NO_OUTPUT': args.no_output,
|
|
2934
|
+
'DEFAULT_RETURN_ZERO': args.return_zero,
|
|
2914
2935
|
'DEFAULT_NO_ENV': args.no_env,
|
|
2915
2936
|
'DEFAULT_ENV_FILE': args.env_file,
|
|
2916
2937
|
'DEFAULT_NO_HISTORY': args.no_history,
|
|
@@ -2945,8 +2966,7 @@ def write_default_config(args,CONFIG_FILE = None):
|
|
|
2945
2966
|
elif inStr.lower().strip().startswith('o'):
|
|
2946
2967
|
backup = False
|
|
2947
2968
|
else:
|
|
2948
|
-
|
|
2949
|
-
sys.exit(1)
|
|
2969
|
+
_exit_with_code(0, "Aborted by user, no config file written")
|
|
2950
2970
|
try:
|
|
2951
2971
|
if backup and os.path.exists(CONFIG_FILE):
|
|
2952
2972
|
os.rename(CONFIG_FILE,CONFIG_FILE+'.bak')
|
|
@@ -2955,8 +2975,7 @@ def write_default_config(args,CONFIG_FILE = None):
|
|
|
2955
2975
|
eprint(f"Do you want to continue writing the new config file to {CONFIG_FILE!r}? (y/n)")
|
|
2956
2976
|
inStr = input_with_timeout_and_countdown(10)
|
|
2957
2977
|
if not inStr or not inStr.lower().strip().startswith('y'):
|
|
2958
|
-
|
|
2959
|
-
sys.exit(1)
|
|
2978
|
+
_exit_with_code(0, "Aborted by user, no config file written")
|
|
2960
2979
|
try:
|
|
2961
2980
|
with open(CONFIG_FILE,'w') as f:
|
|
2962
2981
|
json.dump(__configs_from_file,f,indent=4)
|
|
@@ -2978,6 +2997,7 @@ def main():
|
|
|
2978
2997
|
global __DEBUG_MODE
|
|
2979
2998
|
global __configs_from_file
|
|
2980
2999
|
global _encoding
|
|
3000
|
+
global __returnZero
|
|
2981
3001
|
_emo = False
|
|
2982
3002
|
# We handle the signal
|
|
2983
3003
|
signal.signal(signal.SIGINT, signal_handler)
|
|
@@ -3010,6 +3030,7 @@ def main():
|
|
|
3010
3030
|
parser.add_argument('-B','-sw','--single_window', action='store_true', help=f'Use a single window for all hosts. (default: {DEFAULT_SINGLE_WINDOW})', default=DEFAULT_SINGLE_WINDOW)
|
|
3011
3031
|
parser.add_argument('-R','-eo','--error_only', action='store_true', help=f'Only print the error output. (default: {DEFAULT_ERROR_ONLY})', default=DEFAULT_ERROR_ONLY)
|
|
3012
3032
|
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)
|
|
3033
|
+
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)
|
|
3013
3034
|
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)
|
|
3014
3035
|
parser.add_argument("--env_file", type=str, help=f"The file to load the mssh file based environment variables from. ( Still work with --no_env ) (default: {DEFAULT_ENV_FILE})", default=DEFAULT_ENV_FILE)
|
|
3015
3036
|
parser.add_argument("-m","--max_connections", type=int, help=f"Max number of connections to use (default: 4 * cpu_count)", default=DEFAULT_MAX_CONNECTIONS)
|
|
@@ -3055,7 +3076,10 @@ def main():
|
|
|
3055
3076
|
args.no_history = True
|
|
3056
3077
|
args.greppable = True
|
|
3057
3078
|
args.error_only = True
|
|
3058
|
-
|
|
3079
|
+
|
|
3080
|
+
if args.return_zero:
|
|
3081
|
+
__returnZero = True
|
|
3082
|
+
|
|
3059
3083
|
if args.generate_config_file or args.store_config_file:
|
|
3060
3084
|
if args.store_config_file:
|
|
3061
3085
|
configFileToWriteTo = args.store_config_file
|
|
@@ -3071,7 +3095,7 @@ def main():
|
|
|
3071
3095
|
if configFileToWriteTo:
|
|
3072
3096
|
with open(configFileToWriteTo,'r') as f:
|
|
3073
3097
|
eprint(f"Config file content: \n{f.read()}")
|
|
3074
|
-
|
|
3098
|
+
_exit_with_code(0)
|
|
3075
3099
|
if args.config_file:
|
|
3076
3100
|
if os.path.exists(args.config_file):
|
|
3077
3101
|
__configs_from_file.update(load_config_file(os.path.expanduser(args.config_file)))
|
|
@@ -3094,7 +3118,7 @@ def main():
|
|
|
3094
3118
|
elif inStr.lower().strip().startswith('m'):
|
|
3095
3119
|
eprint(f"\nRunning multiple commands: {', '.join(args.commands)!r} on all hosts")
|
|
3096
3120
|
else:
|
|
3097
|
-
|
|
3121
|
+
_exit_with_code(0, "Aborted by user, no commands to run")
|
|
3098
3122
|
|
|
3099
3123
|
if args.key or args.use_key:
|
|
3100
3124
|
if not args.key:
|
|
@@ -3179,7 +3203,7 @@ def main():
|
|
|
3179
3203
|
# os.system(f'pkill -ef {os.path.basename(__file__)}')
|
|
3180
3204
|
# os._exit(mainReturnCode)
|
|
3181
3205
|
|
|
3182
|
-
|
|
3206
|
+
_exit_with_code(__mainReturnCode)
|
|
3183
3207
|
|
|
3184
3208
|
if __name__ == "__main__":
|
|
3185
3209
|
main()
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
multiSSH3.py,sha256=NzwlKjLdj7y3o3mhe28Ks80SS8E-FFNsp9UPQNNACTE,150917
|
|
2
|
+
multissh3-5.77.dist-info/METADATA,sha256=1kG-DHm3Bek62gr67O8QsAmOeLNBbd-D6ZjeDl9rItw,18093
|
|
3
|
+
multissh3-5.77.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
4
|
+
multissh3-5.77.dist-info/entry_points.txt,sha256=xi2rWWNfmHx6gS8Mmx0rZL2KZz6XWBYP3DWBpWAnnZ0,143
|
|
5
|
+
multissh3-5.77.dist-info/top_level.txt,sha256=tUwttxlnpLkZorSsroIprNo41lYSxjd2ASuL8-EJIJw,10
|
|
6
|
+
multissh3-5.77.dist-info/RECORD,,
|
multissh3-5.75.dist-info/RECORD
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
multiSSH3.py,sha256=5pDQ7zSeNmfzu-o081Z7VOfW_8Ke48WPHi0BxnBnHAw,149915
|
|
2
|
-
multissh3-5.75.dist-info/METADATA,sha256=1_pBC-nNbRg_aR3LFNNInE5Whrn9hBaQMhnxEkO2IOg,18093
|
|
3
|
-
multissh3-5.75.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
4
|
-
multissh3-5.75.dist-info/entry_points.txt,sha256=xi2rWWNfmHx6gS8Mmx0rZL2KZz6XWBYP3DWBpWAnnZ0,143
|
|
5
|
-
multissh3-5.75.dist-info/top_level.txt,sha256=tUwttxlnpLkZorSsroIprNo41lYSxjd2ASuL8-EJIJw,10
|
|
6
|
-
multissh3-5.75.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|