multiSSH3 5.81__tar.gz → 5.83__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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multiSSH3
3
- Version: 5.81
3
+ Version: 5.83
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multiSSH3
3
- Version: 5.81
3
+ Version: 5.83
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
@@ -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.81'
58
+ version = '5.83'
59
59
  VERSION = version
60
60
  __version__ = version
61
- COMMIT_DATE = '2025-07-15'
61
+ COMMIT_DATE = '2025-07-21'
62
62
 
63
63
  CONFIG_FILE_CHAIN = ['./multiSSH3.config.json',
64
64
  '~/multiSSH3.config.json',
@@ -2298,11 +2298,8 @@ def curses_print(stdscr, hosts, threads, min_char_len = DEFAULT_CURSES_MINIMUM_C
2298
2298
  #time.sleep(0.25)
2299
2299
 
2300
2300
  #%% ------------ Generate Output Block ----------------
2301
- def generate_output(hosts, usejson = False, greppable = False):
2302
- global __keyPressesIn
2303
- global __global_suppress_printout
2304
- global __encoding
2305
- if __global_suppress_printout:
2301
+ def generate_output(hosts, usejson = False, greppable = False,quiet = False,encoding = _encoding,keyPressesIn = [[]]):
2302
+ if quiet:
2306
2303
  # remove hosts with returncode 0
2307
2304
  hosts = [dict(host) for host in hosts if host.returncode != 0]
2308
2305
  if not hosts:
@@ -2334,8 +2331,8 @@ def generate_output(hosts, usejson = False, greppable = False):
2334
2331
  rtnList.append(['','','',''])
2335
2332
  rtnStr += pretty_format_table(rtnList)
2336
2333
  rtnStr += '*'*80+'\n'
2337
- if __keyPressesIn[-1]:
2338
- CMDsOut = [''.join(cmd).encode(encoding=_encoding,errors='backslashreplace').decode(encoding=_encoding,errors='backslashreplace').replace('\\n', '↵') for cmd in __keyPressesIn if cmd]
2334
+ if keyPressesIn[-1]:
2335
+ CMDsOut = [''.join(cmd).encode(encoding=encoding,errors='backslashreplace').decode(encoding=encoding,errors='backslashreplace').replace('\\n', '↵') for cmd in keyPressesIn if cmd]
2339
2336
  rtnStr += 'User Inputs: '+ '\nUser Inputs: '.join(CMDsOut)
2340
2337
  #rtnStr += '\n'
2341
2338
  else:
@@ -2357,23 +2354,21 @@ def generate_output(hosts, usejson = False, greppable = False):
2357
2354
  for output, hostSet in outputs.items():
2358
2355
  compact_hosts = compact_hostnames(hostSet)
2359
2356
  rtnStr += '*'*80+'\n'
2360
- if __global_suppress_printout:
2357
+ if quiet:
2361
2358
  rtnStr += f'Abnormal returncode produced by {",".join(compact_hosts)}:\n'
2362
2359
  rtnStr += output+'\n'
2363
2360
  else:
2364
2361
  rtnStr += f'These hosts: "{",".join(compact_hosts)}" have a response of:\n'
2365
2362
  rtnStr += output+'\n'
2366
- if not __global_suppress_printout or outputs:
2363
+ if not quiet or outputs:
2367
2364
  rtnStr += '*'*80+'\n'
2368
- if __keyPressesIn[-1]:
2369
- CMDsOut = [''.join(cmd).encode(encoding=_encoding,errors='backslashreplace').decode(encoding=_encoding,errors='backslashreplace').replace('\\n', '↵') for cmd in __keyPressesIn if cmd]
2370
- #rtnStr += f"Key presses: {''.join(__keyPressesIn).encode('unicode_escape').decode()}\n"
2371
- #rtnStr += f"Key presses: {__keyPressesIn}\n"
2365
+ if keyPressesIn[-1]:
2366
+ CMDsOut = [''.join(cmd).encode(encoding=encoding,errors='backslashreplace').decode(encoding=encoding,errors='backslashreplace').replace('\\n', '↵') for cmd in keyPressesIn if cmd]
2372
2367
  rtnStr += "User Inputs: \n "
2373
2368
  rtnStr += '\n '.join(CMDsOut)
2374
2369
  rtnStr += '\n'
2375
- __keyPressesIn[-1].clear()
2376
- if __global_suppress_printout and not outputs:
2370
+ keyPressesIn[-1].clear()
2371
+ if quiet and not outputs:
2377
2372
  rtnStr += 'Success'
2378
2373
  return rtnStr
2379
2374
 
@@ -2389,7 +2384,10 @@ def print_output(hosts,usejson = False,quiet = False,greppable = False):
2389
2384
  Returns:
2390
2385
  str: The pretty output generated
2391
2386
  '''
2392
- rtnStr = generate_output(hosts,usejson,greppable)
2387
+ global __global_suppress_printout
2388
+ global _encoding
2389
+ global __keyPressesIn
2390
+ rtnStr = generate_output(hosts,usejson,greppable,quiet=__global_suppress_printout,encoding=_encoding,keyPressesIn=__keyPressesIn)
2393
2391
  if not quiet:
2394
2392
  print(rtnStr)
2395
2393
  return rtnStr
@@ -2424,56 +2422,56 @@ def processRunOnHosts(timeout, password, max_connections, hosts, returnUnfinishe
2424
2422
  sleep_interval *= 1.1
2425
2423
  for thread in threads:
2426
2424
  thread.join(timeout=3)
2427
- # update the unavailable hosts and global unavailable hosts
2428
- if willUpdateUnreachableHosts:
2429
- availableHosts = set()
2430
- for host in hosts:
2431
- 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)):
2432
- unavailableHosts[host.name] = int(time.monotonic())
2433
- __globalUnavailableHosts[host.name] = int(time.monotonic())
2434
- else:
2435
- availableHosts.add(host.name)
2436
- if host.name in unavailableHosts:
2437
- del unavailableHosts[host.name]
2438
- if host.name in __globalUnavailableHosts:
2439
- del __globalUnavailableHosts[host.name]
2440
- if __DEBUG_MODE:
2441
- print(f'Unreachable hosts: {unavailableHosts}')
2442
- try:
2443
- # check for the old content, only update if the new content is different
2444
- if not os.path.exists(os.path.join(tempfile.gettempdir(),f'__{getpass.getuser()}_multiSSH3_UNAVAILABLE_HOSTS.csv')):
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.items())
2447
- else:
2448
- oldDic = {}
2449
- try:
2450
- with open(os.path.join(tempfile.gettempdir(),f'__{getpass.getuser()}_multiSSH3_UNAVAILABLE_HOSTS.csv'),'r') as f:
2451
- for line in f:
2452
- line = line.strip()
2453
- if line and ',' in line and len(line.split(',')) >= 2 and line.split(',')[0] and line.split(',')[1].isdigit():
2454
- hostname = line.split(',')[0]
2455
- expireTime = int(line.split(',')[1])
2456
- if expireTime < time.monotonic() and hostname not in availableHosts:
2457
- oldDic[hostname] = expireTime
2458
- except:
2459
- pass
2460
- # add new entries
2461
- oldDic.update(unavailableHosts)
2462
- with open(os.path.join(tempfile.gettempdir(),getpass.getuser()+'__multiSSH3_UNAVAILABLE_HOSTS.csv.new'),'w') as f:
2463
- for key, value in oldDic.items():
2464
- f.write(f'{key},{value}\n')
2465
- 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'))
2466
- except Exception as e:
2467
- eprint(f'Error writing to temporary file: {e!r}')
2468
- import traceback
2469
- eprint(traceback.format_exc())
2425
+ # update the unavailable hosts and global unavailable hosts
2426
+ if willUpdateUnreachableHosts:
2427
+ availableHosts = set()
2428
+ for host in hosts:
2429
+ 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)):
2430
+ unavailableHosts[host.name] = int(time.monotonic())
2431
+ __globalUnavailableHosts[host.name] = int(time.monotonic())
2432
+ else:
2433
+ availableHosts.add(host.name)
2434
+ if host.name in unavailableHosts:
2435
+ del unavailableHosts[host.name]
2436
+ if host.name in __globalUnavailableHosts:
2437
+ del __globalUnavailableHosts[host.name]
2438
+ if __DEBUG_MODE:
2439
+ print(f'Unreachable hosts: {unavailableHosts}')
2440
+ try:
2441
+ # check for the old content, only update if the new content is different
2442
+ if not os.path.exists(os.path.join(tempfile.gettempdir(),f'__{getpass.getuser()}_multiSSH3_UNAVAILABLE_HOSTS.csv')):
2443
+ with open(os.path.join(tempfile.gettempdir(),f'__{getpass.getuser()}_multiSSH3_UNAVAILABLE_HOSTS.csv'),'w') as f:
2444
+ f.writelines(f'{host},{expTime}' for host,expTime in unavailableHosts.items())
2445
+ else:
2446
+ oldDic = {}
2447
+ try:
2448
+ with open(os.path.join(tempfile.gettempdir(),f'__{getpass.getuser()}_multiSSH3_UNAVAILABLE_HOSTS.csv'),'r') as f:
2449
+ for line in f:
2450
+ line = line.strip()
2451
+ if line and ',' in line and len(line.split(',')) >= 2 and line.split(',')[0] and line.split(',')[1].isdigit():
2452
+ hostname = line.split(',')[0]
2453
+ expireTime = int(line.split(',')[1])
2454
+ if expireTime < time.monotonic() and hostname not in availableHosts:
2455
+ oldDic[hostname] = expireTime
2456
+ except:
2457
+ pass
2458
+ # add new entries
2459
+ oldDic.update(unavailableHosts)
2460
+ with open(os.path.join(tempfile.gettempdir(),getpass.getuser()+'__multiSSH3_UNAVAILABLE_HOSTS.csv.new'),'w') as f:
2461
+ for key, value in oldDic.items():
2462
+ f.write(f'{key},{value}\n')
2463
+ 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'))
2464
+ except Exception as e:
2465
+ eprint(f'Error writing to temporary file: {e!r}')
2466
+ import traceback
2467
+ eprint(traceback.format_exc())
2470
2468
 
2471
2469
  # print the output, if the output of multiple hosts are the same, we aggragate them
2472
2470
  if not called:
2473
2471
  print_output(hosts,json,greppable=greppable)
2474
2472
 
2475
2473
  #%% ------------ Stringfy Block ----------------
2476
- @cache_decorator
2474
+
2477
2475
  def formHostStr(host) -> str:
2478
2476
  """
2479
2477
  Forms a comma-separated string of hosts.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes