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.
- {multissh3-6.18 → multissh3-6.21}/PKG-INFO +1 -1
- {multissh3-6.18 → multissh3-6.21}/multiSSH3.egg-info/PKG-INFO +1 -1
- {multissh3-6.18 → multissh3-6.21}/multiSSH3.py +228 -174
- {multissh3-6.18 → multissh3-6.21}/README.md +0 -0
- {multissh3-6.18 → multissh3-6.21}/multiSSH3.egg-info/SOURCES.txt +0 -0
- {multissh3-6.18 → multissh3-6.21}/multiSSH3.egg-info/dependency_links.txt +0 -0
- {multissh3-6.18 → multissh3-6.21}/multiSSH3.egg-info/entry_points.txt +0 -0
- {multissh3-6.18 → multissh3-6.21}/multiSSH3.egg-info/requires.txt +0 -0
- {multissh3-6.18 → multissh3-6.21}/multiSSH3.egg-info/top_level.txt +0 -0
- {multissh3-6.18 → multissh3-6.21}/setup.cfg +0 -0
- {multissh3-6.18 → multissh3-6.21}/setup.py +0 -0
- {multissh3-6.18 → multissh3-6.21}/test/test.py +0 -0
- {multissh3-6.18 → multissh3-6.21}/test/testCurses.py +0 -0
- {multissh3-6.18 → multissh3-6.21}/test/testCursesOld.py +0 -0
- {multissh3-6.18 → multissh3-6.21}/test/testPerfCompact.py +0 -0
- {multissh3-6.18 → multissh3-6.21}/test/testPerfExpand.py +0 -0
|
@@ -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.
|
|
38
|
+
version = '6.21'
|
|
39
39
|
VERSION = version
|
|
40
40
|
__version__ = version
|
|
41
|
-
COMMIT_DATE = '2026-04-
|
|
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,
|
|
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
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
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
|
-
|
|
1836
|
+
sshLocalExtraArgs = ['-o StrictHostKeyChecking=no','-o UserKnownHostsFile=/dev/null']
|
|
1830
1837
|
if host.identity_file:
|
|
1831
|
-
|
|
1832
|
-
rsyncLocalExtraArgs = ['--rsh','ssh ' + ' '.join(
|
|
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
|
-
|
|
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
|
|
1839
|
-
host.command = replace_magic_strings(host.command,['#USER#','#USERNAME#'],
|
|
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
|
|
1856
|
-
|
|
1857
|
-
if
|
|
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 =
|
|
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.
|
|
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.
|
|
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
|
-
|
|
1871
|
-
|
|
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 {
|
|
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 {
|
|
1897
|
-
elif '
|
|
1898
|
-
host.
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
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 = '
|
|
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
|
-
|
|
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('
|
|
1911
|
-
host.stderr.append('
|
|
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,
|
|
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'] +
|
|
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']] +
|
|
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,
|
|
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
|
|
2092
|
-
if host.ipmi and host.returncode != 0
|
|
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.
|
|
2095
|
-
if
|
|
2096
|
-
|
|
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.
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
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,
|
|
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(
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
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,
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
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
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
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
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
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: {
|
|
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 --
|
|
3995
|
-
parser.add_argument('--config_file', type=str,nargs='?', help='
|
|
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
|
|
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
|
|
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 =
|
|
4142
|
+
configFileToWriteTo = __working_config_file
|
|
4079
4143
|
else:
|
|
4080
4144
|
configFileToWriteTo = args.store_config_file
|
|
4081
4145
|
else:
|
|
4082
|
-
configFileToWriteTo =
|
|
4083
|
-
write_default_config(args,configFileToWriteTo,force=args.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|