multiSSH3 5.75__py3-none-any.whl → 5.76__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 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.75'
58
+ version = '5.76'
59
59
  VERSION = version
60
60
  __version__ = version
61
- COMMIT_DATE = '2025-06-17'
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
- sys.exit(0)
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)
@@ -2761,7 +2781,7 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
2761
2781
  else:
2762
2782
  eprint(f"Warning: ssh-copy-id not found in {_binPaths} , skipping copy id to the hosts")
2763
2783
  if not commands:
2764
- sys.exit(0)
2784
+ _exit_with_code(0, "Copy id finished, no commands to run")
2765
2785
  if files and not commands:
2766
2786
  # if files are specified but not target dir, we default to file sync mode
2767
2787
  file_sync = True
@@ -2778,8 +2798,7 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
2778
2798
  except:
2779
2799
  pathSet.update(glob.glob(file,recursive=True))
2780
2800
  if not pathSet:
2781
- eprint(f'Warning: No source files at {files!r} are found after resolving globs!')
2782
- sys.exit(66)
2801
+ _exit_with_code(66, f'No source files at {files!r} are found after resolving globs!')
2783
2802
  else:
2784
2803
  pathSet = set(files)
2785
2804
  if file_sync:
@@ -2796,7 +2815,7 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
2796
2815
  eprint("Error: the number of commands must be the same as the number of hosts")
2797
2816
  eprint(f"Number of commands: {len(commands)}")
2798
2817
  eprint(f"Number of hosts: {len(set(targetHostDic) - set(skipHostSet))}")
2799
- sys.exit(255)
2818
+ _exit_with_code(255, "Number of commands and hosts do not match")
2800
2819
  if not __global_suppress_printout:
2801
2820
  eprint('-'*80)
2802
2821
  eprint("Running in one on one mode")
@@ -2911,6 +2930,7 @@ def generate_default_config(args):
2911
2930
  'DEFAULT_SINGLE_WINDOW': args.single_window,
2912
2931
  'DEFAULT_ERROR_ONLY': args.error_only,
2913
2932
  'DEFAULT_NO_OUTPUT': args.no_output,
2933
+ 'DEFAULT_RETURN_ZERO': args.return_zero,
2914
2934
  'DEFAULT_NO_ENV': args.no_env,
2915
2935
  'DEFAULT_ENV_FILE': args.env_file,
2916
2936
  'DEFAULT_NO_HISTORY': args.no_history,
@@ -2945,8 +2965,7 @@ def write_default_config(args,CONFIG_FILE = None):
2945
2965
  elif inStr.lower().strip().startswith('o'):
2946
2966
  backup = False
2947
2967
  else:
2948
- eprint("Aborted")
2949
- sys.exit(1)
2968
+ _exit_with_code(0, "Aborted by user, no config file written")
2950
2969
  try:
2951
2970
  if backup and os.path.exists(CONFIG_FILE):
2952
2971
  os.rename(CONFIG_FILE,CONFIG_FILE+'.bak')
@@ -2955,8 +2974,7 @@ def write_default_config(args,CONFIG_FILE = None):
2955
2974
  eprint(f"Do you want to continue writing the new config file to {CONFIG_FILE!r}? (y/n)")
2956
2975
  inStr = input_with_timeout_and_countdown(10)
2957
2976
  if not inStr or not inStr.lower().strip().startswith('y'):
2958
- eprint("Aborted")
2959
- sys.exit(1)
2977
+ _exit_with_code(0, "Aborted by user, no config file written")
2960
2978
  try:
2961
2979
  with open(CONFIG_FILE,'w') as f:
2962
2980
  json.dump(__configs_from_file,f,indent=4)
@@ -2978,6 +2996,7 @@ def main():
2978
2996
  global __DEBUG_MODE
2979
2997
  global __configs_from_file
2980
2998
  global _encoding
2999
+ global __returnZero
2981
3000
  _emo = False
2982
3001
  # We handle the signal
2983
3002
  signal.signal(signal.SIGINT, signal_handler)
@@ -3010,6 +3029,7 @@ def main():
3010
3029
  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
3030
  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
3031
  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)
3032
+ 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
3033
  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
3034
  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
3035
  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 +3075,10 @@ def main():
3055
3075
  args.no_history = True
3056
3076
  args.greppable = True
3057
3077
  args.error_only = True
3058
-
3078
+
3079
+ if args.return_zero:
3080
+ __returnZero = True
3081
+
3059
3082
  if args.generate_config_file or args.store_config_file:
3060
3083
  if args.store_config_file:
3061
3084
  configFileToWriteTo = args.store_config_file
@@ -3071,7 +3094,7 @@ def main():
3071
3094
  if configFileToWriteTo:
3072
3095
  with open(configFileToWriteTo,'r') as f:
3073
3096
  eprint(f"Config file content: \n{f.read()}")
3074
- sys.exit(0)
3097
+ _exit_with_code(0)
3075
3098
  if args.config_file:
3076
3099
  if os.path.exists(args.config_file):
3077
3100
  __configs_from_file.update(load_config_file(os.path.expanduser(args.config_file)))
@@ -3094,7 +3117,7 @@ def main():
3094
3117
  elif inStr.lower().strip().startswith('m'):
3095
3118
  eprint(f"\nRunning multiple commands: {', '.join(args.commands)!r} on all hosts")
3096
3119
  else:
3097
- sys.exit(0)
3120
+ _exit_with_code(0, "Aborted by user, no commands to run")
3098
3121
 
3099
3122
  if args.key or args.use_key:
3100
3123
  if not args.key:
@@ -3179,7 +3202,7 @@ def main():
3179
3202
  # os.system(f'pkill -ef {os.path.basename(__file__)}')
3180
3203
  # os._exit(mainReturnCode)
3181
3204
 
3182
- sys.exit(__mainReturnCode)
3205
+ _exit_with_code(__mainReturnCode)
3183
3206
 
3184
3207
  if __name__ == "__main__":
3185
3208
  main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multiSSH3
3
- Version: 5.75
3
+ Version: 5.76
4
4
  Summary: Run commands on multiple hosts via SSH
5
5
  Home-page: https://github.com/yufei-pan/multiSSH3
6
6
  Author: Yufei Pan
@@ -0,0 +1,6 @@
1
+ multiSSH3.py,sha256=ioUj2vZW1RiOyXRm_yJizPp751_ggMZeVtDrM6Lvvjk,150851
2
+ multissh3-5.76.dist-info/METADATA,sha256=VYwjn_-6fQHyubKyY6M0cuh6DNJaI4hIkDh4RV0qE5k,18093
3
+ multissh3-5.76.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
4
+ multissh3-5.76.dist-info/entry_points.txt,sha256=xi2rWWNfmHx6gS8Mmx0rZL2KZz6XWBYP3DWBpWAnnZ0,143
5
+ multissh3-5.76.dist-info/top_level.txt,sha256=tUwttxlnpLkZorSsroIprNo41lYSxjd2ASuL8-EJIJw,10
6
+ multissh3-5.76.dist-info/RECORD,,
@@ -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,,