multiSSH3 5.68__py3-none-any.whl → 5.71__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
@@ -54,10 +54,10 @@ except AttributeError:
54
54
  # If neither is available, use a dummy decorator
55
55
  def cache_decorator(func):
56
56
  return func
57
- version = '5.68'
57
+ version = '5.71'
58
58
  VERSION = version
59
59
  __version__ = version
60
- COMMIT_DATE = '2025-05-09'
60
+ COMMIT_DATE = '2025-05-13'
61
61
 
62
62
  CONFIG_FILE_CHAIN = ['./multiSSH3.config.json',
63
63
  '~/multiSSH3.config.json',
@@ -349,7 +349,7 @@ __failedHosts = set()
349
349
  __wildCharacters = ['*','?','x']
350
350
  _no_env = DEFAULT_NO_ENV
351
351
  _env_file = DEFAULT_ENV_FILE
352
- __globalUnavailableHosts = set()
352
+ __globalUnavailableHosts = dict()
353
353
  __ipmiiInterfaceIPPrefix = DEFAULT_IPMI_INTERFACE_IP_PREFIX
354
354
  __keyPressesIn = [[]]
355
355
  _emo = False
@@ -1191,12 +1191,13 @@ def __handle_writing_stream(stream,stop_event,host):
1191
1191
  line = '> ' + ''.join(__keyPressesIn[sentInput]).encode().decode().replace('\n', '↵')
1192
1192
  host.output.append(line)
1193
1193
  host.stdout.append(line)
1194
+ host.lineNumToPrintSet.add(len(host.output)-1)
1194
1195
  sentInput += 1
1195
1196
  host.lastUpdateTime = time.monotonic()
1196
1197
  else:
1197
1198
  time.sleep(0.01) # sleep for 10ms
1198
1199
  if sentInput < len(__keyPressesIn) - 1 :
1199
- eprint(f"Warning: {len(__keyPressesIn)-sentInput} key presses are not sent before the process is terminated!")
1200
+ eprint(f"Warning: {len(__keyPressesIn)-sentInput} lines of key presses are not sent before the process is terminated!")
1200
1201
  # # send the last line
1201
1202
  # if __keyPressesIn and __keyPressesIn[-1]:
1202
1203
  # stream.write(''.join(__keyPressesIn[-1]).encode())
@@ -2235,7 +2236,7 @@ def generate_output(hosts, usejson = False, greppable = False):
2235
2236
  hostPrintOut = f" Command:\n {host['command']}\n"
2236
2237
  hostPrintOut += " stdout:\n "+'\n '.join(host['stdout'])
2237
2238
  if host['stderr']:
2238
- if host['stderr'][0].strip().startswith('ssh: connect to host '):
2239
+ if host['stderr'][0].strip().startswith('ssh: connect to host ') and host['stderr'][0].strip().endswith('Connection refused'):
2239
2240
  host['stderr'][0] = 'SSH not reachable!'
2240
2241
  elif host['stderr'][-1].strip().endswith('Connection timed out'):
2241
2242
  host['stderr'][-1] = 'SSH connection timed out!'
@@ -2263,7 +2264,7 @@ def generate_output(hosts, usejson = False, greppable = False):
2263
2264
  rtnStr += "User Inputs: \n "
2264
2265
  rtnStr += '\n '.join(CMDsOut)
2265
2266
  rtnStr += '\n'
2266
- __keyPressesIn = [[]]
2267
+ __keyPressesIn[-1].clear()
2267
2268
  if __global_suppress_printout and not outputs:
2268
2269
  rtnStr += 'Success'
2269
2270
  return rtnStr
@@ -2287,7 +2288,7 @@ def print_output(hosts,usejson = False,quiet = False,greppable = False):
2287
2288
 
2288
2289
  #%% ------------ Run / Process Hosts Block ----------------
2289
2290
  def processRunOnHosts(timeout, password, max_connections, hosts, returnUnfinished, no_watch, json, called, greppable,
2290
- unavailableHosts,willUpdateUnreachableHosts,curses_min_char_len = DEFAULT_CURSES_MINIMUM_CHAR_LEN,
2291
+ unavailableHosts:dict,willUpdateUnreachableHosts,curses_min_char_len = DEFAULT_CURSES_MINIMUM_CHAR_LEN,
2291
2292
  curses_min_line_len = DEFAULT_CURSES_MINIMUM_LINE_LEN,single_window = DEFAULT_SINGLE_WINDOW,
2292
2293
  unavailable_host_expiry = DEFAULT_UNAVAILABLE_HOST_EXPIRY):
2293
2294
  global __globalUnavailableHosts
@@ -2317,45 +2318,47 @@ def processRunOnHosts(timeout, password, max_connections, hosts, returnUnfinishe
2317
2318
  thread.join(timeout=3)
2318
2319
  # update the unavailable hosts and global unavailable hosts
2319
2320
  if willUpdateUnreachableHosts:
2320
- unavailableHosts = set(unavailableHosts)
2321
- unavailableHosts.update([host.name for host in hosts if host.stderr and ('No route to host' in host.stderr[0].strip() or (host.stderr[-1].strip().startswith('Timeout!') and host.returncode == 124))])
2322
- # reachable hosts = all hosts - unreachable hosts
2323
- reachableHosts = set([host.name for host in hosts]) - unavailableHosts
2321
+ availableHosts = set()
2322
+ for host in hosts:
2323
+ if host.stderr and ('No route to host' in host.stderr[0].strip() or 'Connection timed out' in host.stderr[0].strip() or (host.stderr[-1].strip().startswith('Timeout!') and host.returncode == 124)):
2324
+ unavailableHosts[host.name] = int(time.monotonic())
2325
+ __globalUnavailableHosts[host.name] = int(time.monotonic())
2326
+ else:
2327
+ availableHosts.add(host.name)
2328
+ if host.name in unavailableHosts:
2329
+ del unavailableHosts[host.name]
2330
+ if host.name in __globalUnavailableHosts:
2331
+ del __globalUnavailableHosts[host.name]
2324
2332
  if __DEBUG_MODE:
2325
2333
  print(f'Unreachable hosts: {unavailableHosts}')
2326
- __globalUnavailableHosts.update(unavailableHosts)
2327
-
2328
- # os.environ['__multiSSH3_UNAVAILABLE_HOSTS'] = ','.join(unavailableHosts)
2329
- # create a temporary file to store the unavailable hosts
2330
2334
  try:
2331
2335
  # check for the old content, only update if the new content is different
2332
- if not os.path.exists(os.path.join(tempfile.gettempdir(),'__multiSSH3_UNAVAILABLE_HOSTS.csv')):
2333
- with open(os.path.join(tempfile.gettempdir(),'__multiSSH3_UNAVAILABLE_HOSTS.csv'),'w') as f:
2334
- f.write(f',{int(time.monotonic())}\n'.join(unavailableHosts) + f',{int(time.monotonic())}\n')
2336
+ if not os.path.exists(os.path.join(tempfile.gettempdir(),f'__{getpass.getuser()}_multiSSH3_UNAVAILABLE_HOSTS.csv')):
2337
+ with open(os.path.join(tempfile.gettempdir(),f'__{getpass.getuser()}_multiSSH3_UNAVAILABLE_HOSTS.csv'),'w') as f:
2338
+ f.writelines(f'{host},{expTime}' for host,expTime in unavailableHosts.values())
2335
2339
  else:
2336
2340
  oldDic = {}
2337
2341
  try:
2338
- with open(os.path.join(tempfile.gettempdir(),'__multiSSH3_UNAVAILABLE_HOSTS.csv'),'r') as f:
2342
+ with open(os.path.join(tempfile.gettempdir(),f'__{getpass.getuser()}_multiSSH3_UNAVAILABLE_HOSTS.csv'),'r') as f:
2339
2343
  for line in f:
2340
2344
  line = line.strip()
2341
2345
  if line and ',' in line and len(line.split(',')) >= 2 and line.split(',')[0] and line.split(',')[1].isdigit():
2342
- oldDic[line.split(',')[0]] = int(line.split(',')[1])
2346
+ hostname = line.split(',')[0]
2347
+ expireTime = int(line.split(',')[1])
2348
+ if expireTime < time.monotonic() and hostname not in availableHosts:
2349
+ oldDic[hostname] = expireTime
2343
2350
  except:
2344
2351
  pass
2345
- for key in list(oldDic.keys()):
2346
- if key in reachableHosts or time.monotonic() < oldDic[key] or time.monotonic() - oldDic[key] > unavailable_host_expiry:
2347
- del oldDic[key]
2348
2352
  # add new entries
2349
- for host in unavailableHosts:
2350
- if host not in oldDic:
2351
- oldDic[host] = int(time.monotonic ())
2352
- with open(os.path.join(tempfile.gettempdir(),'__multiSSH3_UNAVAILABLE_HOSTS.csv.new'),'w') as f:
2353
+ oldDic.update(unavailableHosts)
2354
+ with open(os.path.join(tempfile.gettempdir(),getpass.getuser()+'__multiSSH3_UNAVAILABLE_HOSTS.csv.new'),'w') as f:
2353
2355
  for key, value in oldDic.items():
2354
2356
  f.write(f'{key},{value}\n')
2355
- os.replace(os.path.join(tempfile.gettempdir(),'__multiSSH3_UNAVAILABLE_HOSTS.csv.new'),os.path.join(tempfile.gettempdir(),'__multiSSH3_UNAVAILABLE_HOSTS.csv'))
2356
-
2357
+ os.replace(os.path.join(tempfile.gettempdir(),getpass.getuser()+'__multiSSH3_UNAVAILABLE_HOSTS.csv.new'),os.path.join(tempfile.gettempdir(),f'__{getpass.getuser()}_multiSSH3_UNAVAILABLE_HOSTS.csv'))
2357
2358
  except Exception as e:
2358
2359
  eprint(f'Error writing to temporary file: {e!r}')
2360
+ import traceback
2361
+ eprint(traceback.format_exc())
2359
2362
 
2360
2363
  # print the output, if the output of multiple hosts are the same, we aggragate them
2361
2364
  if not called:
@@ -2436,7 +2439,7 @@ def getStrCommand(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAULT_ONE_O
2436
2439
  copy_id = False, unavailable_host_expiry = DEFAULT_UNAVAILABLE_HOST_EXPIRY,no_history = DEFAULT_NO_HISTORY,
2437
2440
  history_file = DEFAULT_HISTORY_FILE, env_file = DEFAULT_ENV_FILE,
2438
2441
  repeat = DEFAULT_REPEAT,interval = DEFAULT_INTERVAL,
2439
- shortend = False):
2442
+ shortend = False,tabSeperated = False):
2440
2443
  _ = called
2441
2444
  _ = returnUnfinished
2442
2445
  _ = willUpdateUnreachableHosts
@@ -2460,7 +2463,10 @@ def getStrCommand(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAULT_ONE_O
2460
2463
  commandStr = '"' + '" "'.join(commands) + '"' if commands else ''
2461
2464
  filePath = os.path.abspath(__file__)
2462
2465
  programName = filePath if filePath else 'mssh'
2463
- return f'{programName} {argsStr} {hostStr} {commandStr}'
2466
+ if tabSeperated:
2467
+ return f'{programName}\t{argsStr}\t{hostStr}\t{commandStr}'
2468
+ else:
2469
+ return f'{programName} {argsStr} {hostStr} {commandStr}'
2464
2470
 
2465
2471
  #%% ------------ Record History Block ----------------
2466
2472
  def record_command_history(kwargs):
@@ -2484,7 +2490,7 @@ def record_command_history(kwargs):
2484
2490
  for name in sig.parameters
2485
2491
  if name in kwargs
2486
2492
  }
2487
- strCommand = getStrCommand(**wanted)
2493
+ strCommand = getStrCommand(**wanted,shortend=True,tabSeperated=True)
2488
2494
  with open(history_file, 'a') as f:
2489
2495
  # it follows <timestamp>\t<strCommand>\n
2490
2496
  f.write(f'{int(time.time())}\t{strCommand}\n')
@@ -2566,27 +2572,29 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
2566
2572
  record_command_history(locals())
2567
2573
  if error_only:
2568
2574
  __global_suppress_printout = True
2569
- if os.path.exists(os.path.join(tempfile.gettempdir(),'__multiSSH3_UNAVAILABLE_HOSTS.csv')):
2575
+ if os.path.exists(os.path.join(tempfile.gettempdir(),f'__{getpass.getuser()}_multiSSH3_UNAVAILABLE_HOSTS.csv')):
2570
2576
  if unavailable_host_expiry <= 0:
2571
2577
  unavailable_host_expiry = 10
2572
2578
  try:
2573
2579
  readed = False
2574
- if 0 < time.time() - os.path.getmtime(os.path.join(tempfile.gettempdir(),'__multiSSH3_UNAVAILABLE_HOSTS.csv')) < unavailable_host_expiry:
2580
+ if 0 < time.time() - os.path.getmtime(os.path.join(tempfile.gettempdir(),f'__{getpass.getuser()}_multiSSH3_UNAVAILABLE_HOSTS.csv')) < unavailable_host_expiry:
2575
2581
 
2576
- with open(os.path.join(tempfile.gettempdir(),'__multiSSH3_UNAVAILABLE_HOSTS.csv'),'r') as f:
2582
+ with open(os.path.join(tempfile.gettempdir(),f'__{getpass.getuser()}_multiSSH3_UNAVAILABLE_HOSTS.csv'),'r') as f:
2577
2583
  for line in f:
2578
2584
  line = line.strip()
2579
2585
  if line and ',' in line and len(line.split(',')) >= 2 and line.split(',')[0] and line.split(',')[1].isdigit():
2580
- if int(line.split(',')[1]) < time.monotonic() and int(line.split(',')[1]) + unavailable_host_expiry > time.monotonic():
2581
- __globalUnavailableHosts.add(line.split(',')[0])
2586
+ hostname = line.split(',')[0]
2587
+ expireTime = int(line.split(',')[1])
2588
+ if expireTime < time.monotonic() and expireTime + unavailable_host_expiry > time.monotonic():
2589
+ __globalUnavailableHosts[hostname] = expireTime
2582
2590
  readed = True
2583
2591
  if readed and not __global_suppress_printout:
2584
- eprint(f"Read unavailable hosts from the file {os.path.join(tempfile.gettempdir(),'__multiSSH3_UNAVAILABLE_HOSTS.csv')}")
2592
+ eprint(f"Read unavailable hosts from the file {os.path.join(tempfile.gettempdir(),f'__{getpass.getuser()}_multiSSH3_UNAVAILABLE_HOSTS.csv')}")
2585
2593
  except Exception as e:
2586
- eprint(f"Warning: Unable to read the unavailable hosts from the file {os.path.join(tempfile.gettempdir(),'__multiSSH3_UNAVAILABLE_HOSTS.csv')!r}")
2594
+ eprint(f"Warning: Unable to read the unavailable hosts from the file {os.path.join(tempfile.gettempdir(),f'__{getpass.getuser()}_multiSSH3_UNAVAILABLE_HOSTS.csv')!r}")
2587
2595
  eprint(str(e))
2588
2596
  elif '__multiSSH3_UNAVAILABLE_HOSTS' in readEnvFromFile():
2589
- __globalUnavailableHosts.update(readEnvFromFile()['__multiSSH3_UNAVAILABLE_HOSTS'].split(','))
2597
+ __globalUnavailableHosts.update({host: int(time.monotonic()) for host in readEnvFromFile()['__multiSSH3_UNAVAILABLE_HOSTS'].split(',') if host})
2590
2598
  if not max_connections:
2591
2599
  max_connections = 4 * os.cpu_count()
2592
2600
  elif max_connections == 0:
@@ -2619,7 +2627,7 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
2619
2627
  if skipUnreachable:
2620
2628
  unavailableHosts = __globalUnavailableHosts
2621
2629
  else:
2622
- unavailableHosts = set()
2630
+ unavailableHosts = dict()
2623
2631
  # set global input to empty
2624
2632
  __keyPressesIn = [[]]
2625
2633
  __global_suppress_printout = True
@@ -2628,7 +2636,7 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
2628
2636
  if skipUnreachable:
2629
2637
  unavailableHosts = __globalUnavailableHosts
2630
2638
  else:
2631
- unavailableHosts = set()
2639
+ unavailableHosts = dict()
2632
2640
  skipUnreachable = True
2633
2641
  if quiet:
2634
2642
  __global_suppress_printout = True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multiSSH3
3
- Version: 5.68
3
+ Version: 5.71
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=Ynd6Q76wV85vjZUSIavxQePgqWc_4xTObjajioTCFgM,144718
2
+ multissh3-5.71.dist-info/METADATA,sha256=8MsX_mwvDFMX53bwfVi81Gaqp6bjgXfqMhYMOTMQNDc,18093
3
+ multissh3-5.71.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
4
+ multissh3-5.71.dist-info/entry_points.txt,sha256=xi2rWWNfmHx6gS8Mmx0rZL2KZz6XWBYP3DWBpWAnnZ0,143
5
+ multissh3-5.71.dist-info/top_level.txt,sha256=tUwttxlnpLkZorSsroIprNo41lYSxjd2ASuL8-EJIJw,10
6
+ multissh3-5.71.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.3.1)
2
+ Generator: setuptools (80.4.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,6 +0,0 @@
1
- multiSSH3.py,sha256=_Lr8Q00fGgVkJAtP6NXZGnfipNa5yqiZplxg5ZDXvqI,144176
2
- multissh3-5.68.dist-info/METADATA,sha256=mwLzCxpuDPk_k0JqGCGE0CMVOmg9DBs7GHNOPtNvJ6A,18093
3
- multissh3-5.68.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
4
- multissh3-5.68.dist-info/entry_points.txt,sha256=xi2rWWNfmHx6gS8Mmx0rZL2KZz6XWBYP3DWBpWAnnZ0,143
5
- multissh3-5.68.dist-info/top_level.txt,sha256=tUwttxlnpLkZorSsroIprNo41lYSxjd2ASuL8-EJIJw,10
6
- multissh3-5.68.dist-info/RECORD,,