multiSSH3 6.18__tar.gz → 6.21__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multiSSH3
3
- Version: 6.18
3
+ Version: 6.21
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: 6.18
3
+ Version: 6.21
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
@@ -30,15 +30,15 @@ import threading
30
30
  import time
31
31
  import typing
32
32
  import uuid
33
- from collections import Counter, defaultdict, deque
33
+ from collections import Counter, defaultdict, deque, ChainMap
34
34
  from itertools import count, product
35
35
  from pprint import pformat
36
36
 
37
37
 
38
- version = '6.18'
38
+ version = '6.21'
39
39
  VERSION = version
40
40
  __version__ = version
41
- COMMIT_DATE = '2026-04-09'
41
+ COMMIT_DATE = '2026-04-16'
42
42
  # TODO: Add terminal TUI
43
43
 
44
44
  CONFIG_FILE_CHAIN = ['./multiSSH3.config.json',
@@ -70,10 +70,22 @@ DEFAULT_REPEAT = 1
70
70
  DEFAULT_INTERVAL = 0
71
71
  DEFAULT_IPMI = False
72
72
  DEFAULT_IPMI_INTERFACE_IP_PREFIX = ''
73
+ DEFAULT_IPMI_USERNAME = 'admin'
74
+ DEFAULT_IPMI_PASSWORD = 'admin'
75
+ DEFAULT_IPMI_ARGS=''
76
+ DEFAULT_IPMI_METHOD = 'ipmitool'
77
+ DEFAULT_IPMI_RETRY_LOCKOUT_TIME = -1
78
+ def get_default_ipmi_definition():
79
+ return {'interface_ip_prefix': DEFAULT_IPMI_INTERFACE_IP_PREFIX,
80
+ 'username': DEFAULT_IPMI_USERNAME,
81
+ 'password': DEFAULT_IPMI_PASSWORD,
82
+ 'args': DEFAULT_IPMI_ARGS,
83
+ 'ipmi_method': DEFAULT_IPMI_METHOD,
84
+ 'hostname_keyword': '',
85
+ 'retry_lockout_time': DEFAULT_IPMI_RETRY_LOCKOUT_TIME,
86
+ }
87
+ DEFAULT_IPMI_DEFINITIONS = [get_default_ipmi_definition()]
73
88
  DEFAULT_INTERFACE_IP_PREFIX = None
74
- DEFAULT_IPMI_USERNAME = 'ADMIN'
75
- DEFAULT_IPMI_PASSWORD = ''
76
- DEAFULT_IPMI_ARGS=''
77
89
  DEFAULT_NO_WATCH = False
78
90
  DEFAULT_WINDOW_WIDTH = 40
79
91
  DEFAULT_WINDOW_HEIGHT = 1
@@ -325,6 +337,7 @@ def _get_i():
325
337
 
326
338
  #%% ------------ Load Defaults ( Config ) File ----------------
327
339
  def load_config_file(config_file):
340
+ global __working_config_file
328
341
  '''
329
342
  Load the config file to global variables
330
343
 
@@ -339,6 +352,7 @@ def load_config_file(config_file):
339
352
  try:
340
353
  with open(config_file,'r') as f:
341
354
  config = json.load(f)
355
+ __working_config_file = config_file
342
356
  except Exception as e:
343
357
  eprint(f"Error: Cannot load config file {config_file!r}: {e}")
344
358
  return {}
@@ -346,6 +360,7 @@ def load_config_file(config_file):
346
360
 
347
361
  #%% Load Config Based Default Global variables
348
362
  __configs_from_file = {}
363
+ __working_config_file = './multiSSH3.config.json'
349
364
  for config_file in reversed(CONFIG_FILE_CHAIN.copy()):
350
365
  __configs_from_file.update(load_config_file(os.path.expanduser(config_file)))
351
366
  globals().update(__configs_from_file)
@@ -367,8 +382,6 @@ __wildCharacters = ['*','?','x']
367
382
  _no_env = DEFAULT_NO_ENV
368
383
  _env_files = DEFAULT_ENV_FILES
369
384
  __globalUnavailableHosts = dict()
370
- __ipmiiInterfaceIPPrefix = DEFAULT_IPMI_INTERFACE_IP_PREFIX
371
- __ipmi_args = DEAFULT_IPMI_ARGS
372
385
  __keyPressesIn = [[]]
373
386
  _emo = False
374
387
  __curses_global_color_pairs = {(-1,-1):1}
@@ -405,7 +418,7 @@ if __resource_lib_available:
405
418
  resource.setrlimit(resource.RLIMIT_NOFILE, (__system_nofile_limit, __system_nofile_limit))
406
419
  __max_connections_nofile_limit_supported = int((__system_nofile_limit - 10) / 3)
407
420
  _binPaths = {}
408
- _binCalled = set(['sshpass', 'ssh', 'scp', 'ipmitool','rsync','sh','ssh-copy-id'])
421
+ _binCalled = set(['sshpass', 'ssh', 'scp', 'ipmitool','rsync','sh','ssh-copy-id','redfishtool'])
409
422
  def check_path(program_name):
410
423
  global __configs_from_file
411
424
  global _binPaths
@@ -1789,7 +1802,7 @@ def __handle_writing_stream(stream,stop_event,host):
1789
1802
  # host.stdout.append(' $ ' + ''.join(__keyPressesIn[-1]).encode().decode().replace('\n', '↵'))
1790
1803
  return sentInputPos
1791
1804
 
1792
- def run_command(host, sem, timeout=60,passwds=None, retry_limit = 7,ipmi_args = ...):
1805
+ def run_command(host, sem, timeout=60,passwds=None, retry_limit = 7 + len(DEFAULT_IPMI_DEFINITIONS),ipmi_definitions_list = ...):
1793
1806
  '''
1794
1807
  Run the command on the host. Will format the commands accordingly. Main execution function.
1795
1808
 
@@ -1804,45 +1817,67 @@ def run_command(host, sem, timeout=60,passwds=None, retry_limit = 7,ipmi_args =
1804
1817
  '''
1805
1818
  global _emo
1806
1819
  global __ERROR_MESSAGES_TO_IGNORE_REGEX
1807
- global __ipmiiInterfaceIPPrefix
1808
1820
  global _binPaths
1809
1821
  global __DEBUG_MODE
1810
- global DEFAULT_IPMI_USERNAME
1811
- global DEFAULT_IPMI_PASSWORD
1812
1822
  global DEFAULT_USERNAME
1813
1823
  global DEFAULT_PASSWORD
1814
1824
  global SSH_STRICT_HOST_KEY_CHECKING
1815
- global __ipmi_args
1816
- global DEAFULT_IPMI_ARGS
1817
1825
  if retry_limit < 0:
1818
1826
  host.output.append('Error: Retry limit reached!')
1819
1827
  host.stderr.append('Error: Retry limit reached!')
1820
1828
  host.returncode = 1
1821
1829
  return
1822
- if ipmi_args is ...:
1823
- ipmi_args = __ipmi_args
1824
- prePasword = passwds
1830
+ ipmi_definition = {}
1831
+ username = None
1832
+ sshLocalExtraArgs = []
1833
+ ip_prefix = None
1825
1834
  try:
1826
- localExtraArgs = []
1827
-
1828
1835
  if not SSH_STRICT_HOST_KEY_CHECKING:
1829
- localExtraArgs = ['-o StrictHostKeyChecking=no','-o UserKnownHostsFile=/dev/null']
1836
+ sshLocalExtraArgs = ['-o StrictHostKeyChecking=no','-o UserKnownHostsFile=/dev/null']
1830
1837
  if host.identity_file:
1831
- localExtraArgs += ['-i',host.identity_file]
1832
- rsyncLocalExtraArgs = ['--rsh','ssh ' + ' '.join(localExtraArgs)]
1833
- host.username = None
1838
+ sshLocalExtraArgs += ['-i',host.identity_file]
1839
+ rsyncLocalExtraArgs = ['--rsh','ssh ' + ' '.join(sshLocalExtraArgs)]
1834
1840
  host.address = host.name
1835
1841
  if '@' in host.name:
1836
- host.username, host.address = host.name.rsplit('@',1)
1842
+ username, host.address = host.name.rsplit('@',1)
1843
+ if host.ipmi:
1844
+ if ipmi_definitions_list is ...:
1845
+ ipmi_definitions_list = [ipmi_definition for ipmi_definition in DEFAULT_IPMI_DEFINITIONS if not ipmi_definition.get('hostname_keyword') or str(ipmi_definition.get('hostname_keyword')).strip() in host.name]
1846
+ if not ipmi_definitions_list:
1847
+ host.output.append('Error: Exhausted all matching ipmi definitions!')
1848
+ host.stderr.append('Error: Exhausted all matching ipmi definitions!')
1849
+ host.returncode = 1
1850
+ return
1851
+ ipmi_definition = dict(ChainMap(ipmi_definitions_list.pop(0),get_default_ipmi_definition()))
1852
+ if ipmi_definition.get('ipmi_method') == 'ssh_ipmitool':
1853
+ if not host.command:
1854
+ host.command = 'ipmitool power status'
1855
+ else:
1856
+ host.command = 'ipmitool '+host.command if not host.command.startswith('ipmitool ') else host.command
1857
+ if username:
1858
+ host.output.append(f'Warning:-u SSH_USERNAME {username} is provided in the host name! Using the username from the ipmi definition if provided: {ipmi_definition.get("username")}')
1859
+ if __DEBUG_MODE:
1860
+ host.stderr.append(f'Warning:-u SSH_USERNAME {username} is provided in the host name! Using the username from the ipmi definition if provided: {ipmi_definition.get("username")}')
1861
+ username = ipmi_definition.get('username',username)
1862
+ if passwds:
1863
+ host.output.append(f'Warning: -p SSH_PASSWORD {passwds} is provided! Using the password from the ipmi definition if provided: {ipmi_definition.get("password")}')
1864
+ if __DEBUG_MODE:
1865
+ host.stderr.append(f'Warning: -p SSH_PASSWORD {passwds} is provided! Using the password from the ipmi definition if provided: {ipmi_definition.get("password")}')
1866
+ passwds = ipmi_definition.get('password',passwds)
1867
+ if ipmi_definition.get('interface_ip_prefix') and ipmi_definition.get('interface_ip_prefix') == DEFAULT_IPMI_INTERFACE_IP_PREFIX:
1868
+ host.output.append('Warning: ipmi_interface_ip_prefix that is the same as the DEFAULT_IPMI_INTERFACE_IP_PREFIX is ignored in ssh_ipmitool mode.')
1869
+ if __DEBUG_MODE:
1870
+ host.stderr.append('Warning: ipmi_interface_ip_prefix that is the same as the DEFAULT_IPMI_INTERFACE_IP_PREFIX is ignored in ssh_ipmitool mode.')
1871
+ else:
1872
+ ip_prefix = ipmi_definition.get('interface_ip_prefix')
1837
1873
  host.command = replace_magic_strings(host.command,['#HOST#','#HOSTNAME#'],host.address,case_sensitive=False)
1838
- if host.username:
1839
- host.command = replace_magic_strings(host.command,['#USER#','#USERNAME#'],host.username,case_sensitive=False)
1874
+ if username:
1875
+ host.command = replace_magic_strings(host.command,['#USER#','#USERNAME#'],username,case_sensitive=False)
1840
1876
  else:
1841
1877
  current_user = getpass.getuser()
1842
1878
  host.command = replace_magic_strings(host.command,['#USER#','#USERNAME#'],current_user,case_sensitive=False)
1843
1879
  host.command = replace_magic_strings(host.command,['#ID#'],str(id(host)),case_sensitive=False)
1844
1880
  host.command = replace_magic_strings(host.command,['#I#'],str(host.i),case_sensitive=False)
1845
- host.command = replace_magic_strings(host.command,['#PASSWD#','#PASSWORD#'],passwds,case_sensitive=False)
1846
1881
  host.command = replace_magic_strings(host.command,['#UUID#'],str(host.uuid),case_sensitive=False)
1847
1882
  host.command = replace_magic_strings(host.command,['#IP#'],str(host.ip),case_sensitive=False)
1848
1883
  formatedCMD = []
@@ -1852,63 +1887,65 @@ def run_command(host, sem, timeout=60,passwds=None, retry_limit = 7,ipmi_args =
1852
1887
  extraargs = [str(arg) for arg in host.extraargs]
1853
1888
  else:
1854
1889
  extraargs = []
1855
- if __ipmiiInterfaceIPPrefix:
1856
- host.interface_ip_prefix = __ipmiiInterfaceIPPrefix if host.ipmi and not host.interface_ip_prefix else host.interface_ip_prefix
1857
- if host.interface_ip_prefix:
1890
+ if not ip_prefix:
1891
+ ip_prefix = host.interface_ip_prefix
1892
+ if ip_prefix:
1858
1893
  try:
1859
1894
  hostOctets = host.ip.split('.')
1860
- prefixOctets = host.interface_ip_prefix.split('.')
1895
+ prefixOctets = ip_prefix.split('.')
1861
1896
  host.address = '.'.join(prefixOctets[:3]+hostOctets[min(3,len(prefixOctets)):])
1862
- host.resolvedName = host.username + '@' if host.username else ''
1863
- host.resolvedName += host.address
1864
1897
  except Exception:
1865
- host.resolvedName = host.name
1898
+ host.output.append(f'Warning: Failed to resolve the IP address for the host {host.name}. Unable to resolve ip_prefix {ip_prefix}!')
1899
+ if __DEBUG_MODE:
1900
+ host.stderr.append(f'Warning: Failed to resolve the IP address for the host {host.name}. Unable to resolve ip_prefix {ip_prefix}!')
1901
+ if username:
1902
+ host.resolvedName = username + '@' + host.address
1866
1903
  else:
1867
- host.resolvedName = host.name
1904
+ host.resolvedName = host.address
1868
1905
  if host.resolvedName:
1869
1906
  host.command = replace_magic_strings(host.command,['#RESOLVEDNAME#','#RESOLVED#'],host.resolvedName,case_sensitive=False)
1870
- if host.ipmi:
1871
- if 'ipmitool' in _binPaths:
1907
+ if __DEBUG_MODE:
1908
+ host.stderr.append(f'Resolved name: {host.resolvedName}')
1909
+ if host.ipmi and ipmi_definition.get('ipmi_method') != 'ssh_ipmitool':
1910
+ #{interface_ip_prefix,username,password,args,ipmi_method,hostname_keyword,retry_lockout_time}
1911
+ if ipmi_definition.get('ipmi_method') == 'ipmitool':
1912
+ if 'ipmitool' not in _binPaths:
1913
+ host.output.append('Ipmitool not found on the local machine! Trying ipmitool on the remote machine...')
1914
+ if __DEBUG_MODE:
1915
+ host.stderr.append('Ipmitool not found on the local machine! Trying ipmitool on the remote machine...')
1916
+ if ipmi_definition.get('interface_ip_prefix') == DEFAULT_IPMI_INTERFACE_IP_PREFIX:
1917
+ ipmi_definition['interface_ip_prefix'] = DEFAULT_INTERFACE_IP_PREFIX
1918
+ ipmi_definition['username'] = username
1919
+ ipmi_definition['password'] = passwds
1920
+ ipmi_definition['ipmi_method'] = 'ssh_ipmitool'
1921
+ ipmi_definitions_list.insert(0,ipmi_definition)
1922
+ return run_command(host,sem,timeout,passwds,retry_limit=retry_limit - 1,ipmi_definitions_list=ipmi_definitions_list)
1872
1923
  if host.command.startswith('ipmitool '):
1873
1924
  host.command = host.command.replace('ipmitool ','')
1874
1925
  elif host.command.startswith(_binPaths['ipmitool']):
1875
1926
  host.command = host.command.replace(_binPaths['ipmitool'],'')
1876
- if not host.username or host.username == DEFAULT_USERNAME:
1877
- if DEFAULT_IPMI_USERNAME:
1878
- host.username = DEFAULT_IPMI_USERNAME
1879
- elif DEFAULT_USERNAME:
1880
- host.username = DEFAULT_USERNAME
1881
- else:
1882
- host.username = 'ADMIN'
1883
- if not passwds or passwds == DEFAULT_PASSWORD:
1884
- if DEFAULT_IPMI_PASSWORD:
1885
- passwds = DEFAULT_IPMI_PASSWORD
1886
- elif DEFAULT_PASSWORD:
1887
- passwds = DEFAULT_PASSWORD
1888
- else:
1889
- host.output.append('Warning: Password not provided for ipmi! Using a default password `admin`.')
1890
- passwds = 'admin'
1891
1927
  if not host.command:
1892
1928
  host.command = 'power status'
1929
+ ipmi_args = ipmi_definition.get('args','')
1893
1930
  if 'sh' in _binPaths:
1894
- formatedCMD = [_binPaths['sh'],'-c',f'ipmitool -H {host.address} -U {host.username} -P {passwds} {ipmi_args} {" ".join(extraargs)} {host.command}']
1931
+ formatedCMD = [_binPaths['sh'],'-c',f'ipmitool -H {host.address} -U {ipmi_definition.get("username",username)} -P {ipmi_definition.get("password",passwds)} {ipmi_args} {host.command}']
1895
1932
  else:
1896
- formatedCMD = [_binPaths['ipmitool'],f'-H {host.address}',f'-U {host.username}',f'-P {passwds}'] + [f'-{s}' for s in ipmi_args.split('-') if s] + extraargs + [host.command]
1897
- elif 'ssh' in _binPaths:
1898
- host.output.append('Ipmitool not found on the local machine! Trying ipmitool on the remote machine...')
1899
- if __DEBUG_MODE:
1900
- host.stderr.append('Ipmitool not found on the local machine! Trying ipmitool on the remote machine...')
1901
- host.ipmi = False
1902
- host.interface_ip_prefix = None
1933
+ formatedCMD = [_binPaths['ipmitool'],f'-H {host.address}',f'-U {ipmi_definition.get("username",username)}',f'-P {ipmi_definition.get("password",passwds)}'] + [f'-{s}' for s in ipmi_args.split('-') if s] + [host.command]
1934
+ elif ipmi_definition.get('ipmi_method') == 'redfish':
1935
+ if host.command.startswith('redfishtool '):
1936
+ host.command = host.command.replace('redfishtool ','')
1937
+ elif host.command.startswith(_binPaths['redfishtool']):
1938
+ host.command = host.command.replace(_binPaths['redfishtool'],'')
1903
1939
  if not host.command:
1904
- host.command = 'ipmitool power status'
1940
+ host.command = 'Systems'
1941
+ ipmi_args = ipmi_definition.get('args','')
1942
+ if 'sh' in _binPaths:
1943
+ formatedCMD = [_binPaths['sh'],'-c',f'redfishtool -S Always -a -r {host.address} -u {ipmi_definition.get("username",username)} -p {ipmi_definition.get("password",passwds)} {ipmi_args} {host.command}']
1905
1944
  else:
1906
- host.command = 'ipmitool '+host.command if not host.command.startswith('ipmitool ') else host.command
1907
- run_command(host,sem,timeout,prePasword,retry_limit=retry_limit - 1,ipmi_args=ipmi_args)
1908
- return
1945
+ formatedCMD = [_binPaths['redfishtool'],'-S', 'Always','-a','-r',host.address,'-u',ipmi_definition.get("username",username),'-p',ipmi_definition.get("password",passwds)] + [f'-{s}' for s in ipmi_args.split('-') if s] + [host.command]
1909
1946
  else:
1910
- host.output.append('Ipmitool not found on the local machine! Please install ipmitool to use ipmi mode.')
1911
- host.stderr.append('Ipmitool not found on the local machine! Please install ipmitool to use ipmi mode.')
1947
+ host.output.append(f'IPMI method {ipmi_definition.get("ipmi_method","None")} not supported!')
1948
+ host.stderr.append(f'IPMI method {ipmi_definition.get("ipmi_method","None")} not supported!')
1912
1949
  host.returncode = 1
1913
1950
  return
1914
1951
  elif host.shell:
@@ -1923,7 +1960,7 @@ def run_command(host, sem, timeout=60,passwds=None, retry_limit = 7,ipmi_args =
1923
1960
  host.stderr.append('shell not found on the local machine! Using ssh localhost instead...')
1924
1961
  host.shell = False
1925
1962
  host.name = 'localhost'
1926
- run_command(host,sem,timeout,prePasword,retry_limit=retry_limit - 1,ipmi_args=ipmi_args)
1963
+ run_command(host,sem,timeout,passwds,retry_limit=retry_limit - 1,ipmi_definitions_list=ipmi_definitions_list)
1927
1964
  else:
1928
1965
  if host.files:
1929
1966
  if host.scp:
@@ -1956,11 +1993,11 @@ def run_command(host, sem, timeout=60,passwds=None, retry_limit = 7,ipmi_args =
1956
1993
  else:
1957
1994
  fileArgs = host.files + [f'{host.resolvedName}:{host.command}']
1958
1995
  if useScp:
1959
- formatedCMD = [_binPaths['scp'],'-rp'] + localExtraArgs + extraargs +['--']+fileArgs
1996
+ formatedCMD = [_binPaths['scp'],'-rp'] + sshLocalExtraArgs + extraargs +['--']+fileArgs
1960
1997
  else:
1961
1998
  formatedCMD = [_binPaths['rsync'],'-ahlX','--partial','--inplace', '--info=name'] + rsyncLocalExtraArgs + extraargs +['--']+fileArgs
1962
1999
  else:
1963
- formatedCMD = [_binPaths['ssh']] + localExtraArgs + extraargs +['--']+ [host.resolvedName, host.command]
2000
+ formatedCMD = [_binPaths['ssh']] + sshLocalExtraArgs + extraargs +['--']+ [host.resolvedName, host.command]
1964
2001
  if passwds and 'sshpass' in _binPaths:
1965
2002
  formatedCMD = [_binPaths['sshpass'], '-p', passwds] + formatedCMD
1966
2003
  elif passwds:
@@ -2077,7 +2114,7 @@ def run_command(host, sem, timeout=60,passwds=None, retry_limit = 7,ipmi_args =
2077
2114
  host.output.append("Warning: Too many open files. retrying...")
2078
2115
  # Handle the error, e.g., clean up, retry logic, or exit
2079
2116
  time.sleep(0.1)
2080
- run_command(host,sem,timeout,prePasword,retry_limit=retry_limit - 1,ipmi_args=ipmi_args)
2117
+ run_command(host,sem,timeout,passwds,retry_limit=retry_limit - 1,ipmi_definitions_list=ipmi_definitions_list)
2081
2118
  else:
2082
2119
  # Re-raise the exception if it's not the specific one
2083
2120
  raise
@@ -2088,39 +2125,47 @@ def run_command(host, sem, timeout=60,passwds=None, retry_limit = 7,ipmi_args =
2088
2125
  host.stderr.extend(traceback.format_exc().split('\n'))
2089
2126
  host.output.extend(traceback.format_exc().split('\n'))
2090
2127
  host.returncode = -1
2091
- # If using ipmi, we will try again using ssh if ipmi connection is not successful
2092
- if host.ipmi and host.returncode != 0 and any(['Unable to establish IPMI' in line for line in host.stderr]):
2128
+ # If using ipmi, we will try again using next ipmi definition if ipmi connection is not successful
2129
+ if host.ipmi and host.returncode != 0:
2093
2130
  host.returncode = None
2094
- host.stderr = []
2095
- if ipmi_args:
2096
- if DEAFULT_IPMI_ARGS and ipmi_args != DEAFULT_IPMI_ARGS:
2097
- host.output.append(f'IPMI connection failed with arg {ipmi_args}! Trying DEFAULT_IPMI_ARGS:{DEAFULT_IPMI_ARGS}')
2098
- if __DEBUG_MODE:
2099
- host.stderr.append(f'IPMI connection failed with arg {ipmi_args}! Trying DEFAULT_IPMI_ARGS:{DEAFULT_IPMI_ARGS}')
2100
- ipmi_args = DEAFULT_IPMI_ARGS
2101
- else:
2102
- host.output.append(f'IPMI connection failed with arg {ipmi_args}! Trying without args...')
2103
- if __DEBUG_MODE:
2104
- host.stderr.append(f'IPMI connection failed with arg {ipmi_args}! Trying without args...')
2105
- ipmi_args = ''
2131
+ host.output.append('IPMI connection failed! Trying next ipmi definition...')
2132
+ if __DEBUG_MODE:
2133
+ host.stderr.append('IPMI connection failed! Trying next ipmi definition...')
2106
2134
  else:
2107
- host.output.append('IPMI connection failed! Trying SSH connection...')
2108
- if __DEBUG_MODE:
2109
- host.stderr.append('IPMI connection failed! Trying SSH connection...')
2110
- host.ipmi = False
2111
- host.interface_ip_prefix = None
2112
- host.command = 'ipmitool '+host.command if not host.command.startswith('ipmitool ') else host.command
2113
- run_command(host,sem,timeout,prePasword,retry_limit=retry_limit - 1,ipmi_args=ipmi_args)
2135
+ host.stdout = []
2136
+ host.stderr = []
2137
+ if ipmi_definition.get('retry_lockout_time'):
2138
+ try:
2139
+ sleep_seconds = int(ipmi_definition.get('retry_lockout_time'))
2140
+ if sleep_seconds >= 0:
2141
+ host.output.append(f'Sleeping for {sleep_seconds} seconds...')
2142
+ if __DEBUG_MODE:
2143
+ host.stderr.append(f'Sleeping for {sleep_seconds} seconds...')
2144
+ time.sleep(sleep_seconds)
2145
+ except Exception as e:
2146
+ host.output.append(f'Error: Failed to sleep for {ipmi_definition.get("retry_lockout_time")} seconds! {e}')
2147
+ if __DEBUG_MODE:
2148
+ host.stderr.append(f'Error: Failed to sleep for {ipmi_definition.get("retry_lockout_time")} seconds! {e}')
2149
+ time.sleep(DEFAULT_IPMI_RETRY_LOCKOUT_TIME)
2150
+ if ipmi_definition.get('ipmi_method') == 'ipmitool':
2151
+ if ipmi_definition.get('interface_ip_prefix') == DEFAULT_IPMI_INTERFACE_IP_PREFIX:
2152
+ ipmi_definition['interface_ip_prefix'] = DEFAULT_INTERFACE_IP_PREFIX
2153
+ ipmi_definition['username'] = username
2154
+ ipmi_definition['password'] = passwds
2155
+ ipmi_definition['ipmi_method'] = 'ssh_ipmitool'
2156
+ ipmi_definitions_list.append(ipmi_definition)
2157
+ run_command(host,sem,timeout,passwds,retry_limit=retry_limit - 1,ipmi_definitions_list=ipmi_definitions_list)
2114
2158
  # If transfering files, we will try again using scp if rsync connection is not successful
2115
2159
  elif host.files and not host.scp and not useScp and host.returncode != 0 and host.stderr:
2116
2160
  host.returncode = None
2117
- host.stderr = []
2118
- host.stdout = []
2119
2161
  host.output.append('Rsync connection failed! Trying SCP connection...')
2120
2162
  if __DEBUG_MODE:
2121
2163
  host.stderr.append('Rsync connection failed! Trying SCP connection...')
2164
+ else:
2165
+ host.stdout = []
2166
+ host.stderr = []
2122
2167
  host.scp = True
2123
- run_command(host,sem,timeout,prePasword,retry_limit=retry_limit - 1,ipmi_args=ipmi_args)
2168
+ run_command(host,sem,timeout,passwds,retry_limit=retry_limit - 1,ipmi_definitions_list=ipmi_definitions_list)
2124
2169
 
2125
2170
  #%% ------------ Start Threading Block ----------------
2126
2171
  def start_run_on_hosts(hosts, timeout=60,password=None,max_connections=4 * os.cpu_count()):
@@ -3378,16 +3423,35 @@ def formHostStr(host) -> str:
3378
3423
  return ','.join(compact_hostnames(host))
3379
3424
 
3380
3425
  @cache_decorator
3381
- def __formCommandArgStr(oneonone = DEFAULT_ONE_ON_ONE, timeout = DEFAULT_TIMEOUT,password = DEFAULT_PASSWORD,
3382
- no_watch = DEFAULT_NO_WATCH,json = DEFAULT_JSON_MODE,max_connections=DEFAULT_MAX_CONNECTIONS,
3383
- file = None,ipmi = DEFAULT_IPMI,interface_ip_prefix = DEFAULT_INTERFACE_IP_PREFIX,
3384
- scp=DEFAULT_SCP,gather_mode = False,username=DEFAULT_USERNAME,extraargs=DEFAULT_EXTRA_ARGS,skip_unreachable=DEFAULT_SKIP_UNREACHABLE,
3385
- no_env=DEFAULT_NO_ENV,greppable=DEFAULT_GREPPABLE_MODE,skip_hosts = DEFAULT_SKIP_HOSTS,
3386
- file_sync = False, error_only = DEFAULT_ERROR_ONLY, identity_file = DEFAULT_IDENTITY_FILE,
3387
- copy_id = False, unavailable_host_expiry = DEFAULT_UNAVAILABLE_HOST_EXPIRY, no_history = DEFAULT_NO_HISTORY,
3388
- history_file = DEFAULT_HISTORY_FILE, env_file = '', env_files = DEFAULT_ENV_FILES,
3389
- repeat = DEFAULT_REPEAT,interval = DEFAULT_INTERVAL,
3390
- shortend = False) -> str:
3426
+ def __formCommandArgStr(shortend = False, **kwargs) -> str:
3427
+ oneonone = kwargs.get('oneonone', DEFAULT_ONE_ON_ONE)
3428
+ timeout = kwargs.get('timeout', DEFAULT_TIMEOUT)
3429
+ password = kwargs.get('password', DEFAULT_PASSWORD)
3430
+ no_watch = kwargs.get('no_watch', DEFAULT_NO_WATCH)
3431
+ json = kwargs.get('json', DEFAULT_JSON_MODE)
3432
+ max_connections = kwargs.get('max_connections', DEFAULT_MAX_CONNECTIONS)
3433
+ file = kwargs.get('file', None)
3434
+ ipmi = kwargs.get('ipmi', DEFAULT_IPMI)
3435
+ interface_ip_prefix = kwargs.get('interface_ip_prefix', DEFAULT_INTERFACE_IP_PREFIX)
3436
+ scp = kwargs.get('scp', DEFAULT_SCP)
3437
+ gather_mode = kwargs.get('gather_mode', False)
3438
+ username = kwargs.get('username', DEFAULT_USERNAME)
3439
+ extraargs = kwargs.get('extraargs', DEFAULT_EXTRA_ARGS)
3440
+ skip_unreachable = kwargs.get('skip_unreachable', DEFAULT_SKIP_UNREACHABLE)
3441
+ no_env = kwargs.get('no_env', DEFAULT_NO_ENV)
3442
+ greppable = kwargs.get('greppable', DEFAULT_GREPPABLE_MODE)
3443
+ skip_hosts = kwargs.get('skip_hosts', DEFAULT_SKIP_HOSTS)
3444
+ file_sync = kwargs.get('file_sync', False)
3445
+ error_only = kwargs.get('error_only', DEFAULT_ERROR_ONLY)
3446
+ identity_file = kwargs.get('identity_file', DEFAULT_IDENTITY_FILE)
3447
+ copy_id = kwargs.get('copy_id', False)
3448
+ unavailable_host_expiry = kwargs.get('unavailable_host_expiry', DEFAULT_UNAVAILABLE_HOST_EXPIRY)
3449
+ no_history = kwargs.get('no_history', DEFAULT_NO_HISTORY)
3450
+ history_file = kwargs.get('history_file', DEFAULT_HISTORY_FILE)
3451
+ env_file = kwargs.get('env_file', '')
3452
+ env_files = kwargs.get('env_files', DEFAULT_ENV_FILES)
3453
+ repeat = kwargs.get('repeat', DEFAULT_REPEAT)
3454
+ interval = kwargs.get('interval', DEFAULT_INTERVAL)
3391
3455
  argsList = []
3392
3456
  if oneonone:
3393
3457
  argsList.append('--oneonone' if not shortend else '-11')
@@ -3449,37 +3513,22 @@ def __formCommandArgStr(oneonone = DEFAULT_ONE_ON_ONE, timeout = DEFAULT_TIMEOUT
3449
3513
  argsList.append('--file_sync' if not shortend else '-fs')
3450
3514
  return ' '.join(argsList)
3451
3515
 
3452
- def getStrCommand(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAULT_ONE_ON_ONE, timeout = DEFAULT_TIMEOUT,password = DEFAULT_PASSWORD,
3453
- no_watch = DEFAULT_NO_WATCH,json = DEFAULT_JSON_MODE,called = _DEFAULT_CALLED,max_connections=DEFAULT_MAX_CONNECTIONS,
3454
- file = None,ipmi = DEFAULT_IPMI,interface_ip_prefix = DEFAULT_INTERFACE_IP_PREFIX,return_unfinished = _DEFAULT_RETURN_UNFINISHED,
3455
- scp=DEFAULT_SCP,gather_mode = False,username=DEFAULT_USERNAME,extraargs=DEFAULT_EXTRA_ARGS,skip_unreachable=DEFAULT_SKIP_UNREACHABLE,
3456
- no_env=DEFAULT_NO_ENV,greppable=DEFAULT_GREPPABLE_MODE,will_update_unreachable_hosts=_DEFAULT_UPDATE_UNREACHABLE_HOSTS,no_start=_DEFAULT_NO_START,
3457
- skip_hosts = DEFAULT_SKIP_HOSTS, window_width = DEFAULT_WINDOW_WIDTH, window_height = DEFAULT_WINDOW_HEIGHT,
3458
- single_window = DEFAULT_SINGLE_WINDOW,file_sync = False,error_only = DEFAULT_ERROR_ONLY, identity_file = DEFAULT_IDENTITY_FILE,
3459
- copy_id = False, unavailable_host_expiry = DEFAULT_UNAVAILABLE_HOST_EXPIRY,no_history = DEFAULT_NO_HISTORY,
3460
- history_file = DEFAULT_HISTORY_FILE, env_file = '', env_files = [],
3461
- repeat = DEFAULT_REPEAT,interval = DEFAULT_INTERVAL,
3462
- shortend = False,tabSeperated = False,**kwargs) -> str:
3463
- _ = called
3464
- _ = return_unfinished
3465
- _ = will_update_unreachable_hosts
3466
- _ = no_start
3467
- _ = window_width
3468
- _ = window_height
3469
- _ = single_window
3516
+ def getStrCommand(hosts = DEFAULT_HOSTS,commands = None,shortend = False,tabSeperated = False,**kwargs) -> str:
3517
+ # Allow explicit kwargs to override base parameters when forwarding dicts.
3518
+ hosts = kwargs.get('hosts', hosts)
3519
+ commands = kwargs.get('commands', commands)
3520
+ shortend = kwargs.get('shortend', shortend)
3521
+ tabSeperated = kwargs.get('tabSeperated', tabSeperated)
3470
3522
  hosts = hosts if isinstance(hosts,str) else frozenset(hosts)
3471
3523
  hostStr = formHostStr(hosts)
3472
- file = frozenset(file) if file else None
3473
- argsStr = __formCommandArgStr(oneonone = oneonone, timeout = timeout,password = password,
3474
- no_watch = no_watch,json = json,max_connections=max_connections,
3475
- file = file,ipmi = ipmi,interface_ip_prefix = interface_ip_prefix,
3476
- scp=scp,gather_mode = gather_mode,username=username,extraargs=extraargs,skip_unreachable=skip_unreachable,
3477
- no_env=no_env, greppable=greppable,skip_hosts = skip_hosts,
3478
- file_sync = file_sync,error_only = error_only, identity_file = identity_file,
3479
- copy_id = copy_id, unavailable_host_expiry =unavailable_host_expiry,no_history = no_history,
3480
- history_file = history_file, env_file = env_file, env_files = env_files,
3481
- repeat = repeat,interval = interval,
3482
- shortend = shortend)
3524
+ commandArgKwargs = dict(kwargs)
3525
+ commandArgKwargs.pop('hosts', None)
3526
+ commandArgKwargs.pop('commands', None)
3527
+ commandArgKwargs.pop('shortend', None)
3528
+ commandArgKwargs.pop('tabSeperated', None)
3529
+ if commandArgKwargs.get('file'):
3530
+ commandArgKwargs['file'] = frozenset(commandArgKwargs['file'])
3531
+ argsStr = __formCommandArgStr(shortend = shortend, **commandArgKwargs)
3483
3532
  commands = [command.replace('"', '\\"').replace('\n', '\\n').replace('\t', '\\t') for command in format_commands(commands)]
3484
3533
  commandStr = '"' + '" "'.join(commands) + '"' if commands else ''
3485
3534
  filePath = os.path.abspath(__file__)
@@ -3503,15 +3552,11 @@ def record_command_history(kwargs):
3503
3552
  global __global_suppress_printout
3504
3553
  global __DEBUG_MODE
3505
3554
  try:
3506
- history_file = os.path.expanduser(kwargs.get('history_file', DEFAULT_HISTORY_FILE))
3507
- import inspect
3508
- sig = inspect.signature(getStrCommand)
3509
- wanted = {
3510
- name: kwargs[name]
3511
- for name in sig.parameters
3512
- if name in kwargs
3513
- }
3514
- strCommand = getStrCommand(**wanted,shortend=True,tabSeperated=True)
3555
+ historyKwargs = dict(kwargs)
3556
+ historyKwargs['shortend'] = True
3557
+ historyKwargs['tabSeperated'] = True
3558
+ history_file = os.path.expanduser(historyKwargs.get('history_file', DEFAULT_HISTORY_FILE))
3559
+ strCommand = getStrCommand(**historyKwargs)
3515
3560
  with open(history_file, 'a') as f:
3516
3561
  # it follows <timestamp>\t<strCommand>\n
3517
3562
  f.write(f'{int(time.time())}\t{strCommand}\n')
@@ -3867,10 +3912,13 @@ def generate_default_config(args):
3867
3912
  'DEFAULT_INTERVAL': args.interval,
3868
3913
  'DEFAULT_IPMI': args.ipmi,
3869
3914
  'DEFAULT_IPMI_INTERFACE_IP_PREFIX': args.ipmi_interface_ip_prefix,
3870
- 'DEFAULT_INTERFACE_IP_PREFIX': args.interface_ip_prefix,
3871
3915
  'DEFAULT_IPMI_USERNAME': args.ipmi_username,
3872
3916
  'DEFAULT_IPMI_PASSWORD': args.ipmi_password,
3873
3917
  'DEAFULT_IPMI_ARGS': args.ipmi_args,
3918
+ 'DEFAULT_IPMI_METHOD': args.ipmi_method,
3919
+ 'DEFAULT_IPMI_RETRY_LOCKOUT_TIME': args.ipmi_retry_lockout_time,
3920
+ 'DEFAULT_INTERFACE_IP_PREFIX': args.interface_ip_prefix,
3921
+ 'DEFAULT_IPMI_DEFINITIONS': DEFAULT_IPMI_DEFINITIONS,
3874
3922
  'DEFAULT_NO_WATCH': args.no_watch,
3875
3923
  'DEFAULT_WINDOW_WIDTH': args.window_width,
3876
3924
  'DEFAULT_WINDOW_HEIGHT': args.window_height,
@@ -3891,10 +3939,7 @@ def generate_default_config(args):
3891
3939
  'DEFAULT_HOST_FILE': args.host_file,
3892
3940
  'DEFAULT_ENCODING': args.encoding,
3893
3941
  'DEFAULT_DIFF_DISPLAY_THRESHOLD': args.diff_display_threshold,
3894
- 'SSH_STRICT_HOST_KEY_CHECKING': SSH_STRICT_HOST_KEY_CHECKING,
3895
- 'ERROR_MESSAGES_TO_IGNORE': ERROR_MESSAGES_TO_IGNORE,
3896
3942
  'FORCE_TRUECOLOR': args.force_truecolor,
3897
- 'POSSIBLE_SSH_KEY_FILES': POSSIBLE_SSH_KEY_FILES,
3898
3943
  }
3899
3944
 
3900
3945
  def write_default_config(args,CONFIG_FILE = None, force = False):
@@ -3951,11 +3996,11 @@ def get_parser():
3951
3996
  )
3952
3997
  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)
3953
3998
  parser.add_argument('commands', metavar='commands', type=str, nargs='*',default=None,help='the command to run on the hosts / the destination of the files #HOST# or #HOSTNAME# will be replaced with the host name.')
3954
- parser.add_argument('-u','--username', type=str,help=f'The general username to use to connect to the hosts. Will get overwrote by individual username@host if specified. (default: {DEFAULT_USERNAME})',default=DEFAULT_USERNAME)
3955
- parser.add_argument('-p', '--password', type=str,help=f'The password to use to connect to the hosts, (default: {DEFAULT_PASSWORD})',default=DEFAULT_PASSWORD)
3999
+ parser.add_argument('-u','--username', type=str,help=f'The general username to use to connect to the hosts via ssh. Will get overwrote by individual username@host if specified. (default: {DEFAULT_USERNAME})',default=DEFAULT_USERNAME)
4000
+ parser.add_argument('-p', '--password', type=str,help=f'The password to use to connect to the hosts via ssh, (default: {DEFAULT_PASSWORD})',default=DEFAULT_PASSWORD)
3956
4001
  parser.add_argument('-k','--identity_file','--key','--identity',nargs='?', type=str,help=f'The identity file to use to connect to the hosts. Implies --use_key. Specify a folder for program to search for a key. Use option without value to use {DEFAULT_SSH_KEY_SEARCH_PATH} (default: {DEFAULT_IDENTITY_FILE})',const=DEFAULT_SSH_KEY_SEARCH_PATH,default=DEFAULT_IDENTITY_FILE)
3957
4002
  parser.add_argument('-uk','--use_key', action='store_true', help=f'Attempt to use public key file to connect to the hosts. (default: {DEFAULT_USE_KEY})', default=DEFAULT_USE_KEY)
3958
- parser.add_argument('-ea','--extraargs',type=str,help=f'Extra arguments to pass to the ssh / rsync / scp command. Put in one string for multiple arguments.Use "=" ! Ex. -ea="--delete" (default: {DEFAULT_EXTRA_ARGS})',default=DEFAULT_EXTRA_ARGS)
4003
+ parser.add_argument('-ea','--extraargs',type=str,help=f'Extra arguments to pass to the ssh / rsync / scp command. (Use -ia for ipmi arguments) Put in one string for multiple arguments.Use "=" ! Ex. -ea="--delete" (default: {DEFAULT_EXTRA_ARGS})',default=DEFAULT_EXTRA_ARGS)
3959
4004
  parser.add_argument("-11",'--oneonone', action='store_true', help=f"Run one corresponding command on each host. (default: {DEFAULT_ONE_ON_ONE})", default=DEFAULT_ONE_ON_ONE)
3960
4005
  parser.add_argument("-f","--file", action='append', help="The file to be copied to the hosts. Use -f multiple times to copy multiple files")
3961
4006
  parser.add_argument('-s','-fs','--file_sync',nargs='?', action='append', help=f'Operate in file sync mode, sync path in <COMMANDS> from this machine to <HOSTS>. Treat --file <FILE> and <COMMANDS> both as source and source and destination will be the same in this mode. Infer destination from source path. (default: {DEFAULT_FILE_SYNC})',const=True, default=[DEFAULT_FILE_SYNC])
@@ -3971,7 +4016,9 @@ def get_parser():
3971
4016
  parser.add_argument("-pre","--interface_ip_prefix", type=str, help=f"The prefix of the for the interfaces (default: {DEFAULT_INTERFACE_IP_PREFIX})", default=DEFAULT_INTERFACE_IP_PREFIX)
3972
4017
  parser.add_argument('-iu','--ipmi_username', type=str,help=f'The username to use to connect to the hosts via ipmi. (default: {DEFAULT_IPMI_USERNAME})',default=DEFAULT_IPMI_USERNAME)
3973
4018
  parser.add_argument('-ip','--ipmi_password', type=str,help=f'The password to use to connect to the hosts via ipmi. (default: {DEFAULT_IPMI_PASSWORD})',default=DEFAULT_IPMI_PASSWORD)
3974
- parser.add_argument('-ia','--ipmi_args',type=str,help=f'The arguments passed to ipmitool to use to connect to the hosts via ipmi. (default: {DEAFULT_IPMI_ARGS})',default=DEAFULT_IPMI_ARGS)
4019
+ parser.add_argument('-ia','--ipmi_args',type=str,help=f'The arguments passed to ipmitool to use to connect to the hosts via ipmi. (default: {DEFAULT_IPMI_ARGS})',default=DEFAULT_IPMI_ARGS)
4020
+ parser.add_argument('-im','--ipmi_method', type=str,help=f'The method to use to connect to the hosts via ipmi. (default: {DEFAULT_IPMI_METHOD})',default=DEFAULT_IPMI_METHOD)
4021
+ parser.add_argument('-irlt','--ipmi_retry_lockout_time', type=int,help=f'The time in seconds to retry the lockout of the hosts via ipmi. (default: {DEFAULT_IPMI_RETRY_LOCKOUT_TIME})',default=DEFAULT_IPMI_RETRY_LOCKOUT_TIME)
3975
4022
  parser.add_argument('-S',"-q","-nw","--no_watch", action='store_true', help=f"Quiet mode, no curses watch, only print the output. (default: {DEFAULT_NO_WATCH})", default=DEFAULT_NO_WATCH)
3976
4023
  parser.add_argument("-ww",'--window_width', type=int, help=f"The minimum character length of the curses window. (default: {DEFAULT_WINDOW_WIDTH})", default=DEFAULT_WINDOW_WIDTH)
3977
4024
  parser.add_argument("-wh",'--window_height', type=int, help=f"The minimum line height of the curses window. (default: {DEFAULT_WINDOW_HEIGHT})", default=DEFAULT_WINDOW_HEIGHT)
@@ -3991,9 +4038,9 @@ def get_parser():
3991
4038
  su_group.add_argument('-a',"-nsu","--no_skip_unreachable",dest = 'skip_unreachable', action='store_false', help=f"Do not skip unreachable hosts. Note: Timedout Hosts are considered unreachable. Note: multiple command sequence will still auto skip unreachable hosts. (default: {not DEFAULT_SKIP_UNREACHABLE})", default=not DEFAULT_SKIP_UNREACHABLE)
3992
4039
  parser.add_argument('-uhe','--unavailable_host_expiry', type=int, help=f"Time in seconds to expire the unavailable hosts (default: {DEFAULT_UNAVAILABLE_HOST_EXPIRY})", default=DEFAULT_UNAVAILABLE_HOST_EXPIRY)
3993
4040
  parser.add_argument('-X',"-sh","--skip_hosts", type=str, help=f"Skip the hosts in the list. (default: {DEFAULT_SKIP_HOSTS if DEFAULT_SKIP_HOSTS else 'None'})", default=DEFAULT_SKIP_HOSTS)
3994
- parser.add_argument('--generate_config_file', action='store_true', help='Restore / generate using the build-in default configs with default config file chain and command line argument to write to --config_file / stdout')
3995
- parser.add_argument('--config_file', type=str,nargs='?', help='Additional config file to apply, Warning: will over write CLI options. When using with generate_config_file, will store the resulting config file at this location. Use without a path will use multiSSH3.config.json',const='multiSSH3.config.json',default=None)
3996
- parser.add_argument('--store_config_file',type = str,nargs='?',help='Refresh / store the specified config file with new params and command line args into the config file. Defaults to ./multiSSH3.config.json or --config_file if supplied.',const=...)
4041
+ parser.add_argument('--generate_config_file', action='store_true', help='Restore / generate using the build-in default configs with default config file chain and command line argument to write to --store_config_file / stdout')
4042
+ parser.add_argument('--config_file', type=str,nargs='?', help='Load additional config file to apply, Warning: will over write CLI options. Use without a path will use multiSSH3.config.json',const='multiSSH3.config.json',default=None)
4043
+ parser.add_argument('--store_config_file',type = str,nargs='?',help=f'Refresh / store the specified config file with new params and command line args into the config file. Defaults to the first config file found in search path: {__working_config_file}',const=...)
3997
4044
  parser.add_argument('--debug', action='store_true', help='Print debug information')
3998
4045
  parser.add_argument('-ci','--copy_id', action='store_true', help='Copy the ssh id to the hosts')
3999
4046
  parser.add_argument('-I','-nh','--no_history', action='store_true', help=f'Do not record the command to history. Default: {DEFAULT_NO_HISTORY}', default=DEFAULT_NO_HISTORY)
@@ -4008,15 +4055,12 @@ def get_parser():
4008
4055
  return parser
4009
4056
 
4010
4057
  def process_args(args = None):
4011
- global DEFAULT_IPMI_USERNAME
4012
- global DEFAULT_IPMI_PASSWORD
4013
4058
  global __configs_from_file
4014
4059
  cfp = parser = argparse.ArgumentParser(add_help=False)
4015
4060
  cfp.add_argument('--config_file', type=str,nargs='?',const='multiSSH3.config.json',default=None)
4016
- cfp.add_argument('--generate_config_file', action='store_true')
4017
4061
  try:
4018
4062
  cfpa, args = cfp.parse_known_args(args)
4019
- if cfpa.config_file and not cfpa.generate_config_file:
4063
+ if cfpa.config_file:
4020
4064
  if os.path.exists(cfpa.config_file):
4021
4065
  configs = load_config_file(os.path.expanduser(cfpa.config_file))
4022
4066
  globals().update(configs)
@@ -4042,7 +4086,6 @@ def process_args(args = None):
4042
4086
  args.commands += unknown
4043
4087
  try:
4044
4088
  args.config_file = cfpa.config_file
4045
- args.generate_config_file = cfpa.generate_config_file
4046
4089
  except Exception:
4047
4090
  pass
4048
4091
  if args.script:
@@ -4067,24 +4110,45 @@ def process_args(args = None):
4067
4110
  if args.unavailable_host_expiry <= 0:
4068
4111
  eprint(f"Warning: The unavailable host expiry time {args.unavailable_host_expiry} is less than 0, setting it to 10 seconds.")
4069
4112
  args.unavailable_host_expiry = 10
4070
-
4113
+ default_ipmi_definition = get_default_ipmi_definition()
4114
+ while DEFAULT_IPMI_DEFINITIONS:
4115
+ last_definition = DEFAULT_IPMI_DEFINITIONS.pop()
4116
+ if last_definition != default_ipmi_definition:
4117
+ DEFAULT_IPMI_DEFINITIONS.append(last_definition)
4118
+ break
4119
+ fields = [
4120
+ ('interface_ip_prefix', args.ipmi_interface_ip_prefix),
4121
+ ('username', args.ipmi_username),
4122
+ ('password', args.ipmi_password),
4123
+ ('args', args.ipmi_args),
4124
+ ('ipmi_method', args.ipmi_method),
4125
+ ('retry_lockout_time', args.ipmi_retry_lockout_time),
4126
+ ]
4127
+ modified = any(val != default_ipmi_definition[key] for key, val in fields)
4128
+ for key, val in fields:
4129
+ default_ipmi_definition[key] = val
4130
+ if modified:
4131
+ DEFAULT_IPMI_DEFINITIONS.insert(0, default_ipmi_definition)
4132
+ else:
4133
+ DEFAULT_IPMI_DEFINITIONS.append(default_ipmi_definition)
4071
4134
  return args
4072
4135
 
4073
4136
  def process_config_file(args):
4074
4137
  global __configs_from_file
4138
+ global __working_config_file
4075
4139
  if args.generate_config_file or args.store_config_file:
4076
4140
  if args.store_config_file:
4077
4141
  if args.store_config_file is ...:
4078
- configFileToWriteTo = args.config_file if args.config_file else './multiSSH3.config.json'
4142
+ configFileToWriteTo = __working_config_file
4079
4143
  else:
4080
4144
  configFileToWriteTo = args.store_config_file
4081
4145
  else:
4082
- configFileToWriteTo = args.config_file
4083
- write_default_config(args,configFileToWriteTo,force=args.store_config_file)
4146
+ configFileToWriteTo = None
4147
+ write_default_config(args,configFileToWriteTo,force=args.generate_config_file)
4084
4148
  if not args.commands and not args.file:
4085
4149
  if configFileToWriteTo:
4086
4150
  with open(configFileToWriteTo,'r') as f:
4087
- eprint(f"Config file content: \n{f.read()}")
4151
+ eprint(f"Config file {configFileToWriteTo} content: \n{f.read()}")
4088
4152
  _exit_with_code(0)
4089
4153
  return args
4090
4154
 
@@ -4150,19 +4214,14 @@ def process_control_master_config(args):
4150
4214
 
4151
4215
  def set_global_with_args(args):
4152
4216
  global _emo
4153
- global __ipmiiInterfaceIPPrefix
4154
4217
  global _env_files
4155
4218
  global __DEBUG_MODE
4156
4219
  global _encoding
4157
4220
  global __returnZero
4158
- global DEFAULT_IPMI_USERNAME
4159
- global DEFAULT_IPMI_PASSWORD
4160
4221
  global DEFAULT_DIFF_DISPLAY_THRESHOLD
4161
4222
  global FORCE_TRUECOLOR
4162
4223
  global DEFAULT_HOST_FILE
4163
- global __ipmi_args
4164
4224
  _emo = False
4165
- __ipmiiInterfaceIPPrefix = args.ipmi_interface_ip_prefix
4166
4225
  if args.env_file:
4167
4226
  _env_files = [args.env_file]
4168
4227
  else:
@@ -4171,15 +4230,10 @@ def set_global_with_args(args):
4171
4230
  _encoding = args.encoding
4172
4231
  if args.return_zero:
4173
4232
  __returnZero = True
4174
- if args.ipmi_username:
4175
- DEFAULT_IPMI_USERNAME = args.ipmi_username
4176
- if args.ipmi_password:
4177
- DEFAULT_IPMI_PASSWORD = args.ipmi_password
4178
4233
  DEFAULT_DIFF_DISPLAY_THRESHOLD = args.diff_display_threshold
4179
4234
  FORCE_TRUECOLOR = args.force_truecolor
4180
4235
  if args.host_file:
4181
4236
  DEFAULT_HOST_FILE = args.host_file
4182
- __ipmi_args = args.ipmi_args
4183
4237
 
4184
4238
  #%% ------------ Wrapper Block ----------------
4185
4239
  def main(args = None):
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes