multiSSH3 6.17__tar.gz → 6.20__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.17 → multissh3-6.20}/PKG-INFO +1 -1
- {multissh3-6.17 → multissh3-6.20}/multiSSH3.egg-info/PKG-INFO +1 -1
- {multissh3-6.17 → multissh3-6.20}/multiSSH3.py +429 -370
- {multissh3-6.17 → multissh3-6.20}/README.md +0 -0
- {multissh3-6.17 → multissh3-6.20}/multiSSH3.egg-info/SOURCES.txt +0 -0
- {multissh3-6.17 → multissh3-6.20}/multiSSH3.egg-info/dependency_links.txt +0 -0
- {multissh3-6.17 → multissh3-6.20}/multiSSH3.egg-info/entry_points.txt +0 -0
- {multissh3-6.17 → multissh3-6.20}/multiSSH3.egg-info/requires.txt +0 -0
- {multissh3-6.17 → multissh3-6.20}/multiSSH3.egg-info/top_level.txt +0 -0
- {multissh3-6.17 → multissh3-6.20}/setup.cfg +0 -0
- {multissh3-6.17 → multissh3-6.20}/setup.py +0 -0
- {multissh3-6.17 → multissh3-6.20}/test/test.py +0 -0
- {multissh3-6.17 → multissh3-6.20}/test/testCurses.py +0 -0
- {multissh3-6.17 → multissh3-6.20}/test/testCursesOld.py +0 -0
- {multissh3-6.17 → multissh3-6.20}/test/testPerfCompact.py +0 -0
- {multissh3-6.17 → multissh3-6.20}/test/testPerfExpand.py +0 -0
|
@@ -30,24 +30,130 @@ 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
|
-
__curses_available = False
|
|
38
|
-
__resource_lib_available = False
|
|
39
|
-
try:
|
|
40
|
-
import curses
|
|
41
|
-
import curses.panel
|
|
42
|
-
__curses_available = True
|
|
43
|
-
except ImportError:
|
|
44
|
-
pass
|
|
45
|
-
try:
|
|
46
|
-
import resource
|
|
47
|
-
__resource_lib_available = True
|
|
48
|
-
except ImportError:
|
|
49
|
-
pass
|
|
50
37
|
|
|
38
|
+
version = '6.20'
|
|
39
|
+
VERSION = version
|
|
40
|
+
__version__ = version
|
|
41
|
+
COMMIT_DATE = '2026-04-16'
|
|
42
|
+
# TODO: Add terminal TUI
|
|
43
|
+
|
|
44
|
+
CONFIG_FILE_CHAIN = ['./multiSSH3.config.json',
|
|
45
|
+
'~/multiSSH3.config.json',
|
|
46
|
+
'~/.multiSSH3.config.json',
|
|
47
|
+
'~/.config/multiSSH3/multiSSH3.config.json',
|
|
48
|
+
'/etc/multiSSH3.d/multiSSH3.config.json',
|
|
49
|
+
'/etc/multiSSH3.config.json'] # The first one has the highest priority
|
|
50
|
+
|
|
51
|
+
ERRORS = []
|
|
52
|
+
|
|
53
|
+
#%% ------------ User Defined Global Variables ----------------
|
|
54
|
+
AUTHOR = 'Yufei Pan'
|
|
55
|
+
AUTHOR_EMAIL = 'pan@zopyr.us'
|
|
56
|
+
DEFAULT_HOSTS = 'all'
|
|
57
|
+
DEFAULT_USERNAME = None
|
|
58
|
+
DEFAULT_PASSWORD = ''
|
|
59
|
+
DEFAULT_IDENTITY_FILE = None
|
|
60
|
+
DEFAULT_SSH_KEY_SEARCH_PATH = '~/.ssh/'
|
|
61
|
+
DEFAULT_USE_KEY = False
|
|
62
|
+
DEFAULT_EXTRA_ARGS = None
|
|
63
|
+
DEFAULT_ONE_ON_ONE = False
|
|
64
|
+
DEFAULT_SCP = False
|
|
65
|
+
DEFAULT_FILE_SYNC = False
|
|
66
|
+
DEFAULT_TIMEOUT = 50
|
|
67
|
+
DEFAULT_CLI_TIMEOUT = 0
|
|
68
|
+
DEFAULT_UNAVAILABLE_HOST_EXPIRY = 600
|
|
69
|
+
DEFAULT_REPEAT = 1
|
|
70
|
+
DEFAULT_INTERVAL = 0
|
|
71
|
+
DEFAULT_IPMI = False
|
|
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()]
|
|
88
|
+
DEFAULT_INTERFACE_IP_PREFIX = None
|
|
89
|
+
DEFAULT_NO_WATCH = False
|
|
90
|
+
DEFAULT_WINDOW_WIDTH = 40
|
|
91
|
+
DEFAULT_WINDOW_HEIGHT = 1
|
|
92
|
+
DEFAULT_SINGLE_WINDOW = False
|
|
93
|
+
DEFAULT_ERROR_ONLY = False
|
|
94
|
+
DEFAULT_NO_OUTPUT = False
|
|
95
|
+
DEFAULT_RETURN_ZERO = False
|
|
96
|
+
DEFAULT_NO_ENV = False
|
|
97
|
+
DEFAULT_ENV_FILE = ''
|
|
98
|
+
DEFAULT_ENV_FILES = ['/etc/profile.d/hosts.sh',
|
|
99
|
+
'~/.bashrc',
|
|
100
|
+
'~/.zshrc',
|
|
101
|
+
'~/host.env',
|
|
102
|
+
'~/hosts.env',
|
|
103
|
+
'.env',
|
|
104
|
+
'host.env',
|
|
105
|
+
'hosts.env',
|
|
106
|
+
]
|
|
107
|
+
DEFAULT_NO_HISTORY = False
|
|
108
|
+
DEFAULT_HISTORY_FILE = '~/.mssh_history'
|
|
109
|
+
DEFAULT_MAX_CONNECTIONS = 4 * os.cpu_count()
|
|
110
|
+
DEFAULT_JSON_MODE = False
|
|
111
|
+
DEFAULT_PRINT_SUCCESS_HOSTS = False
|
|
112
|
+
DEFAULT_GREPPABLE_MODE = False
|
|
113
|
+
DEFAULT_SKIP_UNREACHABLE = True
|
|
114
|
+
DEFAULT_SKIP_HOSTS = ''
|
|
115
|
+
DEFAULT_HOST_FILE = '/etc/hosts'
|
|
116
|
+
DEFAULT_ENCODING = 'utf-8'
|
|
117
|
+
DEFAULT_DIFF_DISPLAY_THRESHOLD = 0.9
|
|
118
|
+
SSH_STRICT_HOST_KEY_CHECKING = False
|
|
119
|
+
FORCE_TRUECOLOR = False
|
|
120
|
+
ERROR_MESSAGES_TO_IGNORE = [
|
|
121
|
+
'Pseudo-terminal will not be allocated because stdin is not a terminal',
|
|
122
|
+
'Connection to .* closed',
|
|
123
|
+
'Warning: Permanently added',
|
|
124
|
+
'mux_client_request_session',
|
|
125
|
+
'disabling multiplexing',
|
|
126
|
+
'Killed by signal',
|
|
127
|
+
'Connection reset by peer',
|
|
128
|
+
]
|
|
129
|
+
POSSIBLE_SSH_KEY_FILES = ['id_ed25519','id_ed25519_sk','id_ecdsa','id_ecdsa_sk','id_rsa','id_dsa']
|
|
130
|
+
__DEFAULT_COLOR_PALETTE = {
|
|
131
|
+
'cyan': (86, 173, 188),
|
|
132
|
+
'green': (114, 180, 43),
|
|
133
|
+
'magenta': (140, 107, 200),
|
|
134
|
+
'red': (196, 38, 94),
|
|
135
|
+
'white': (227, 227, 221),
|
|
136
|
+
'yellow': (179, 180, 43),
|
|
137
|
+
'blue': (106, 126, 200),
|
|
138
|
+
'bright_black': (102, 102, 102),
|
|
139
|
+
'bright_blue': (129, 154, 255),
|
|
140
|
+
'bright_cyan': (102, 217, 239),
|
|
141
|
+
'bright_green': (126, 226, 46),
|
|
142
|
+
'bright_magenta': (174, 129, 255),
|
|
143
|
+
'bright_red': (249, 38, 114),
|
|
144
|
+
'bright_white': (248, 248, 242),
|
|
145
|
+
'bright_yellow': (226, 226, 46),
|
|
146
|
+
}
|
|
147
|
+
COLOR_PALETTE = __DEFAULT_COLOR_PALETTE.copy()
|
|
148
|
+
_DEFAULT_CALLED = True
|
|
149
|
+
_DEFAULT_RETURN_UNFINISHED = False
|
|
150
|
+
_DEFAULT_UPDATE_UNREACHABLE_HOSTS = True
|
|
151
|
+
_DEFAULT_NO_START = False
|
|
152
|
+
_etc_hosts = {}
|
|
153
|
+
__ERROR_MESSAGES_TO_IGNORE_REGEX =None
|
|
154
|
+
__DEBUG_MODE = False
|
|
155
|
+
|
|
156
|
+
#%% ------------ Pre Helper Functions ----------------
|
|
51
157
|
try:
|
|
52
158
|
# Check if functiools.cache is available
|
|
53
159
|
# cache_decorator = functools.cache
|
|
@@ -85,23 +191,7 @@ except Exception:
|
|
|
85
191
|
print('Warning: functools.lru_cache is not available, multiSSH3 will run slower without cache.',file=sys.stderr)
|
|
86
192
|
def cache_decorator(func):
|
|
87
193
|
return func
|
|
88
|
-
version = '6.17'
|
|
89
|
-
VERSION = version
|
|
90
|
-
__version__ = version
|
|
91
|
-
COMMIT_DATE = '2026-03-27'
|
|
92
|
-
|
|
93
|
-
CONFIG_FILE_CHAIN = ['./multiSSH3.config.json',
|
|
94
|
-
'~/multiSSH3.config.json',
|
|
95
|
-
'~/.multiSSH3.config.json',
|
|
96
|
-
'~/.config/multiSSH3/multiSSH3.config.json',
|
|
97
|
-
'/etc/multiSSH3.d/multiSSH3.config.json',
|
|
98
|
-
'/etc/multiSSH3.config.json'] # The first one has the highest priority
|
|
99
|
-
|
|
100
|
-
ERRORS = []
|
|
101
|
-
|
|
102
|
-
# TODO: Add terminal TUI
|
|
103
194
|
|
|
104
|
-
#%% ------------ Pre Helper Functions ----------------
|
|
105
195
|
def eprint(*args, **kwargs):
|
|
106
196
|
global ERRORS
|
|
107
197
|
try:
|
|
@@ -245,76 +335,6 @@ def _get_i():
|
|
|
245
335
|
'''
|
|
246
336
|
return next(_i_counter)
|
|
247
337
|
|
|
248
|
-
#%% ------------ Host Object ----------------
|
|
249
|
-
class Host:
|
|
250
|
-
def __init__(self, name, command, files = None,ipmi = False,interface_ip_prefix = None,scp=False,extraargs=None,gatherMode=False,identity_file=None,shell=False,i = -1,uuid=uuid.uuid4(),ip = None):
|
|
251
|
-
self.name = name # the name of the host (hostname or IP address)
|
|
252
|
-
self.command = command # the command to run on the host
|
|
253
|
-
self.returncode = None # the return code of the command
|
|
254
|
-
self.output = [] # the output of the command for curses
|
|
255
|
-
self.stdout = [] # the stdout of the command
|
|
256
|
-
self.stderr = [] # the stderr of the command
|
|
257
|
-
self.lineNumToPrintSet = set() # line numbers to reprint
|
|
258
|
-
self.lastUpdateTime = time.monotonic() # the last time the output was updated
|
|
259
|
-
self.lastPrintedUpdateTime = 0 # the last time the output was printed
|
|
260
|
-
self.files = files # the files to be copied to the host
|
|
261
|
-
self.ipmi = ipmi # whether to use ipmi to connect to the host
|
|
262
|
-
self.shell = shell # whether to use shell to run the command
|
|
263
|
-
self.interface_ip_prefix = interface_ip_prefix # the prefix of the ip address of the interface to be used to connect to the host
|
|
264
|
-
self.scp = scp # whether to use scp to copy files to the host
|
|
265
|
-
self.gatherMode = gatherMode # whether the host is in gather mode
|
|
266
|
-
self.extraargs = extraargs # extra arguments to be passed to ssh
|
|
267
|
-
self.resolvedName = None # the resolved IP address of the host
|
|
268
|
-
# also store a globally unique integer i from 0
|
|
269
|
-
self.i = i if i != -1 else _get_i()
|
|
270
|
-
self.uuid = uuid
|
|
271
|
-
self.identity_file = identity_file
|
|
272
|
-
self.ip = ip if ip else getIP(name)
|
|
273
|
-
self.current_color_pair = [-1, -1, 1]
|
|
274
|
-
self.output_buffer = io.BytesIO()
|
|
275
|
-
self.stdout_buffer = io.BytesIO()
|
|
276
|
-
self.stderr_buffer = io.BytesIO()
|
|
277
|
-
self.thread = None
|
|
278
|
-
|
|
279
|
-
def __iter__(self):
|
|
280
|
-
return zip(['name', 'command', 'returncode', 'stdout', 'stderr'], [self.name, self.command, self.returncode, self.stdout, self.stderr])
|
|
281
|
-
def __repr__(self):
|
|
282
|
-
return f"Host(\n{pformat(self.__dict__)}\n)"
|
|
283
|
-
def __str__(self):
|
|
284
|
-
return f"Host(\n{pformat(dict(self))}\n)"
|
|
285
|
-
def get_output_hash(self):
|
|
286
|
-
return hash((
|
|
287
|
-
self.command,
|
|
288
|
-
tuple(self.stdout),
|
|
289
|
-
tuple(self.stderr),
|
|
290
|
-
self.returncode
|
|
291
|
-
))
|
|
292
|
-
def copy(self):
|
|
293
|
-
newHost = Host(
|
|
294
|
-
name=self.name,
|
|
295
|
-
command=self.command,
|
|
296
|
-
files=self.files,
|
|
297
|
-
ipmi=self.ipmi,
|
|
298
|
-
interface_ip_prefix=self.interface_ip_prefix,
|
|
299
|
-
scp=self.scp,
|
|
300
|
-
extraargs=self.extraargs,
|
|
301
|
-
gatherMode=self.gatherMode,
|
|
302
|
-
identity_file=self.identity_file,
|
|
303
|
-
shell=self.shell,
|
|
304
|
-
i=self.i,
|
|
305
|
-
uuid=self.uuid,
|
|
306
|
-
ip=self.ip
|
|
307
|
-
)
|
|
308
|
-
newHost.returncode = self.returncode
|
|
309
|
-
newHost.output = self.output.copy()
|
|
310
|
-
newHost.stdout = self.stdout.copy()
|
|
311
|
-
newHost.stderr = self.stderr.copy()
|
|
312
|
-
newHost.lineNumToPrintSet = self.lineNumToPrintSet.copy()
|
|
313
|
-
newHost.lastUpdateTime = self.lastUpdateTime
|
|
314
|
-
newHost.lastPrintedUpdateTime = self.lastPrintedUpdateTime
|
|
315
|
-
newHost.current_color_pair = self.current_color_pair.copy()
|
|
316
|
-
return newHost
|
|
317
|
-
|
|
318
338
|
#%% ------------ Load Defaults ( Config ) File ----------------
|
|
319
339
|
def load_config_file(config_file):
|
|
320
340
|
'''
|
|
@@ -336,96 +356,6 @@ def load_config_file(config_file):
|
|
|
336
356
|
return {}
|
|
337
357
|
return config
|
|
338
358
|
|
|
339
|
-
#%% ------------ Global Variables ----------------
|
|
340
|
-
AUTHOR = 'Yufei Pan'
|
|
341
|
-
AUTHOR_EMAIL = 'pan@zopyr.us'
|
|
342
|
-
DEFAULT_HOSTS = 'all'
|
|
343
|
-
DEFAULT_USERNAME = None
|
|
344
|
-
DEFAULT_PASSWORD = ''
|
|
345
|
-
DEFAULT_IDENTITY_FILE = None
|
|
346
|
-
DEFAULT_SSH_KEY_SEARCH_PATH = '~/.ssh/'
|
|
347
|
-
DEFAULT_USE_KEY = False
|
|
348
|
-
DEFAULT_EXTRA_ARGS = None
|
|
349
|
-
DEFAULT_ONE_ON_ONE = False
|
|
350
|
-
DEFAULT_SCP = False
|
|
351
|
-
DEFAULT_FILE_SYNC = False
|
|
352
|
-
DEFAULT_TIMEOUT = 50
|
|
353
|
-
DEFAULT_CLI_TIMEOUT = 0
|
|
354
|
-
DEFAULT_UNAVAILABLE_HOST_EXPIRY = 600
|
|
355
|
-
DEFAULT_REPEAT = 1
|
|
356
|
-
DEFAULT_INTERVAL = 0
|
|
357
|
-
DEFAULT_IPMI = False
|
|
358
|
-
DEFAULT_IPMI_INTERFACE_IP_PREFIX = ''
|
|
359
|
-
DEFAULT_INTERFACE_IP_PREFIX = None
|
|
360
|
-
DEFAULT_IPMI_USERNAME = 'ADMIN'
|
|
361
|
-
DEFAULT_IPMI_PASSWORD = ''
|
|
362
|
-
DEAFULT_IPMI_ARGS=''
|
|
363
|
-
DEFAULT_NO_WATCH = False
|
|
364
|
-
DEFAULT_WINDOW_WIDTH = 40
|
|
365
|
-
DEFAULT_WINDOW_HEIGHT = 1
|
|
366
|
-
DEFAULT_SINGLE_WINDOW = False
|
|
367
|
-
DEFAULT_ERROR_ONLY = False
|
|
368
|
-
DEFAULT_NO_OUTPUT = False
|
|
369
|
-
DEFAULT_RETURN_ZERO = False
|
|
370
|
-
DEFAULT_NO_ENV = False
|
|
371
|
-
DEFAULT_ENV_FILE = ''
|
|
372
|
-
DEFAULT_ENV_FILES = ['/etc/profile.d/hosts.sh',
|
|
373
|
-
'~/.bashrc',
|
|
374
|
-
'~/.zshrc',
|
|
375
|
-
'~/host.env',
|
|
376
|
-
'~/hosts.env',
|
|
377
|
-
'.env',
|
|
378
|
-
'host.env',
|
|
379
|
-
'hosts.env',
|
|
380
|
-
]
|
|
381
|
-
DEFAULT_NO_HISTORY = False
|
|
382
|
-
DEFAULT_HISTORY_FILE = '~/.mssh_history'
|
|
383
|
-
DEFAULT_MAX_CONNECTIONS = 4 * os.cpu_count()
|
|
384
|
-
DEFAULT_JSON_MODE = False
|
|
385
|
-
DEFAULT_PRINT_SUCCESS_HOSTS = False
|
|
386
|
-
DEFAULT_GREPPABLE_MODE = False
|
|
387
|
-
DEFAULT_SKIP_UNREACHABLE = True
|
|
388
|
-
DEFAULT_SKIP_HOSTS = ''
|
|
389
|
-
DEFAULT_HOST_FILE = '/etc/hosts'
|
|
390
|
-
DEFAULT_ENCODING = 'utf-8'
|
|
391
|
-
DEFAULT_DIFF_DISPLAY_THRESHOLD = 0.9
|
|
392
|
-
SSH_STRICT_HOST_KEY_CHECKING = False
|
|
393
|
-
FORCE_TRUECOLOR = False
|
|
394
|
-
ERROR_MESSAGES_TO_IGNORE = [
|
|
395
|
-
'Pseudo-terminal will not be allocated because stdin is not a terminal',
|
|
396
|
-
'Connection to .* closed',
|
|
397
|
-
'Warning: Permanently added',
|
|
398
|
-
'mux_client_request_session',
|
|
399
|
-
'disabling multiplexing',
|
|
400
|
-
'Killed by signal',
|
|
401
|
-
'Connection reset by peer',
|
|
402
|
-
]
|
|
403
|
-
__DEFAULT_COLOR_PALETTE = {
|
|
404
|
-
'cyan': (86, 173, 188),
|
|
405
|
-
'green': (114, 180, 43),
|
|
406
|
-
'magenta': (140, 107, 200),
|
|
407
|
-
'red': (196, 38, 94),
|
|
408
|
-
'white': (227, 227, 221),
|
|
409
|
-
'yellow': (179, 180, 43),
|
|
410
|
-
'blue': (106, 126, 200),
|
|
411
|
-
'bright_black': (102, 102, 102),
|
|
412
|
-
'bright_blue': (129, 154, 255),
|
|
413
|
-
'bright_cyan': (102, 217, 239),
|
|
414
|
-
'bright_green': (126, 226, 46),
|
|
415
|
-
'bright_magenta': (174, 129, 255),
|
|
416
|
-
'bright_red': (249, 38, 114),
|
|
417
|
-
'bright_white': (248, 248, 242),
|
|
418
|
-
'bright_yellow': (226, 226, 46),
|
|
419
|
-
}
|
|
420
|
-
COLOR_PALETTE = __DEFAULT_COLOR_PALETTE.copy()
|
|
421
|
-
_DEFAULT_CALLED = True
|
|
422
|
-
_DEFAULT_RETURN_UNFINISHED = False
|
|
423
|
-
_DEFAULT_UPDATE_UNREACHABLE_HOSTS = True
|
|
424
|
-
_DEFAULT_NO_START = False
|
|
425
|
-
_etc_hosts = {}
|
|
426
|
-
__ERROR_MESSAGES_TO_IGNORE_REGEX =None
|
|
427
|
-
__DEBUG_MODE = False
|
|
428
|
-
|
|
429
359
|
#%% Load Config Based Default Global variables
|
|
430
360
|
__configs_from_file = {}
|
|
431
361
|
for config_file in reversed(CONFIG_FILE_CHAIN.copy()):
|
|
@@ -449,8 +379,6 @@ __wildCharacters = ['*','?','x']
|
|
|
449
379
|
_no_env = DEFAULT_NO_ENV
|
|
450
380
|
_env_files = DEFAULT_ENV_FILES
|
|
451
381
|
__globalUnavailableHosts = dict()
|
|
452
|
-
__ipmiiInterfaceIPPrefix = DEFAULT_IPMI_INTERFACE_IP_PREFIX
|
|
453
|
-
__ipmi_args = DEAFULT_IPMI_ARGS
|
|
454
382
|
__keyPressesIn = [[]]
|
|
455
383
|
_emo = False
|
|
456
384
|
__curses_global_color_pairs = {(-1,-1):1}
|
|
@@ -467,12 +395,42 @@ __control_master_string = '''Host *
|
|
|
467
395
|
ControlPath /run/user/%i/ssh_sockets_%C
|
|
468
396
|
ControlPersist 3600
|
|
469
397
|
'''
|
|
398
|
+
__curses_available = False
|
|
399
|
+
__resource_lib_available = False
|
|
400
|
+
try:
|
|
401
|
+
import curses
|
|
402
|
+
import curses.panel
|
|
403
|
+
__curses_available = True
|
|
404
|
+
except ImportError:
|
|
405
|
+
pass
|
|
406
|
+
try:
|
|
407
|
+
import resource
|
|
408
|
+
__resource_lib_available = True
|
|
409
|
+
except ImportError:
|
|
410
|
+
pass
|
|
470
411
|
if __resource_lib_available:
|
|
471
412
|
# Get the current limits
|
|
472
413
|
_, __system_nofile_limit = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
473
414
|
# Set the soft limit to the hard limit
|
|
474
415
|
resource.setrlimit(resource.RLIMIT_NOFILE, (__system_nofile_limit, __system_nofile_limit))
|
|
475
416
|
__max_connections_nofile_limit_supported = int((__system_nofile_limit - 10) / 3)
|
|
417
|
+
_binPaths = {}
|
|
418
|
+
_binCalled = set(['sshpass', 'ssh', 'scp', 'ipmitool','rsync','sh','ssh-copy-id','redfishtool'])
|
|
419
|
+
def check_path(program_name):
|
|
420
|
+
global __configs_from_file
|
|
421
|
+
global _binPaths
|
|
422
|
+
config_key = f'_{program_name}Path'
|
|
423
|
+
program_path = (
|
|
424
|
+
__configs_from_file.get(config_key) or
|
|
425
|
+
globals().get(config_key) or
|
|
426
|
+
shutil.which(program_name)
|
|
427
|
+
)
|
|
428
|
+
if program_path:
|
|
429
|
+
_binPaths[program_name] = program_path
|
|
430
|
+
return True
|
|
431
|
+
return False
|
|
432
|
+
|
|
433
|
+
[check_path(program) for program in _binCalled]
|
|
476
434
|
|
|
477
435
|
#%% Mapping of ANSI 4-bit colors to curses colors
|
|
478
436
|
if __curses_available:
|
|
@@ -494,26 +452,78 @@ if __curses_available:
|
|
|
494
452
|
96: curses.COLOR_CYAN, # Bright Cyan
|
|
495
453
|
97: curses.COLOR_WHITE # Bright White
|
|
496
454
|
}
|
|
497
|
-
#%% ------------ Exportable Help Functions ----------------
|
|
498
|
-
# check if command sshpass is available
|
|
499
|
-
_binPaths = {}
|
|
500
|
-
_binCalled = set(['sshpass', 'ssh', 'scp', 'ipmitool','rsync','sh','ssh-copy-id'])
|
|
501
|
-
def check_path(program_name):
|
|
502
|
-
global __configs_from_file
|
|
503
|
-
global _binPaths
|
|
504
|
-
config_key = f'_{program_name}Path'
|
|
505
|
-
program_path = (
|
|
506
|
-
__configs_from_file.get(config_key) or
|
|
507
|
-
globals().get(config_key) or
|
|
508
|
-
shutil.which(program_name)
|
|
509
|
-
)
|
|
510
|
-
if program_path:
|
|
511
|
-
_binPaths[program_name] = program_path
|
|
512
|
-
return True
|
|
513
|
-
return False
|
|
514
455
|
|
|
515
|
-
|
|
456
|
+
#%% ------------ Host Object ----------------
|
|
457
|
+
class Host:
|
|
458
|
+
def __init__(self, name, command, files = None,ipmi = False,interface_ip_prefix = None,scp=False,extraargs=None,gatherMode=False,identity_file=None,shell=False,i = -1,uuid=uuid.uuid4(),ip = None):
|
|
459
|
+
self.name = name # the name of the host (hostname or IP address)
|
|
460
|
+
self.command = command # the command to run on the host
|
|
461
|
+
self.returncode = None # the return code of the command
|
|
462
|
+
self.output = [] # the output of the command for curses
|
|
463
|
+
self.stdout = [] # the stdout of the command
|
|
464
|
+
self.stderr = [] # the stderr of the command
|
|
465
|
+
self.lineNumToPrintSet = set() # line numbers to reprint
|
|
466
|
+
self.lastUpdateTime = time.monotonic() # the last time the output was updated
|
|
467
|
+
self.lastPrintedUpdateTime = 0 # the last time the output was printed
|
|
468
|
+
self.files = files # the files to be copied to the host
|
|
469
|
+
self.ipmi = ipmi # whether to use ipmi to connect to the host
|
|
470
|
+
self.shell = shell # whether to use shell to run the command
|
|
471
|
+
self.interface_ip_prefix = interface_ip_prefix # the prefix of the ip address of the interface to be used to connect to the host
|
|
472
|
+
self.scp = scp # whether to use scp to copy files to the host
|
|
473
|
+
self.gatherMode = gatherMode # whether the host is in gather mode
|
|
474
|
+
self.extraargs = extraargs # extra arguments to be passed to ssh
|
|
475
|
+
self.resolvedName = None # the resolved IP address of the host
|
|
476
|
+
# also store a globally unique integer i from 0
|
|
477
|
+
self.i = i if i != -1 else _get_i()
|
|
478
|
+
self.uuid = uuid
|
|
479
|
+
self.identity_file = identity_file
|
|
480
|
+
self.ip = ip if ip else getIP(name)
|
|
481
|
+
self.current_color_pair = [-1, -1, 1]
|
|
482
|
+
self.output_buffer = io.BytesIO()
|
|
483
|
+
self.stdout_buffer = io.BytesIO()
|
|
484
|
+
self.stderr_buffer = io.BytesIO()
|
|
485
|
+
self.thread = None
|
|
486
|
+
|
|
487
|
+
def __iter__(self):
|
|
488
|
+
return zip(['name', 'command', 'returncode', 'stdout', 'stderr'], [self.name, self.command, self.returncode, self.stdout, self.stderr])
|
|
489
|
+
def __repr__(self):
|
|
490
|
+
return f"Host(\n{pformat(self.__dict__)}\n)"
|
|
491
|
+
def __str__(self):
|
|
492
|
+
return f"Host(\n{pformat(dict(self))}\n)"
|
|
493
|
+
def get_output_hash(self):
|
|
494
|
+
return hash((
|
|
495
|
+
self.command,
|
|
496
|
+
tuple(self.stdout),
|
|
497
|
+
tuple(self.stderr),
|
|
498
|
+
self.returncode
|
|
499
|
+
))
|
|
500
|
+
def copy(self):
|
|
501
|
+
newHost = Host(
|
|
502
|
+
name=self.name,
|
|
503
|
+
command=self.command,
|
|
504
|
+
files=self.files,
|
|
505
|
+
ipmi=self.ipmi,
|
|
506
|
+
interface_ip_prefix=self.interface_ip_prefix,
|
|
507
|
+
scp=self.scp,
|
|
508
|
+
extraargs=self.extraargs,
|
|
509
|
+
gatherMode=self.gatherMode,
|
|
510
|
+
identity_file=self.identity_file,
|
|
511
|
+
shell=self.shell,
|
|
512
|
+
i=self.i,
|
|
513
|
+
uuid=self.uuid,
|
|
514
|
+
ip=self.ip
|
|
515
|
+
)
|
|
516
|
+
newHost.returncode = self.returncode
|
|
517
|
+
newHost.output = self.output.copy()
|
|
518
|
+
newHost.stdout = self.stdout.copy()
|
|
519
|
+
newHost.stderr = self.stderr.copy()
|
|
520
|
+
newHost.lineNumToPrintSet = self.lineNumToPrintSet.copy()
|
|
521
|
+
newHost.lastUpdateTime = self.lastUpdateTime
|
|
522
|
+
newHost.lastPrintedUpdateTime = self.lastPrintedUpdateTime
|
|
523
|
+
newHost.current_color_pair = self.current_color_pair.copy()
|
|
524
|
+
return newHost
|
|
516
525
|
|
|
526
|
+
#%% ------------ Exportable Help Functions ----------------
|
|
517
527
|
def find_ssh_key_file(searchPath = DEFAULT_SSH_KEY_SEARCH_PATH):
|
|
518
528
|
'''
|
|
519
529
|
Find the ssh public key file
|
|
@@ -528,8 +538,7 @@ def find_ssh_key_file(searchPath = DEFAULT_SSH_KEY_SEARCH_PATH):
|
|
|
528
538
|
sshKeyPath = searchPath
|
|
529
539
|
else:
|
|
530
540
|
sshKeyPath ='~/.ssh'
|
|
531
|
-
|
|
532
|
-
for sshKeyFile in possibleSshKeyFiles:
|
|
541
|
+
for sshKeyFile in POSSIBLE_SSH_KEY_FILES:
|
|
533
542
|
if os.path.exists(os.path.expanduser(os.path.join(sshKeyPath,sshKeyFile))):
|
|
534
543
|
return os.path.join(sshKeyPath,sshKeyFile)
|
|
535
544
|
return None
|
|
@@ -1790,7 +1799,7 @@ def __handle_writing_stream(stream,stop_event,host):
|
|
|
1790
1799
|
# host.stdout.append(' $ ' + ''.join(__keyPressesIn[-1]).encode().decode().replace('\n', '↵'))
|
|
1791
1800
|
return sentInputPos
|
|
1792
1801
|
|
|
1793
|
-
def run_command(host, sem, timeout=60,passwds=None, retry_limit = 7,
|
|
1802
|
+
def run_command(host, sem, timeout=60,passwds=None, retry_limit = 7 + len(DEFAULT_IPMI_DEFINITIONS),ipmi_definitions_list = ...):
|
|
1794
1803
|
'''
|
|
1795
1804
|
Run the command on the host. Will format the commands accordingly. Main execution function.
|
|
1796
1805
|
|
|
@@ -1805,45 +1814,67 @@ def run_command(host, sem, timeout=60,passwds=None, retry_limit = 7,ipmi_args =
|
|
|
1805
1814
|
'''
|
|
1806
1815
|
global _emo
|
|
1807
1816
|
global __ERROR_MESSAGES_TO_IGNORE_REGEX
|
|
1808
|
-
global __ipmiiInterfaceIPPrefix
|
|
1809
1817
|
global _binPaths
|
|
1810
1818
|
global __DEBUG_MODE
|
|
1811
|
-
global DEFAULT_IPMI_USERNAME
|
|
1812
|
-
global DEFAULT_IPMI_PASSWORD
|
|
1813
1819
|
global DEFAULT_USERNAME
|
|
1814
1820
|
global DEFAULT_PASSWORD
|
|
1815
1821
|
global SSH_STRICT_HOST_KEY_CHECKING
|
|
1816
|
-
global __ipmi_args
|
|
1817
|
-
global DEAFULT_IPMI_ARGS
|
|
1818
1822
|
if retry_limit < 0:
|
|
1819
1823
|
host.output.append('Error: Retry limit reached!')
|
|
1820
1824
|
host.stderr.append('Error: Retry limit reached!')
|
|
1821
1825
|
host.returncode = 1
|
|
1822
1826
|
return
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1827
|
+
ipmi_definition = {}
|
|
1828
|
+
username = None
|
|
1829
|
+
sshLocalExtraArgs = []
|
|
1830
|
+
ip_prefix = None
|
|
1826
1831
|
try:
|
|
1827
|
-
localExtraArgs = []
|
|
1828
|
-
|
|
1829
1832
|
if not SSH_STRICT_HOST_KEY_CHECKING:
|
|
1830
|
-
|
|
1833
|
+
sshLocalExtraArgs = ['-o StrictHostKeyChecking=no','-o UserKnownHostsFile=/dev/null']
|
|
1831
1834
|
if host.identity_file:
|
|
1832
|
-
|
|
1833
|
-
rsyncLocalExtraArgs = ['--rsh','ssh ' + ' '.join(
|
|
1834
|
-
host.username = None
|
|
1835
|
+
sshLocalExtraArgs += ['-i',host.identity_file]
|
|
1836
|
+
rsyncLocalExtraArgs = ['--rsh','ssh ' + ' '.join(sshLocalExtraArgs)]
|
|
1835
1837
|
host.address = host.name
|
|
1836
1838
|
if '@' in host.name:
|
|
1837
|
-
|
|
1839
|
+
username, host.address = host.name.rsplit('@',1)
|
|
1840
|
+
if host.ipmi:
|
|
1841
|
+
if ipmi_definitions_list is ...:
|
|
1842
|
+
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]
|
|
1843
|
+
if not ipmi_definitions_list:
|
|
1844
|
+
host.output.append('Error: Exhausted all matching ipmi definitions!')
|
|
1845
|
+
host.stderr.append('Error: Exhausted all matching ipmi definitions!')
|
|
1846
|
+
host.returncode = 1
|
|
1847
|
+
return
|
|
1848
|
+
ipmi_definition = dict(ChainMap(ipmi_definitions_list.pop(0),get_default_ipmi_definition()))
|
|
1849
|
+
if ipmi_definition.get('ipmi_method') == 'ssh_ipmitool':
|
|
1850
|
+
if not host.command:
|
|
1851
|
+
host.command = 'ipmitool power status'
|
|
1852
|
+
else:
|
|
1853
|
+
host.command = 'ipmitool '+host.command if not host.command.startswith('ipmitool ') else host.command
|
|
1854
|
+
if username:
|
|
1855
|
+
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")}')
|
|
1856
|
+
if __DEBUG_MODE:
|
|
1857
|
+
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")}')
|
|
1858
|
+
username = ipmi_definition.get('username',username)
|
|
1859
|
+
if passwds:
|
|
1860
|
+
host.output.append(f'Warning: -p SSH_PASSWORD {passwds} is provided! Using the password from the ipmi definition if provided: {ipmi_definition.get("password")}')
|
|
1861
|
+
if __DEBUG_MODE:
|
|
1862
|
+
host.stderr.append(f'Warning: -p SSH_PASSWORD {passwds} is provided! Using the password from the ipmi definition if provided: {ipmi_definition.get("password")}')
|
|
1863
|
+
passwds = ipmi_definition.get('password',passwds)
|
|
1864
|
+
if ipmi_definition.get('interface_ip_prefix') and ipmi_definition.get('interface_ip_prefix') == DEFAULT_IPMI_INTERFACE_IP_PREFIX:
|
|
1865
|
+
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.')
|
|
1866
|
+
if __DEBUG_MODE:
|
|
1867
|
+
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.')
|
|
1868
|
+
else:
|
|
1869
|
+
ip_prefix = ipmi_definition.get('interface_ip_prefix')
|
|
1838
1870
|
host.command = replace_magic_strings(host.command,['#HOST#','#HOSTNAME#'],host.address,case_sensitive=False)
|
|
1839
|
-
if
|
|
1840
|
-
host.command = replace_magic_strings(host.command,['#USER#','#USERNAME#'],
|
|
1871
|
+
if username:
|
|
1872
|
+
host.command = replace_magic_strings(host.command,['#USER#','#USERNAME#'],username,case_sensitive=False)
|
|
1841
1873
|
else:
|
|
1842
1874
|
current_user = getpass.getuser()
|
|
1843
1875
|
host.command = replace_magic_strings(host.command,['#USER#','#USERNAME#'],current_user,case_sensitive=False)
|
|
1844
1876
|
host.command = replace_magic_strings(host.command,['#ID#'],str(id(host)),case_sensitive=False)
|
|
1845
1877
|
host.command = replace_magic_strings(host.command,['#I#'],str(host.i),case_sensitive=False)
|
|
1846
|
-
host.command = replace_magic_strings(host.command,['#PASSWD#','#PASSWORD#'],passwds,case_sensitive=False)
|
|
1847
1878
|
host.command = replace_magic_strings(host.command,['#UUID#'],str(host.uuid),case_sensitive=False)
|
|
1848
1879
|
host.command = replace_magic_strings(host.command,['#IP#'],str(host.ip),case_sensitive=False)
|
|
1849
1880
|
formatedCMD = []
|
|
@@ -1853,63 +1884,65 @@ def run_command(host, sem, timeout=60,passwds=None, retry_limit = 7,ipmi_args =
|
|
|
1853
1884
|
extraargs = [str(arg) for arg in host.extraargs]
|
|
1854
1885
|
else:
|
|
1855
1886
|
extraargs = []
|
|
1856
|
-
if
|
|
1857
|
-
|
|
1858
|
-
if
|
|
1887
|
+
if not ip_prefix:
|
|
1888
|
+
ip_prefix = host.interface_ip_prefix
|
|
1889
|
+
if ip_prefix:
|
|
1859
1890
|
try:
|
|
1860
1891
|
hostOctets = host.ip.split('.')
|
|
1861
|
-
prefixOctets =
|
|
1892
|
+
prefixOctets = ip_prefix.split('.')
|
|
1862
1893
|
host.address = '.'.join(prefixOctets[:3]+hostOctets[min(3,len(prefixOctets)):])
|
|
1863
|
-
host.resolvedName = host.username + '@' if host.username else ''
|
|
1864
|
-
host.resolvedName += host.address
|
|
1865
1894
|
except Exception:
|
|
1866
|
-
host.
|
|
1895
|
+
host.output.append(f'Warning: Failed to resolve the IP address for the host {host.name}. Unable to resolve ip_prefix {ip_prefix}!')
|
|
1896
|
+
if __DEBUG_MODE:
|
|
1897
|
+
host.stderr.append(f'Warning: Failed to resolve the IP address for the host {host.name}. Unable to resolve ip_prefix {ip_prefix}!')
|
|
1898
|
+
if username:
|
|
1899
|
+
host.resolvedName = username + '@' + host.address
|
|
1867
1900
|
else:
|
|
1868
|
-
host.resolvedName = host.
|
|
1901
|
+
host.resolvedName = host.address
|
|
1869
1902
|
if host.resolvedName:
|
|
1870
1903
|
host.command = replace_magic_strings(host.command,['#RESOLVEDNAME#','#RESOLVED#'],host.resolvedName,case_sensitive=False)
|
|
1871
|
-
|
|
1872
|
-
|
|
1904
|
+
if __DEBUG_MODE:
|
|
1905
|
+
host.stderr.append(f'Resolved name: {host.resolvedName}')
|
|
1906
|
+
if host.ipmi and ipmi_definition.get('ipmi_method') != 'ssh_ipmitool':
|
|
1907
|
+
#{interface_ip_prefix,username,password,args,ipmi_method,hostname_keyword,retry_lockout_time}
|
|
1908
|
+
if ipmi_definition.get('ipmi_method') == 'ipmitool':
|
|
1909
|
+
if 'ipmitool' not in _binPaths:
|
|
1910
|
+
host.output.append('Ipmitool not found on the local machine! Trying ipmitool on the remote machine...')
|
|
1911
|
+
if __DEBUG_MODE:
|
|
1912
|
+
host.stderr.append('Ipmitool not found on the local machine! Trying ipmitool on the remote machine...')
|
|
1913
|
+
if ipmi_definition.get('interface_ip_prefix') == DEFAULT_IPMI_INTERFACE_IP_PREFIX:
|
|
1914
|
+
ipmi_definition['interface_ip_prefix'] = DEFAULT_INTERFACE_IP_PREFIX
|
|
1915
|
+
ipmi_definition['username'] = username
|
|
1916
|
+
ipmi_definition['password'] = passwds
|
|
1917
|
+
ipmi_definition['ipmi_method'] = 'ssh_ipmitool'
|
|
1918
|
+
ipmi_definitions_list.insert(0,ipmi_definition)
|
|
1919
|
+
return run_command(host,sem,timeout,passwds,retry_limit=retry_limit - 1,ipmi_definitions_list=ipmi_definitions_list)
|
|
1873
1920
|
if host.command.startswith('ipmitool '):
|
|
1874
1921
|
host.command = host.command.replace('ipmitool ','')
|
|
1875
1922
|
elif host.command.startswith(_binPaths['ipmitool']):
|
|
1876
1923
|
host.command = host.command.replace(_binPaths['ipmitool'],'')
|
|
1877
|
-
if not host.username or host.username == DEFAULT_USERNAME:
|
|
1878
|
-
if DEFAULT_IPMI_USERNAME:
|
|
1879
|
-
host.username = DEFAULT_IPMI_USERNAME
|
|
1880
|
-
elif DEFAULT_USERNAME:
|
|
1881
|
-
host.username = DEFAULT_USERNAME
|
|
1882
|
-
else:
|
|
1883
|
-
host.username = 'ADMIN'
|
|
1884
|
-
if not passwds or passwds == DEFAULT_PASSWORD:
|
|
1885
|
-
if DEFAULT_IPMI_PASSWORD:
|
|
1886
|
-
passwds = DEFAULT_IPMI_PASSWORD
|
|
1887
|
-
elif DEFAULT_PASSWORD:
|
|
1888
|
-
passwds = DEFAULT_PASSWORD
|
|
1889
|
-
else:
|
|
1890
|
-
host.output.append('Warning: Password not provided for ipmi! Using a default password `admin`.')
|
|
1891
|
-
passwds = 'admin'
|
|
1892
1924
|
if not host.command:
|
|
1893
1925
|
host.command = 'power status'
|
|
1926
|
+
ipmi_args = ipmi_definition.get('args','')
|
|
1894
1927
|
if 'sh' in _binPaths:
|
|
1895
|
-
formatedCMD = [_binPaths['sh'],'-c',f'ipmitool -H {host.address} -U {
|
|
1928
|
+
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}']
|
|
1896
1929
|
else:
|
|
1897
|
-
formatedCMD = [_binPaths['ipmitool'],f'-H {host.address}',f'-U {
|
|
1898
|
-
elif '
|
|
1899
|
-
host.
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
host.interface_ip_prefix = None
|
|
1930
|
+
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]
|
|
1931
|
+
elif ipmi_definition.get('ipmi_method') == 'redfish':
|
|
1932
|
+
if host.command.startswith('redfishtool '):
|
|
1933
|
+
host.command = host.command.replace('redfishtool ','')
|
|
1934
|
+
elif host.command.startswith(_binPaths['redfishtool']):
|
|
1935
|
+
host.command = host.command.replace(_binPaths['redfishtool'],'')
|
|
1904
1936
|
if not host.command:
|
|
1905
|
-
host.command = '
|
|
1937
|
+
host.command = 'Systems'
|
|
1938
|
+
ipmi_args = ipmi_definition.get('args','')
|
|
1939
|
+
if 'sh' in _binPaths:
|
|
1940
|
+
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}']
|
|
1906
1941
|
else:
|
|
1907
|
-
|
|
1908
|
-
run_command(host,sem,timeout,prePasword,retry_limit=retry_limit - 1,ipmi_args=ipmi_args)
|
|
1909
|
-
return
|
|
1942
|
+
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]
|
|
1910
1943
|
else:
|
|
1911
|
-
host.output.append('
|
|
1912
|
-
host.stderr.append('
|
|
1944
|
+
host.output.append(f'IPMI method {ipmi_definition.get("ipmi_method","None")} not supported!')
|
|
1945
|
+
host.stderr.append(f'IPMI method {ipmi_definition.get("ipmi_method","None")} not supported!')
|
|
1913
1946
|
host.returncode = 1
|
|
1914
1947
|
return
|
|
1915
1948
|
elif host.shell:
|
|
@@ -1924,7 +1957,7 @@ def run_command(host, sem, timeout=60,passwds=None, retry_limit = 7,ipmi_args =
|
|
|
1924
1957
|
host.stderr.append('shell not found on the local machine! Using ssh localhost instead...')
|
|
1925
1958
|
host.shell = False
|
|
1926
1959
|
host.name = 'localhost'
|
|
1927
|
-
run_command(host,sem,timeout,
|
|
1960
|
+
run_command(host,sem,timeout,passwds,retry_limit=retry_limit - 1,ipmi_definitions_list=ipmi_definitions_list)
|
|
1928
1961
|
else:
|
|
1929
1962
|
if host.files:
|
|
1930
1963
|
if host.scp:
|
|
@@ -1957,11 +1990,11 @@ def run_command(host, sem, timeout=60,passwds=None, retry_limit = 7,ipmi_args =
|
|
|
1957
1990
|
else:
|
|
1958
1991
|
fileArgs = host.files + [f'{host.resolvedName}:{host.command}']
|
|
1959
1992
|
if useScp:
|
|
1960
|
-
formatedCMD = [_binPaths['scp'],'-rp'] +
|
|
1993
|
+
formatedCMD = [_binPaths['scp'],'-rp'] + sshLocalExtraArgs + extraargs +['--']+fileArgs
|
|
1961
1994
|
else:
|
|
1962
1995
|
formatedCMD = [_binPaths['rsync'],'-ahlX','--partial','--inplace', '--info=name'] + rsyncLocalExtraArgs + extraargs +['--']+fileArgs
|
|
1963
1996
|
else:
|
|
1964
|
-
formatedCMD = [_binPaths['ssh']] +
|
|
1997
|
+
formatedCMD = [_binPaths['ssh']] + sshLocalExtraArgs + extraargs +['--']+ [host.resolvedName, host.command]
|
|
1965
1998
|
if passwds and 'sshpass' in _binPaths:
|
|
1966
1999
|
formatedCMD = [_binPaths['sshpass'], '-p', passwds] + formatedCMD
|
|
1967
2000
|
elif passwds:
|
|
@@ -2078,7 +2111,7 @@ def run_command(host, sem, timeout=60,passwds=None, retry_limit = 7,ipmi_args =
|
|
|
2078
2111
|
host.output.append("Warning: Too many open files. retrying...")
|
|
2079
2112
|
# Handle the error, e.g., clean up, retry logic, or exit
|
|
2080
2113
|
time.sleep(0.1)
|
|
2081
|
-
run_command(host,sem,timeout,
|
|
2114
|
+
run_command(host,sem,timeout,passwds,retry_limit=retry_limit - 1,ipmi_definitions_list=ipmi_definitions_list)
|
|
2082
2115
|
else:
|
|
2083
2116
|
# Re-raise the exception if it's not the specific one
|
|
2084
2117
|
raise
|
|
@@ -2089,39 +2122,47 @@ def run_command(host, sem, timeout=60,passwds=None, retry_limit = 7,ipmi_args =
|
|
|
2089
2122
|
host.stderr.extend(traceback.format_exc().split('\n'))
|
|
2090
2123
|
host.output.extend(traceback.format_exc().split('\n'))
|
|
2091
2124
|
host.returncode = -1
|
|
2092
|
-
# If using ipmi, we will try again using
|
|
2093
|
-
if host.ipmi and host.returncode != 0
|
|
2125
|
+
# If using ipmi, we will try again using next ipmi definition if ipmi connection is not successful
|
|
2126
|
+
if host.ipmi and host.returncode != 0:
|
|
2094
2127
|
host.returncode = None
|
|
2095
|
-
host.
|
|
2096
|
-
if
|
|
2097
|
-
|
|
2098
|
-
host.output.append(f'IPMI connection failed with arg {ipmi_args}! Trying DEFAULT_IPMI_ARGS:{DEAFULT_IPMI_ARGS}')
|
|
2099
|
-
if __DEBUG_MODE:
|
|
2100
|
-
host.stderr.append(f'IPMI connection failed with arg {ipmi_args}! Trying DEFAULT_IPMI_ARGS:{DEAFULT_IPMI_ARGS}')
|
|
2101
|
-
ipmi_args = DEAFULT_IPMI_ARGS
|
|
2102
|
-
else:
|
|
2103
|
-
host.output.append(f'IPMI connection failed with arg {ipmi_args}! Trying without args...')
|
|
2104
|
-
if __DEBUG_MODE:
|
|
2105
|
-
host.stderr.append(f'IPMI connection failed with arg {ipmi_args}! Trying without args...')
|
|
2106
|
-
ipmi_args = ''
|
|
2128
|
+
host.output.append('IPMI connection failed! Trying next ipmi definition...')
|
|
2129
|
+
if __DEBUG_MODE:
|
|
2130
|
+
host.stderr.append('IPMI connection failed! Trying next ipmi definition...')
|
|
2107
2131
|
else:
|
|
2108
|
-
host.
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2132
|
+
host.stdout = []
|
|
2133
|
+
host.stderr = []
|
|
2134
|
+
if ipmi_definition.get('retry_lockout_time'):
|
|
2135
|
+
try:
|
|
2136
|
+
sleep_seconds = int(ipmi_definition.get('retry_lockout_time'))
|
|
2137
|
+
if sleep_seconds >= 0:
|
|
2138
|
+
host.output.append(f'Sleeping for {sleep_seconds} seconds...')
|
|
2139
|
+
if __DEBUG_MODE:
|
|
2140
|
+
host.stderr.append(f'Sleeping for {sleep_seconds} seconds...')
|
|
2141
|
+
time.sleep(sleep_seconds)
|
|
2142
|
+
except Exception as e:
|
|
2143
|
+
host.output.append(f'Error: Failed to sleep for {ipmi_definition.get("retry_lockout_time")} seconds! {e}')
|
|
2144
|
+
if __DEBUG_MODE:
|
|
2145
|
+
host.stderr.append(f'Error: Failed to sleep for {ipmi_definition.get("retry_lockout_time")} seconds! {e}')
|
|
2146
|
+
time.sleep(DEFAULT_IPMI_RETRY_LOCKOUT_TIME)
|
|
2147
|
+
if ipmi_definition.get('ipmi_method') == 'ipmitool':
|
|
2148
|
+
if ipmi_definition.get('interface_ip_prefix') == DEFAULT_IPMI_INTERFACE_IP_PREFIX:
|
|
2149
|
+
ipmi_definition['interface_ip_prefix'] = DEFAULT_INTERFACE_IP_PREFIX
|
|
2150
|
+
ipmi_definition['username'] = username
|
|
2151
|
+
ipmi_definition['password'] = passwds
|
|
2152
|
+
ipmi_definition['ipmi_method'] = 'ssh_ipmitool'
|
|
2153
|
+
ipmi_definitions_list.append(ipmi_definition)
|
|
2154
|
+
run_command(host,sem,timeout,passwds,retry_limit=retry_limit - 1,ipmi_definitions_list=ipmi_definitions_list)
|
|
2115
2155
|
# If transfering files, we will try again using scp if rsync connection is not successful
|
|
2116
2156
|
elif host.files and not host.scp and not useScp and host.returncode != 0 and host.stderr:
|
|
2117
2157
|
host.returncode = None
|
|
2118
|
-
host.stderr = []
|
|
2119
|
-
host.stdout = []
|
|
2120
2158
|
host.output.append('Rsync connection failed! Trying SCP connection...')
|
|
2121
2159
|
if __DEBUG_MODE:
|
|
2122
2160
|
host.stderr.append('Rsync connection failed! Trying SCP connection...')
|
|
2161
|
+
else:
|
|
2162
|
+
host.stdout = []
|
|
2163
|
+
host.stderr = []
|
|
2123
2164
|
host.scp = True
|
|
2124
|
-
run_command(host,sem,timeout,
|
|
2165
|
+
run_command(host,sem,timeout,passwds,retry_limit=retry_limit - 1,ipmi_definitions_list=ipmi_definitions_list)
|
|
2125
2166
|
|
|
2126
2167
|
#%% ------------ Start Threading Block ----------------
|
|
2127
2168
|
def start_run_on_hosts(hosts, timeout=60,password=None,max_connections=4 * os.cpu_count()):
|
|
@@ -3357,7 +3398,6 @@ def processRunOnHosts(timeout, password, max_connections, hosts, return_unfinish
|
|
|
3357
3398
|
|
|
3358
3399
|
|
|
3359
3400
|
#%% ------------ Stringfy Block ----------------
|
|
3360
|
-
|
|
3361
3401
|
def formHostStr(host) -> str:
|
|
3362
3402
|
"""
|
|
3363
3403
|
Forms a comma-separated string of hosts.
|
|
@@ -3380,16 +3420,35 @@ def formHostStr(host) -> str:
|
|
|
3380
3420
|
return ','.join(compact_hostnames(host))
|
|
3381
3421
|
|
|
3382
3422
|
@cache_decorator
|
|
3383
|
-
def __formCommandArgStr(
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3423
|
+
def __formCommandArgStr(shortend = False, **kwargs) -> str:
|
|
3424
|
+
oneonone = kwargs.get('oneonone', DEFAULT_ONE_ON_ONE)
|
|
3425
|
+
timeout = kwargs.get('timeout', DEFAULT_TIMEOUT)
|
|
3426
|
+
password = kwargs.get('password', DEFAULT_PASSWORD)
|
|
3427
|
+
no_watch = kwargs.get('no_watch', DEFAULT_NO_WATCH)
|
|
3428
|
+
json = kwargs.get('json', DEFAULT_JSON_MODE)
|
|
3429
|
+
max_connections = kwargs.get('max_connections', DEFAULT_MAX_CONNECTIONS)
|
|
3430
|
+
file = kwargs.get('file', None)
|
|
3431
|
+
ipmi = kwargs.get('ipmi', DEFAULT_IPMI)
|
|
3432
|
+
interface_ip_prefix = kwargs.get('interface_ip_prefix', DEFAULT_INTERFACE_IP_PREFIX)
|
|
3433
|
+
scp = kwargs.get('scp', DEFAULT_SCP)
|
|
3434
|
+
gather_mode = kwargs.get('gather_mode', False)
|
|
3435
|
+
username = kwargs.get('username', DEFAULT_USERNAME)
|
|
3436
|
+
extraargs = kwargs.get('extraargs', DEFAULT_EXTRA_ARGS)
|
|
3437
|
+
skip_unreachable = kwargs.get('skip_unreachable', DEFAULT_SKIP_UNREACHABLE)
|
|
3438
|
+
no_env = kwargs.get('no_env', DEFAULT_NO_ENV)
|
|
3439
|
+
greppable = kwargs.get('greppable', DEFAULT_GREPPABLE_MODE)
|
|
3440
|
+
skip_hosts = kwargs.get('skip_hosts', DEFAULT_SKIP_HOSTS)
|
|
3441
|
+
file_sync = kwargs.get('file_sync', False)
|
|
3442
|
+
error_only = kwargs.get('error_only', DEFAULT_ERROR_ONLY)
|
|
3443
|
+
identity_file = kwargs.get('identity_file', DEFAULT_IDENTITY_FILE)
|
|
3444
|
+
copy_id = kwargs.get('copy_id', False)
|
|
3445
|
+
unavailable_host_expiry = kwargs.get('unavailable_host_expiry', DEFAULT_UNAVAILABLE_HOST_EXPIRY)
|
|
3446
|
+
no_history = kwargs.get('no_history', DEFAULT_NO_HISTORY)
|
|
3447
|
+
history_file = kwargs.get('history_file', DEFAULT_HISTORY_FILE)
|
|
3448
|
+
env_file = kwargs.get('env_file', '')
|
|
3449
|
+
env_files = kwargs.get('env_files', DEFAULT_ENV_FILES)
|
|
3450
|
+
repeat = kwargs.get('repeat', DEFAULT_REPEAT)
|
|
3451
|
+
interval = kwargs.get('interval', DEFAULT_INTERVAL)
|
|
3393
3452
|
argsList = []
|
|
3394
3453
|
if oneonone:
|
|
3395
3454
|
argsList.append('--oneonone' if not shortend else '-11')
|
|
@@ -3451,37 +3510,22 @@ def __formCommandArgStr(oneonone = DEFAULT_ONE_ON_ONE, timeout = DEFAULT_TIMEOUT
|
|
|
3451
3510
|
argsList.append('--file_sync' if not shortend else '-fs')
|
|
3452
3511
|
return ' '.join(argsList)
|
|
3453
3512
|
|
|
3454
|
-
def getStrCommand(hosts = DEFAULT_HOSTS,commands = None,
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
single_window = DEFAULT_SINGLE_WINDOW,file_sync = False,error_only = DEFAULT_ERROR_ONLY, identity_file = DEFAULT_IDENTITY_FILE,
|
|
3461
|
-
copy_id = False, unavailable_host_expiry = DEFAULT_UNAVAILABLE_HOST_EXPIRY,no_history = DEFAULT_NO_HISTORY,
|
|
3462
|
-
history_file = DEFAULT_HISTORY_FILE, env_file = '', env_files = [],
|
|
3463
|
-
repeat = DEFAULT_REPEAT,interval = DEFAULT_INTERVAL,
|
|
3464
|
-
shortend = False,tabSeperated = False,**kwargs) -> str:
|
|
3465
|
-
_ = called
|
|
3466
|
-
_ = return_unfinished
|
|
3467
|
-
_ = will_update_unreachable_hosts
|
|
3468
|
-
_ = no_start
|
|
3469
|
-
_ = window_width
|
|
3470
|
-
_ = window_height
|
|
3471
|
-
_ = single_window
|
|
3513
|
+
def getStrCommand(hosts = DEFAULT_HOSTS,commands = None,shortend = False,tabSeperated = False,**kwargs) -> str:
|
|
3514
|
+
# Allow explicit kwargs to override base parameters when forwarding dicts.
|
|
3515
|
+
hosts = kwargs.get('hosts', hosts)
|
|
3516
|
+
commands = kwargs.get('commands', commands)
|
|
3517
|
+
shortend = kwargs.get('shortend', shortend)
|
|
3518
|
+
tabSeperated = kwargs.get('tabSeperated', tabSeperated)
|
|
3472
3519
|
hosts = hosts if isinstance(hosts,str) else frozenset(hosts)
|
|
3473
3520
|
hostStr = formHostStr(hosts)
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
history_file = history_file, env_file = env_file, env_files = env_files,
|
|
3483
|
-
repeat = repeat,interval = interval,
|
|
3484
|
-
shortend = shortend)
|
|
3521
|
+
commandArgKwargs = dict(kwargs)
|
|
3522
|
+
commandArgKwargs.pop('hosts', None)
|
|
3523
|
+
commandArgKwargs.pop('commands', None)
|
|
3524
|
+
commandArgKwargs.pop('shortend', None)
|
|
3525
|
+
commandArgKwargs.pop('tabSeperated', None)
|
|
3526
|
+
if commandArgKwargs.get('file'):
|
|
3527
|
+
commandArgKwargs['file'] = frozenset(commandArgKwargs['file'])
|
|
3528
|
+
argsStr = __formCommandArgStr(shortend = shortend, **commandArgKwargs)
|
|
3485
3529
|
commands = [command.replace('"', '\\"').replace('\n', '\\n').replace('\t', '\\t') for command in format_commands(commands)]
|
|
3486
3530
|
commandStr = '"' + '" "'.join(commands) + '"' if commands else ''
|
|
3487
3531
|
filePath = os.path.abspath(__file__)
|
|
@@ -3505,15 +3549,11 @@ def record_command_history(kwargs):
|
|
|
3505
3549
|
global __global_suppress_printout
|
|
3506
3550
|
global __DEBUG_MODE
|
|
3507
3551
|
try:
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
for name in sig.parameters
|
|
3514
|
-
if name in kwargs
|
|
3515
|
-
}
|
|
3516
|
-
strCommand = getStrCommand(**wanted,shortend=True,tabSeperated=True)
|
|
3552
|
+
historyKwargs = dict(kwargs)
|
|
3553
|
+
historyKwargs['shortend'] = True
|
|
3554
|
+
historyKwargs['tabSeperated'] = True
|
|
3555
|
+
history_file = os.path.expanduser(historyKwargs.get('history_file', DEFAULT_HISTORY_FILE))
|
|
3556
|
+
strCommand = getStrCommand(**historyKwargs)
|
|
3517
3557
|
with open(history_file, 'a') as f:
|
|
3518
3558
|
# it follows <timestamp>\t<strCommand>\n
|
|
3519
3559
|
f.write(f'{int(time.time())}\t{strCommand}\n')
|
|
@@ -3869,10 +3909,13 @@ def generate_default_config(args):
|
|
|
3869
3909
|
'DEFAULT_INTERVAL': args.interval,
|
|
3870
3910
|
'DEFAULT_IPMI': args.ipmi,
|
|
3871
3911
|
'DEFAULT_IPMI_INTERFACE_IP_PREFIX': args.ipmi_interface_ip_prefix,
|
|
3872
|
-
'DEFAULT_INTERFACE_IP_PREFIX': args.interface_ip_prefix,
|
|
3873
3912
|
'DEFAULT_IPMI_USERNAME': args.ipmi_username,
|
|
3874
3913
|
'DEFAULT_IPMI_PASSWORD': args.ipmi_password,
|
|
3875
3914
|
'DEAFULT_IPMI_ARGS': args.ipmi_args,
|
|
3915
|
+
'DEFAULT_IPMI_METHOD': args.ipmi_method,
|
|
3916
|
+
'DEFAULT_IPMI_RETRY_LOCKOUT_TIME': args.ipmi_retry_lockout_time,
|
|
3917
|
+
'DEFAULT_INTERFACE_IP_PREFIX': args.interface_ip_prefix,
|
|
3918
|
+
'DEFAULT_IPMI_DEFINITIONS': DEFAULT_IPMI_DEFINITIONS,
|
|
3876
3919
|
'DEFAULT_NO_WATCH': args.no_watch,
|
|
3877
3920
|
'DEFAULT_WINDOW_WIDTH': args.window_width,
|
|
3878
3921
|
'DEFAULT_WINDOW_HEIGHT': args.window_height,
|
|
@@ -3893,8 +3936,6 @@ def generate_default_config(args):
|
|
|
3893
3936
|
'DEFAULT_HOST_FILE': args.host_file,
|
|
3894
3937
|
'DEFAULT_ENCODING': args.encoding,
|
|
3895
3938
|
'DEFAULT_DIFF_DISPLAY_THRESHOLD': args.diff_display_threshold,
|
|
3896
|
-
'SSH_STRICT_HOST_KEY_CHECKING': SSH_STRICT_HOST_KEY_CHECKING,
|
|
3897
|
-
'ERROR_MESSAGES_TO_IGNORE': ERROR_MESSAGES_TO_IGNORE,
|
|
3898
3939
|
'FORCE_TRUECOLOR': args.force_truecolor,
|
|
3899
3940
|
}
|
|
3900
3941
|
|
|
@@ -3939,16 +3980,24 @@ def write_default_config(args,CONFIG_FILE = None, force = False):
|
|
|
3939
3980
|
|
|
3940
3981
|
#%% ------------ Argument Processing -----------------
|
|
3941
3982
|
def get_parser():
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3983
|
+
parser = argparse.ArgumentParser(
|
|
3984
|
+
description='Run a command on multiple hosts, Use #HOST# or #HOSTNAME# to replace the host name in the command.',
|
|
3985
|
+
epilog=(
|
|
3986
|
+
f"Terminal color capability: {get_terminal_color_capability()}\n"
|
|
3987
|
+
f"Found bins: \n{pformat(set(_binPaths.values()))}\n"
|
|
3988
|
+
f"Missing bins: \n{pformat(_binCalled - set(_binPaths.keys()))}\n"
|
|
3989
|
+
f"Config file chain: \n{pformat(CONFIG_FILE_CHAIN)}\n"
|
|
3990
|
+
f"Hosts Env files searched:\n{pformat(DEFAULT_ENV_FILES)}"
|
|
3991
|
+
),
|
|
3992
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
3993
|
+
)
|
|
3945
3994
|
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)
|
|
3946
3995
|
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.')
|
|
3947
|
-
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)
|
|
3948
|
-
parser.add_argument('-p', '--password', type=str,help=f'The password to use to connect to the hosts, (default: {DEFAULT_PASSWORD})',default=DEFAULT_PASSWORD)
|
|
3996
|
+
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)
|
|
3997
|
+
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)
|
|
3949
3998
|
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)
|
|
3950
3999
|
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)
|
|
3951
|
-
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)
|
|
4000
|
+
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)
|
|
3952
4001
|
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)
|
|
3953
4002
|
parser.add_argument("-f","--file", action='append', help="The file to be copied to the hosts. Use -f multiple times to copy multiple files")
|
|
3954
4003
|
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])
|
|
@@ -3964,7 +4013,9 @@ def get_parser():
|
|
|
3964
4013
|
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)
|
|
3965
4014
|
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)
|
|
3966
4015
|
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)
|
|
3967
|
-
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: {
|
|
4016
|
+
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)
|
|
4017
|
+
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)
|
|
4018
|
+
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)
|
|
3968
4019
|
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)
|
|
3969
4020
|
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)
|
|
3970
4021
|
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)
|
|
@@ -4001,8 +4052,6 @@ def get_parser():
|
|
|
4001
4052
|
return parser
|
|
4002
4053
|
|
|
4003
4054
|
def process_args(args = None):
|
|
4004
|
-
global DEFAULT_IPMI_USERNAME
|
|
4005
|
-
global DEFAULT_IPMI_PASSWORD
|
|
4006
4055
|
global __configs_from_file
|
|
4007
4056
|
cfp = parser = argparse.ArgumentParser(add_help=False)
|
|
4008
4057
|
cfp.add_argument('--config_file', type=str,nargs='?',const='multiSSH3.config.json',default=None)
|
|
@@ -4060,7 +4109,27 @@ def process_args(args = None):
|
|
|
4060
4109
|
if args.unavailable_host_expiry <= 0:
|
|
4061
4110
|
eprint(f"Warning: The unavailable host expiry time {args.unavailable_host_expiry} is less than 0, setting it to 10 seconds.")
|
|
4062
4111
|
args.unavailable_host_expiry = 10
|
|
4063
|
-
|
|
4112
|
+
default_ipmi_definition = get_default_ipmi_definition()
|
|
4113
|
+
while DEFAULT_IPMI_DEFINITIONS:
|
|
4114
|
+
last_definition = DEFAULT_IPMI_DEFINITIONS.pop()
|
|
4115
|
+
if last_definition != default_ipmi_definition:
|
|
4116
|
+
DEFAULT_IPMI_DEFINITIONS.append(last_definition)
|
|
4117
|
+
break
|
|
4118
|
+
fields = [
|
|
4119
|
+
('interface_ip_prefix', args.ipmi_interface_ip_prefix),
|
|
4120
|
+
('username', args.ipmi_username),
|
|
4121
|
+
('password', args.ipmi_password),
|
|
4122
|
+
('args', args.ipmi_args),
|
|
4123
|
+
('ipmi_method', args.ipmi_method),
|
|
4124
|
+
('retry_lockout_time', args.ipmi_retry_lockout_time),
|
|
4125
|
+
]
|
|
4126
|
+
modified = any(val != default_ipmi_definition[key] for key, val in fields)
|
|
4127
|
+
for key, val in fields:
|
|
4128
|
+
default_ipmi_definition[key] = val
|
|
4129
|
+
if modified:
|
|
4130
|
+
DEFAULT_IPMI_DEFINITIONS.insert(0, default_ipmi_definition)
|
|
4131
|
+
else:
|
|
4132
|
+
DEFAULT_IPMI_DEFINITIONS.append(default_ipmi_definition)
|
|
4064
4133
|
return args
|
|
4065
4134
|
|
|
4066
4135
|
def process_config_file(args):
|
|
@@ -4143,19 +4212,14 @@ def process_control_master_config(args):
|
|
|
4143
4212
|
|
|
4144
4213
|
def set_global_with_args(args):
|
|
4145
4214
|
global _emo
|
|
4146
|
-
global __ipmiiInterfaceIPPrefix
|
|
4147
4215
|
global _env_files
|
|
4148
4216
|
global __DEBUG_MODE
|
|
4149
4217
|
global _encoding
|
|
4150
4218
|
global __returnZero
|
|
4151
|
-
global DEFAULT_IPMI_USERNAME
|
|
4152
|
-
global DEFAULT_IPMI_PASSWORD
|
|
4153
4219
|
global DEFAULT_DIFF_DISPLAY_THRESHOLD
|
|
4154
4220
|
global FORCE_TRUECOLOR
|
|
4155
4221
|
global DEFAULT_HOST_FILE
|
|
4156
|
-
global __ipmi_args
|
|
4157
4222
|
_emo = False
|
|
4158
|
-
__ipmiiInterfaceIPPrefix = args.ipmi_interface_ip_prefix
|
|
4159
4223
|
if args.env_file:
|
|
4160
4224
|
_env_files = [args.env_file]
|
|
4161
4225
|
else:
|
|
@@ -4164,15 +4228,10 @@ def set_global_with_args(args):
|
|
|
4164
4228
|
_encoding = args.encoding
|
|
4165
4229
|
if args.return_zero:
|
|
4166
4230
|
__returnZero = True
|
|
4167
|
-
if args.ipmi_username:
|
|
4168
|
-
DEFAULT_IPMI_USERNAME = args.ipmi_username
|
|
4169
|
-
if args.ipmi_password:
|
|
4170
|
-
DEFAULT_IPMI_PASSWORD = args.ipmi_password
|
|
4171
4231
|
DEFAULT_DIFF_DISPLAY_THRESHOLD = args.diff_display_threshold
|
|
4172
4232
|
FORCE_TRUECOLOR = args.force_truecolor
|
|
4173
4233
|
if args.host_file:
|
|
4174
4234
|
DEFAULT_HOST_FILE = args.host_file
|
|
4175
|
-
__ipmi_args = args.ipmi_args
|
|
4176
4235
|
|
|
4177
4236
|
#%% ------------ Wrapper Block ----------------
|
|
4178
4237
|
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
|