multiSSH3 4.76__py3-none-any.whl → 4.83__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of multiSSH3 might be problematic. Click here for more details.
- {multiSSH3-4.76.dist-info → multiSSH3-4.83.dist-info}/METADATA +72 -58
- multiSSH3-4.83.dist-info/RECORD +7 -0
- {multiSSH3-4.76.dist-info → multiSSH3-4.83.dist-info}/WHEEL +1 -1
- multiSSH3.py +510 -268
- multiSSH3-4.76.dist-info/RECORD +0 -7
- {multiSSH3-4.76.dist-info → multiSSH3-4.83.dist-info}/LICENSE +0 -0
- {multiSSH3-4.76.dist-info → multiSSH3-4.83.dist-info}/entry_points.txt +0 -0
- {multiSSH3-4.76.dist-info → multiSSH3-4.83.dist-info}/top_level.txt +0 -0
multiSSH3.py
CHANGED
|
@@ -15,6 +15,9 @@ import io
|
|
|
15
15
|
import signal
|
|
16
16
|
import functools
|
|
17
17
|
import glob
|
|
18
|
+
import shutil
|
|
19
|
+
import getpass
|
|
20
|
+
|
|
18
21
|
try:
|
|
19
22
|
# Check if functiools.cache is available
|
|
20
23
|
cache_decorator = functools.cache
|
|
@@ -26,49 +29,140 @@ except AttributeError:
|
|
|
26
29
|
# If neither is available, use a dummy decorator
|
|
27
30
|
def cache_decorator(func):
|
|
28
31
|
return func
|
|
29
|
-
|
|
30
|
-
version = '4.76'
|
|
32
|
+
version = '4.83'
|
|
31
33
|
VERSION = version
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
35
|
+
CONFIG_FILE = '/etc/multiSSH3.config.json'
|
|
36
|
+
|
|
37
|
+
def load_config_file(config_file):
|
|
38
|
+
'''
|
|
39
|
+
Load the config file to global variables
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
config_file (str): The config file
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
dict: The config
|
|
46
|
+
'''
|
|
47
|
+
if not os.path.exists(config_file):
|
|
48
|
+
return {}
|
|
49
|
+
with open(config_file,'r') as f:
|
|
50
|
+
config = json.load(f)
|
|
51
|
+
return config
|
|
52
|
+
|
|
53
|
+
__configs_from_file = load_config_file(CONFIG_FILE)
|
|
54
|
+
|
|
55
|
+
__build_in_default_config = {
|
|
56
|
+
'AUTHOR': 'Yufei Pan',
|
|
57
|
+
'AUTHOR_EMAIL': 'pan@zopyr.us',
|
|
58
|
+
'DEFAULT_HOSTS': 'all',
|
|
59
|
+
'DEFAULT_USERNAME': None,
|
|
60
|
+
'DEFAULT_PASSWORD': '',
|
|
61
|
+
'DEFAULT_EXTRA_ARGS': None,
|
|
62
|
+
'DEFAULT_ONE_ON_ONE': False,
|
|
63
|
+
'DEFAULT_SCP': False,
|
|
64
|
+
'DEFAULT_FILE_SYNC': False,
|
|
65
|
+
'DEFAULT_TIMEOUT': 50,
|
|
66
|
+
'DEFAULT_CLI_TIMEOUT': 0,
|
|
67
|
+
'DEFAULT_REPEAT': 1,
|
|
68
|
+
'DEFAULT_INTERVAL': 0,
|
|
69
|
+
'DEFAULT_IPMI': False,
|
|
70
|
+
'DEFAULT_IPMI_INTERFACE_IP_PREFIX': '',
|
|
71
|
+
'DEFAULT_INTERFACE_IP_PREFIX': None,
|
|
72
|
+
'DEFAULT_NO_WATCH': False,
|
|
73
|
+
'DEFAULT_CURSES_MINIMUM_CHAR_LEN': 40,
|
|
74
|
+
'DEFAULT_CURSES_MINIMUM_LINE_LEN': 1,
|
|
75
|
+
'DEFAULT_SINGLE_WINDOW': False,
|
|
76
|
+
'DEFAULT_ERROR_ONLY': False,
|
|
77
|
+
'DEFAULT_NO_OUTPUT': False,
|
|
78
|
+
'DEFAULT_NO_ENV': False,
|
|
79
|
+
'DEFAULT_ENV_FILE': '/etc/profile.d/hosts.sh',
|
|
80
|
+
'DEFAULT_MAX_CONNECTIONS': 4 * os.cpu_count(),
|
|
81
|
+
'DEFAULT_JSON_MODE': False,
|
|
82
|
+
'DEFAULT_PRINT_SUCCESS_HOSTS': False,
|
|
83
|
+
'DEFAULT_GREPPABLE_MODE': False,
|
|
84
|
+
'DEFAULT_SKIP_UNREACHABLE': False,
|
|
85
|
+
'DEFAULT_SKIP_HOSTS': '',
|
|
86
|
+
'ERROR_MESSAGES_TO_IGNORE': [
|
|
87
|
+
'Pseudo-terminal will not be allocated because stdin is not a terminal',
|
|
88
|
+
'Connection to .* closed',
|
|
89
|
+
'Warning: Permanently added',
|
|
90
|
+
'mux_client_request_session',
|
|
91
|
+
'disabling multiplexing',
|
|
92
|
+
'Killed by signal',
|
|
93
|
+
'Connection reset by peer',
|
|
94
|
+
],
|
|
95
|
+
'_DEFAULT_CALLED': True,
|
|
96
|
+
'_DEFAULT_RETURN_UNFINISHED': False,
|
|
97
|
+
'_DEFAULT_UPDATE_UNREACHABLE_HOSTS': True,
|
|
98
|
+
'_DEFAULT_NO_START': False,
|
|
99
|
+
'_etc_hosts': {},
|
|
100
|
+
'_sshpassPath': None,
|
|
101
|
+
'_sshPath': None,
|
|
102
|
+
'_scpPath': None,
|
|
103
|
+
'_ipmitoolPath': None,
|
|
104
|
+
'_rsyncPath': None,
|
|
105
|
+
'_bashPath': None,
|
|
106
|
+
'__ERROR_MESSAGES_TO_IGNORE_REGEX':None,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
AUTHOR = __configs_from_file.get('AUTHOR', __build_in_default_config['AUTHOR'])
|
|
110
|
+
AUTHOR_EMAIL = __configs_from_file.get('AUTHOR_EMAIL', __build_in_default_config['AUTHOR_EMAIL'])
|
|
111
|
+
|
|
112
|
+
DEFAULT_HOSTS = __configs_from_file.get('DEFAULT_HOSTS', __build_in_default_config['DEFAULT_HOSTS'])
|
|
113
|
+
DEFAULT_ENV_FILE = __configs_from_file.get('DEFAULT_ENV_FILE', __build_in_default_config['DEFAULT_ENV_FILE'])
|
|
114
|
+
DEFAULT_USERNAME = __configs_from_file.get('DEFAULT_USERNAME', __build_in_default_config['DEFAULT_USERNAME'])
|
|
115
|
+
DEFAULT_PASSWORD = __configs_from_file.get('DEFAULT_PASSWORD', __build_in_default_config['DEFAULT_PASSWORD'])
|
|
116
|
+
DEFAULT_EXTRA_ARGS = __configs_from_file.get('DEFAULT_EXTRA_ARGS', __build_in_default_config['DEFAULT_EXTRA_ARGS'])
|
|
117
|
+
DEFAULT_ONE_ON_ONE = __configs_from_file.get('DEFAULT_ONE_ON_ONE', __build_in_default_config['DEFAULT_ONE_ON_ONE'])
|
|
118
|
+
DEFAULT_SCP = __configs_from_file.get('DEFAULT_SCP', __build_in_default_config['DEFAULT_SCP'])
|
|
119
|
+
DEFAULT_FILE_SYNC = __configs_from_file.get('DEFAULT_FILE_SYNC', __build_in_default_config['DEFAULT_FILE_SYNC'])
|
|
120
|
+
DEFAULT_TIMEOUT = __configs_from_file.get('DEFAULT_TIMEOUT', __build_in_default_config['DEFAULT_TIMEOUT'])
|
|
121
|
+
DEFAULT_CLI_TIMEOUT = __configs_from_file.get('DEFAULT_CLI_TIMEOUT', __build_in_default_config['DEFAULT_CLI_TIMEOUT'])
|
|
122
|
+
DEFAULT_REPEAT = __configs_from_file.get('DEFAULT_REPEAT', __build_in_default_config['DEFAULT_REPEAT'])
|
|
123
|
+
DEFAULT_INTERVAL = __configs_from_file.get('DEFAULT_INTERVAL', __build_in_default_config['DEFAULT_INTERVAL'])
|
|
124
|
+
DEFAULT_IPMI = __configs_from_file.get('DEFAULT_IPMI', __build_in_default_config['DEFAULT_IPMI'])
|
|
125
|
+
DEFAULT_IPMI_INTERFACE_IP_PREFIX = __configs_from_file.get('DEFAULT_IPMI_INTERFACE_IP_PREFIX', __build_in_default_config['DEFAULT_IPMI_INTERFACE_IP_PREFIX'])
|
|
126
|
+
DEFAULT_INTERFACE_IP_PREFIX = __configs_from_file.get('DEFAULT_INTERFACE_IP_PREFIX', __build_in_default_config['DEFAULT_INTERFACE_IP_PREFIX'])
|
|
127
|
+
DEFAULT_NO_WATCH = __configs_from_file.get('DEFAULT_NO_WATCH', __build_in_default_config['DEFAULT_NO_WATCH'])
|
|
128
|
+
DEFAULT_CURSES_MINIMUM_CHAR_LEN = __configs_from_file.get('DEFAULT_CURSES_MINIMUM_CHAR_LEN', __build_in_default_config['DEFAULT_CURSES_MINIMUM_CHAR_LEN'])
|
|
129
|
+
DEFAULT_CURSES_MINIMUM_LINE_LEN = __configs_from_file.get('DEFAULT_CURSES_MINIMUM_LINE_LEN', __build_in_default_config['DEFAULT_CURSES_MINIMUM_LINE_LEN'])
|
|
130
|
+
DEFAULT_SINGLE_WINDOW = __configs_from_file.get('DEFAULT_SINGLE_WINDOW', __build_in_default_config['DEFAULT_SINGLE_WINDOW'])
|
|
131
|
+
DEFAULT_ERROR_ONLY = __configs_from_file.get('DEFAULT_ERROR_ONLY', __build_in_default_config['DEFAULT_ERROR_ONLY'])
|
|
132
|
+
DEFAULT_NO_OUTPUT = __configs_from_file.get('DEFAULT_NO_OUTPUT', __build_in_default_config['DEFAULT_NO_OUTPUT'])
|
|
133
|
+
DEFAULT_NO_ENV = __configs_from_file.get('DEFAULT_NO_ENV', __build_in_default_config['DEFAULT_NO_ENV'])
|
|
134
|
+
DEFAULT_MAX_CONNECTIONS = __configs_from_file.get('DEFAULT_MAX_CONNECTIONS', __build_in_default_config['DEFAULT_MAX_CONNECTIONS'])
|
|
135
|
+
if not DEFAULT_MAX_CONNECTIONS:
|
|
136
|
+
DEFAULT_MAX_CONNECTIONS = 4 * os.cpu_count()
|
|
137
|
+
DEFAULT_JSON_MODE = __configs_from_file.get('DEFAULT_JSON_MODE', __build_in_default_config['DEFAULT_JSON_MODE'])
|
|
138
|
+
DEFAULT_PRINT_SUCCESS_HOSTS = __configs_from_file.get('DEFAULT_PRINT_SUCCESS_HOSTS', __build_in_default_config['DEFAULT_PRINT_SUCCESS_HOSTS'])
|
|
139
|
+
DEFAULT_GREPPABLE_MODE = __configs_from_file.get('DEFAULT_GREPPABLE_MODE', __build_in_default_config['DEFAULT_GREPPABLE_MODE'])
|
|
140
|
+
DEFAULT_SKIP_UNREACHABLE = __configs_from_file.get('DEFAULT_SKIP_UNREACHABLE', __build_in_default_config['DEFAULT_SKIP_UNREACHABLE'])
|
|
141
|
+
DEFAULT_SKIP_HOSTS = __configs_from_file.get('DEFAULT_SKIP_HOSTS', __build_in_default_config['DEFAULT_SKIP_HOSTS'])
|
|
142
|
+
|
|
143
|
+
ERROR_MESSAGES_TO_IGNORE = __configs_from_file.get('ERROR_MESSAGES_TO_IGNORE', __build_in_default_config['ERROR_MESSAGES_TO_IGNORE'])
|
|
144
|
+
|
|
145
|
+
_DEFAULT_CALLED = __configs_from_file.get('_DEFAULT_CALLED', __build_in_default_config['_DEFAULT_CALLED'])
|
|
146
|
+
_DEFAULT_RETURN_UNFINISHED = __configs_from_file.get('_DEFAULT_RETURN_UNFINISHED', __build_in_default_config['_DEFAULT_RETURN_UNFINISHED'])
|
|
147
|
+
_DEFAULT_UPDATE_UNREACHABLE_HOSTS = __configs_from_file.get('_DEFAULT_UPDATE_UNREACHABLE_HOSTS', __build_in_default_config['_DEFAULT_UPDATE_UNREACHABLE_HOSTS'])
|
|
148
|
+
_DEFAULT_NO_START = __configs_from_file.get('_DEFAULT_NO_START', __build_in_default_config['_DEFAULT_NO_START'])
|
|
149
|
+
|
|
150
|
+
# form the regex from the list
|
|
151
|
+
__ERROR_MESSAGES_TO_IGNORE_REGEX = __configs_from_file.get('__ERROR_MESSAGES_TO_IGNORE_REGEX', __build_in_default_config['__ERROR_MESSAGES_TO_IGNORE_REGEX'])
|
|
152
|
+
if __ERROR_MESSAGES_TO_IGNORE_REGEX:
|
|
153
|
+
print('Using __ERROR_MESSAGES_TO_IGNORE_REGEX from config file, ignoring ERROR_MESSAGES_TO_IGNORE')
|
|
154
|
+
__ERROR_MESSAGES_TO_IGNORE_REGEX = re.compile(__configs_from_file['__ERROR_MESSAGES_TO_IGNORE_REGEX'])
|
|
155
|
+
else:
|
|
156
|
+
__ERROR_MESSAGES_TO_IGNORE_REGEX = re.compile('|'.join(ERROR_MESSAGES_TO_IGNORE))
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
__global_suppress_printout = True
|
|
161
|
+
|
|
162
|
+
__mainReturnCode = 0
|
|
163
|
+
__failedHosts = set()
|
|
70
164
|
class Host:
|
|
71
|
-
def __init__(self, name, command, files = None,ipmi = False,interface_ip_prefix = None,scp=False,extraargs=None):
|
|
165
|
+
def __init__(self, name, command, files = None,ipmi = False,interface_ip_prefix = None,scp=False,extraargs=None,gatherMode=False):
|
|
72
166
|
self.name = name # the name of the host (hostname or IP address)
|
|
73
167
|
self.command = command # the command to run on the host
|
|
74
168
|
self.returncode = None # the return code of the command
|
|
@@ -80,37 +174,50 @@ class Host:
|
|
|
80
174
|
self.ipmi = ipmi # whether to use ipmi to connect to the host
|
|
81
175
|
self.interface_ip_prefix = interface_ip_prefix # the prefix of the ip address of the interface to be used to connect to the host
|
|
82
176
|
self.scp = scp # whether to use scp to copy files to the host
|
|
177
|
+
self.gatherMode = gatherMode # whether the host is in gather mode
|
|
83
178
|
self.extraargs = extraargs # extra arguments to be passed to ssh
|
|
84
179
|
self.resolvedName = None # the resolved IP address of the host
|
|
85
180
|
def __iter__(self):
|
|
86
181
|
return zip(['name', 'command', 'returncode', 'stdout', 'stderr'], [self.name, self.command, self.returncode, self.stdout, self.stderr])
|
|
87
182
|
def __repr__(self):
|
|
88
183
|
# return the complete data structure
|
|
89
|
-
return f"Host(name={self.name}, command={self.command}, returncode={self.returncode}, stdout={self.stdout}, stderr={self.stderr}, output={self.output}, printedLines={self.printedLines}, files={self.files}, ipmi={self.ipmi}, interface_ip_prefix={self.interface_ip_prefix}, scp={self.scp}, extraargs={self.extraargs}, resolvedName={self.resolvedName})"
|
|
184
|
+
return f"Host(name={self.name}, command={self.command}, returncode={self.returncode}, stdout={self.stdout}, stderr={self.stderr}, output={self.output}, printedLines={self.printedLines}, files={self.files}, ipmi={self.ipmi}, interface_ip_prefix={self.interface_ip_prefix}, scp={self.scp}, gatherMode={self.gatherMode}, extraargs={self.extraargs}, resolvedName={self.resolvedName})"
|
|
90
185
|
def __str__(self):
|
|
91
186
|
return f"Host(name={self.name}, command={self.command}, returncode={self.returncode}, stdout={self.stdout}, stderr={self.stderr})"
|
|
92
187
|
|
|
93
|
-
|
|
188
|
+
__wildCharacters = ['*','?','x']
|
|
94
189
|
|
|
95
|
-
|
|
190
|
+
__gloablUnavailableHosts = set()
|
|
96
191
|
|
|
97
|
-
|
|
192
|
+
__ipmiiInterfaceIPPrefix = DEFAULT_IPMI_INTERFACE_IP_PREFIX
|
|
98
193
|
|
|
99
|
-
|
|
194
|
+
__keyPressesIn = [[]]
|
|
100
195
|
|
|
101
|
-
|
|
196
|
+
_emo = False
|
|
102
197
|
|
|
103
|
-
|
|
198
|
+
_etc_hosts = __configs_from_file.get('_etc_hosts', __build_in_default_config['_etc_hosts'])
|
|
104
199
|
|
|
105
|
-
|
|
200
|
+
_env_file = DEFAULT_ENV_FILE
|
|
106
201
|
|
|
107
202
|
# check if command sshpass is available
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
203
|
+
_binPaths = {}
|
|
204
|
+
def check_path(program_name):
|
|
205
|
+
global __configs_from_file
|
|
206
|
+
global __build_in_default_config
|
|
207
|
+
global _binPaths
|
|
208
|
+
config_key = f'_{program_name}Path'
|
|
209
|
+
program_path = (
|
|
210
|
+
__configs_from_file.get(config_key) or
|
|
211
|
+
__build_in_default_config.get(config_key) or
|
|
212
|
+
shutil.which(program_name)
|
|
213
|
+
)
|
|
214
|
+
if program_path:
|
|
215
|
+
_binPaths[program_name] = program_path
|
|
216
|
+
return True
|
|
217
|
+
return False
|
|
218
|
+
|
|
219
|
+
[check_path(program) for program in ['sshpass', 'ssh', 'scp', 'ipmitool','rsync','bash']]
|
|
220
|
+
|
|
114
221
|
|
|
115
222
|
|
|
116
223
|
@cache_decorator
|
|
@@ -135,14 +242,14 @@ def expandIPv4Address(hosts):
|
|
|
135
242
|
# Handle wildcards
|
|
136
243
|
octetRange = octet.split('-')
|
|
137
244
|
for i in range(len(octetRange)):
|
|
138
|
-
if not octetRange[i] or octetRange[i] in
|
|
245
|
+
if not octetRange[i] or octetRange[i] in __wildCharacters:
|
|
139
246
|
if i == 0:
|
|
140
247
|
octetRange[i] = '0'
|
|
141
248
|
elif i == 1:
|
|
142
249
|
octetRange[i] = '255'
|
|
143
250
|
|
|
144
251
|
expandedOctets.append([str(i) for i in range(int(octetRange[0]),int(octetRange[1])+1)])
|
|
145
|
-
elif octet in
|
|
252
|
+
elif octet in __wildCharacters:
|
|
146
253
|
expandedOctets.append([str(i) for i in range(0,256)])
|
|
147
254
|
else:
|
|
148
255
|
expandedOctets.append([octet])
|
|
@@ -171,11 +278,11 @@ def readEnvFromFile(environemnt_file = ''):
|
|
|
171
278
|
return env
|
|
172
279
|
except:
|
|
173
280
|
env = {}
|
|
174
|
-
global
|
|
281
|
+
global _env_file
|
|
175
282
|
if environemnt_file:
|
|
176
283
|
envf = environemnt_file
|
|
177
284
|
else:
|
|
178
|
-
envf =
|
|
285
|
+
envf = _env_file if _env_file else DEFAULT_ENV_FILE
|
|
179
286
|
if os.path.exists(envf):
|
|
180
287
|
with open(envf,'r') as f:
|
|
181
288
|
for line in f:
|
|
@@ -200,7 +307,7 @@ def getIP(hostname,local=False):
|
|
|
200
307
|
Returns:
|
|
201
308
|
str: The IP address of the hostname
|
|
202
309
|
'''
|
|
203
|
-
global
|
|
310
|
+
global _etc_hosts
|
|
204
311
|
# First we check if the hostname is an IP address
|
|
205
312
|
try:
|
|
206
313
|
ipaddress.ip_address(hostname)
|
|
@@ -208,7 +315,7 @@ def getIP(hostname,local=False):
|
|
|
208
315
|
except ValueError:
|
|
209
316
|
pass
|
|
210
317
|
# Then we check /etc/hosts
|
|
211
|
-
if not
|
|
318
|
+
if not _etc_hosts and os.path.exists('/etc/hosts'):
|
|
212
319
|
with open('/etc/hosts','r') as f:
|
|
213
320
|
for line in f:
|
|
214
321
|
if line.startswith('#') or not line.strip():
|
|
@@ -219,9 +326,9 @@ def getIP(hostname,local=False):
|
|
|
219
326
|
continue
|
|
220
327
|
ip = chunks[0]
|
|
221
328
|
for host in chunks[1:]:
|
|
222
|
-
|
|
223
|
-
if hostname in
|
|
224
|
-
return
|
|
329
|
+
_etc_hosts[host] = ip
|
|
330
|
+
if hostname in _etc_hosts:
|
|
331
|
+
return _etc_hosts[hostname]
|
|
225
332
|
if local:
|
|
226
333
|
return None
|
|
227
334
|
# Then we check the DNS
|
|
@@ -375,10 +482,10 @@ def validate_expand_hostname(hostname,no_env=False):
|
|
|
375
482
|
return [hostname]
|
|
376
483
|
else:
|
|
377
484
|
print(f"Error: {hostname} is not a valid hostname or IP address!")
|
|
378
|
-
global
|
|
379
|
-
|
|
380
|
-
global
|
|
381
|
-
|
|
485
|
+
global __mainReturnCode
|
|
486
|
+
__mainReturnCode += 1
|
|
487
|
+
global __failedHosts
|
|
488
|
+
__failedHosts.add(hostname)
|
|
382
489
|
return []
|
|
383
490
|
|
|
384
491
|
def input_with_timeout_and_countdown(timeout, prompt='Please enter your selection'):
|
|
@@ -458,28 +565,28 @@ def handle_writing_stream(stream,stop_event,host):
|
|
|
458
565
|
Returns:
|
|
459
566
|
None
|
|
460
567
|
'''
|
|
461
|
-
global
|
|
462
|
-
#
|
|
568
|
+
global __keyPressesIn
|
|
569
|
+
# __keyPressesIn is a list of lists.
|
|
463
570
|
# Each list is a list of characters to be sent to the stdin of the process at once.
|
|
464
571
|
# We do not send the last line as it may be incomplete.
|
|
465
572
|
sentInput = 0
|
|
466
573
|
while not stop_event.is_set():
|
|
467
|
-
if sentInput < len(
|
|
468
|
-
stream.write(''.join(
|
|
574
|
+
if sentInput < len(__keyPressesIn) - 1 :
|
|
575
|
+
stream.write(''.join(__keyPressesIn[sentInput]).encode())
|
|
469
576
|
stream.flush()
|
|
470
|
-
host.output.append(' $ ' + ''.join(
|
|
471
|
-
host.stdout.append(' $ ' + ''.join(
|
|
577
|
+
host.output.append(' $ ' + ''.join(__keyPressesIn[sentInput]).encode().decode().replace('\n', '↵'))
|
|
578
|
+
host.stdout.append(' $ ' + ''.join(__keyPressesIn[sentInput]).encode().decode().replace('\n', '↵'))
|
|
472
579
|
sentInput += 1
|
|
473
580
|
else:
|
|
474
581
|
time.sleep(0.1)
|
|
475
|
-
if sentInput < len(
|
|
476
|
-
print(f"Warning: {len(
|
|
582
|
+
if sentInput < len(__keyPressesIn) - 1 :
|
|
583
|
+
print(f"Warning: {len(__keyPressesIn)-sentInput} key presses are not sent before the process is terminated!")
|
|
477
584
|
# # send the last line
|
|
478
|
-
# if
|
|
479
|
-
# stream.write(''.join(
|
|
585
|
+
# if __keyPressesIn and __keyPressesIn[-1]:
|
|
586
|
+
# stream.write(''.join(__keyPressesIn[-1]).encode())
|
|
480
587
|
# stream.flush()
|
|
481
|
-
# host.output.append(' $ ' + ''.join(
|
|
482
|
-
# host.stdout.append(' $ ' + ''.join(
|
|
588
|
+
# host.output.append(' $ ' + ''.join(__keyPressesIn[-1]).encode().decode().replace('\n', '↵'))
|
|
589
|
+
# host.stdout.append(' $ ' + ''.join(__keyPressesIn[-1]).encode().decode().replace('\n', '↵'))
|
|
483
590
|
return sentInput
|
|
484
591
|
|
|
485
592
|
def ssh_command(host, sem, timeout=60,passwds=None):
|
|
@@ -495,64 +602,127 @@ def ssh_command(host, sem, timeout=60,passwds=None):
|
|
|
495
602
|
Returns:
|
|
496
603
|
None
|
|
497
604
|
'''
|
|
498
|
-
global
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
formatedCMD = []
|
|
513
|
-
if host.extraargs:
|
|
514
|
-
extraargs = host.extraargs.split()
|
|
515
|
-
else:
|
|
516
|
-
extraargs = []
|
|
517
|
-
if ipmiiInterfaceIPPrefix:
|
|
518
|
-
host.interface_ip_prefix = ipmiiInterfaceIPPrefix if host.ipmi and not host.interface_ip_prefix else host.interface_ip_prefix
|
|
519
|
-
if host.interface_ip_prefix:
|
|
520
|
-
try:
|
|
521
|
-
hostOctets = getIP(host.address,local=False).split('.')
|
|
522
|
-
prefixOctets = host.interface_ip_prefix.split('.')
|
|
523
|
-
host.address = '.'.join(prefixOctets[:3]+hostOctets[min(3,len(prefixOctets)):])
|
|
524
|
-
host.resolvedName = host.username + '@' if host.username else ''
|
|
525
|
-
host.resolvedName += host.address
|
|
526
|
-
except:
|
|
527
|
-
host.resolvedName = host.name
|
|
605
|
+
global _emo
|
|
606
|
+
global __ERROR_MESSAGES_TO_IGNORE_REGEX
|
|
607
|
+
global __ipmiiInterfaceIPPrefix
|
|
608
|
+
global _binPaths
|
|
609
|
+
try:
|
|
610
|
+
host.username = None
|
|
611
|
+
host.address = host.name
|
|
612
|
+
if '@' in host.name:
|
|
613
|
+
host.username, host.address = host.name.rsplit('@',1)
|
|
614
|
+
if "#HOST#" in host.command.upper() or "#HOSTNAME#" in host.command.upper():
|
|
615
|
+
host.command = host.command.replace("#HOST#",host.address).replace("#HOSTNAME#",host.address).replace("#host#",host.address).replace("#hostname#",host.address)
|
|
616
|
+
if "#USER#" in host.command.upper() or "#USERNAME#" in host.command.upper():
|
|
617
|
+
if host.username:
|
|
618
|
+
host.command = host.command.replace("#USER#",host.username).replace("#USERNAME#",host.username).replace("#user#",host.username).replace("#username#",host.username)
|
|
528
619
|
else:
|
|
620
|
+
current_user = getpass.getuser()
|
|
621
|
+
host.command = host.command.replace("#USER#",current_user).replace("#USERNAME#",current_user).replace("#user#",current_user).replace("#username#",current_user)
|
|
622
|
+
formatedCMD = []
|
|
623
|
+
if host.extraargs and type(host.extraargs) == str:
|
|
624
|
+
extraargs = host.extraargs.split()
|
|
625
|
+
elif host.extraargs and type(host.extraargs) == list:
|
|
626
|
+
extraargs = [str(arg) for arg in host.extraargs]
|
|
627
|
+
else:
|
|
628
|
+
extraargs = []
|
|
629
|
+
if __ipmiiInterfaceIPPrefix:
|
|
630
|
+
host.interface_ip_prefix = __ipmiiInterfaceIPPrefix if host.ipmi and not host.interface_ip_prefix else host.interface_ip_prefix
|
|
631
|
+
if host.interface_ip_prefix:
|
|
632
|
+
try:
|
|
633
|
+
hostOctets = getIP(host.address,local=False).split('.')
|
|
634
|
+
prefixOctets = host.interface_ip_prefix.split('.')
|
|
635
|
+
host.address = '.'.join(prefixOctets[:3]+hostOctets[min(3,len(prefixOctets)):])
|
|
636
|
+
host.resolvedName = host.username + '@' if host.username else ''
|
|
637
|
+
host.resolvedName += host.address
|
|
638
|
+
except:
|
|
529
639
|
host.resolvedName = host.name
|
|
530
|
-
|
|
640
|
+
else:
|
|
641
|
+
host.resolvedName = host.name
|
|
642
|
+
if host.ipmi:
|
|
643
|
+
if 'ipmitool' in _binPaths:
|
|
531
644
|
if host.command.startswith('ipmitool '):
|
|
532
645
|
host.command = host.command.replace('ipmitool ','')
|
|
646
|
+
elif host.command.startswith(_binPaths['ipmitool']):
|
|
647
|
+
host.command = host.command.replace(_binPaths['ipmitool'],'')
|
|
533
648
|
if not host.username:
|
|
534
649
|
host.username = 'admin'
|
|
535
|
-
if
|
|
536
|
-
|
|
650
|
+
if 'bash' in _binPaths:
|
|
651
|
+
if passwds:
|
|
652
|
+
formatedCMD = [_binPaths['bash'],'-c',f'ipmitool -H {host.address} -U {host.username} -P {passwds} {" ".join(extraargs)} {host.command}']
|
|
653
|
+
else:
|
|
654
|
+
formatedCMD = [_binPaths['bash'],'-c',f'ipmitool -H {host.address} -U {host.username} {" ".join(extraargs)} {host.command}']
|
|
537
655
|
else:
|
|
538
|
-
|
|
656
|
+
if passwds:
|
|
657
|
+
formatedCMD = [_binPaths['ipmitool'],f'-H {host.address}',f'-U {host.username}',f'-P {passwds}'] + extraargs + [host.command]
|
|
658
|
+
else:
|
|
659
|
+
formatedCMD = [_binPaths['ipmitool'],f'-H {host.address}',f'-U {host.username}'] + extraargs + [host.command]
|
|
660
|
+
elif 'ssh' in _binPaths:
|
|
661
|
+
host.output.append('Ipmitool not found on the local machine! Trying ipmitool on the remote machine...')
|
|
662
|
+
host.ipmi = False
|
|
663
|
+
host.interface_ip_prefix = None
|
|
664
|
+
host.command = 'ipmitool '+host.command if not host.command.startswith('ipmitool ') else host.command
|
|
665
|
+
ssh_command(host,sem,timeout,passwds)
|
|
666
|
+
return
|
|
539
667
|
else:
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
668
|
+
host.output.append('Ipmitool not found on the local machine! Please install ipmitool to use ipmi mode.')
|
|
669
|
+
host.stderr.append('Ipmitool not found on the local machine! Please install ipmitool to use ipmi mode.')
|
|
670
|
+
host.returncode = 1
|
|
671
|
+
return
|
|
672
|
+
else:
|
|
673
|
+
if host.files:
|
|
674
|
+
if host.scp:
|
|
675
|
+
if 'scp' in _binPaths:
|
|
676
|
+
useScp = True
|
|
677
|
+
elif 'rsync' in _binPaths:
|
|
678
|
+
host.output.append('scp not found on the local machine! Trying to use rsync...')
|
|
679
|
+
useScp = False
|
|
680
|
+
else:
|
|
681
|
+
host.output.append('scp not found on the local machine! Please install scp or rsync to use file sync mode.')
|
|
682
|
+
host.stderr.append('scp not found on the local machine! Please install scp or rsync to use file sync mode.')
|
|
683
|
+
host.returncode = 1
|
|
684
|
+
return
|
|
685
|
+
elif 'rsync' in _binPaths:
|
|
686
|
+
useScp = False
|
|
687
|
+
elif 'scp' in _binPaths:
|
|
688
|
+
host.output.append('rsync not found on the local machine! Trying to use scp...')
|
|
689
|
+
useScp = True
|
|
690
|
+
else:
|
|
691
|
+
host.output.append('rsync not found on the local machine! Please install rsync or scp to use file sync mode.')
|
|
692
|
+
host.stderr.append('rsync not found on the local machine! Please install rsync or scp to use file sync mode.')
|
|
693
|
+
host.returncode = 1
|
|
694
|
+
return
|
|
695
|
+
if host.gatherMode:
|
|
696
|
+
fileArgs = [f'{host.resolvedName}:{file}' for file in host.files] + [host.command]
|
|
697
|
+
else:
|
|
698
|
+
fileArgs = host.files + [f'{host.resolvedName}:{host.command}']
|
|
699
|
+
if useScp:
|
|
700
|
+
formatedCMD = [_binPaths['scp'],'-rpB'] + extraargs +['--']+fileArgs
|
|
545
701
|
else:
|
|
546
|
-
formatedCMD = ['
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
702
|
+
formatedCMD = [_binPaths['rsync'],'-ahlX','--partial','--inplace', '--info=name'] + extraargs +['--']+fileArgs
|
|
703
|
+
else:
|
|
704
|
+
formatedCMD = [_binPaths['ssh']] + extraargs +['--']+ [host.resolvedName, host.command]
|
|
705
|
+
if passwds and 'sshpass' in _binPaths:
|
|
706
|
+
formatedCMD = [_binPaths['sshpass'], '-p', passwds] + formatedCMD
|
|
707
|
+
elif passwds:
|
|
708
|
+
host.output.append('Warning: sshpass is not available. Please install sshpass to use password authentication.')
|
|
709
|
+
#host.stderr.append('Warning: sshpass is not available. Please install sshpass to use password authentication.')
|
|
710
|
+
host.output.append('Please provide password via live input or use ssh key authentication.')
|
|
711
|
+
# # try to send the password via __keyPressesIn
|
|
712
|
+
# __keyPressesIn[-1] = list(passwds) + ['\n']
|
|
713
|
+
# __keyPressesIn.append([])
|
|
714
|
+
except Exception as e:
|
|
715
|
+
import traceback
|
|
716
|
+
host.output.append(f'Error occurred while formatting the command : {host.command}!')
|
|
717
|
+
host.stderr.append(f'Error occurred while formatting the command : {host.command}!')
|
|
718
|
+
host.stderr.extend(str(e).split('\n'))
|
|
719
|
+
host.output.extend(str(e).split('\n'))
|
|
720
|
+
host.stderr.extend(traceback.format_exc().split('\n'))
|
|
721
|
+
host.output.extend(traceback.format_exc().split('\n'))
|
|
722
|
+
host.returncode = -1
|
|
723
|
+
return
|
|
724
|
+
with sem:
|
|
725
|
+
try:
|
|
556
726
|
host.output.append('Running command: '+' '.join(formatedCMD))
|
|
557
727
|
#host.stdout = []
|
|
558
728
|
proc = subprocess.Popen(formatedCMD,stdout=subprocess.PIPE,stderr=subprocess.PIPE,stdin=subprocess.PIPE)
|
|
@@ -592,7 +762,7 @@ def ssh_command(host, sem, timeout=60,passwds=None):
|
|
|
592
762
|
host.printedLines -= 1
|
|
593
763
|
host.output.append(timeoutLine)
|
|
594
764
|
outLength = len(host.output)
|
|
595
|
-
if
|
|
765
|
+
if _emo:
|
|
596
766
|
host.stderr.append('Ctrl C detected, Emergency Stop!')
|
|
597
767
|
host.output.append('Ctrl C detected, Emergency Stop!')
|
|
598
768
|
proc.send_signal(signal.SIGINT)
|
|
@@ -607,7 +777,7 @@ def ssh_command(host, sem, timeout=60,passwds=None):
|
|
|
607
777
|
stdin_thread.join(timeout=1)
|
|
608
778
|
# here we handle the rest of the stdout after the subprocess returns
|
|
609
779
|
host.output.append(f'Pipe Closed. Trying to read the rest of the stdout...')
|
|
610
|
-
if not
|
|
780
|
+
if not _emo:
|
|
611
781
|
stdout = None
|
|
612
782
|
stderr = None
|
|
613
783
|
try:
|
|
@@ -627,8 +797,9 @@ def ssh_command(host, sem, timeout=60,passwds=None):
|
|
|
627
797
|
elif host.stderr and host.stderr[-1].strip().startswith('Ctrl C detected, Emergency Stop!'):
|
|
628
798
|
host.returncode = 137
|
|
629
799
|
host.output.append(f'Command finished with return code {host.returncode}')
|
|
630
|
-
if host.stderr
|
|
631
|
-
|
|
800
|
+
if host.stderr:
|
|
801
|
+
# filter out the error messages that we want to ignore
|
|
802
|
+
host.stderr = [line for line in host.stderr if not __ERROR_MESSAGES_TO_IGNORE_REGEX.search(line)]
|
|
632
803
|
except Exception as e:
|
|
633
804
|
import traceback
|
|
634
805
|
host.stderr.extend(str(e).split('\n'))
|
|
@@ -645,7 +816,7 @@ def ssh_command(host, sem, timeout=60,passwds=None):
|
|
|
645
816
|
host.command = 'ipmitool '+host.command if not host.command.startswith('ipmitool ') else host.command
|
|
646
817
|
ssh_command(host,sem,timeout,passwds)
|
|
647
818
|
# If transfering files, we will try again using scp if rsync connection is not successful
|
|
648
|
-
if host.files and not host.scp and host.returncode != 0 and host.stderr:
|
|
819
|
+
if host.files and not host.scp and not useScp and host.returncode != 0 and host.stderr:
|
|
649
820
|
host.stderr = []
|
|
650
821
|
host.stdout = []
|
|
651
822
|
host.output.append('Rsync connection failed! Trying SCP connection...')
|
|
@@ -764,7 +935,7 @@ def generate_display(stdscr, hosts, threads,lineToDisplay = -1,curserPosition =
|
|
|
764
935
|
last_refresh_time = time.perf_counter()
|
|
765
936
|
stdscr.clear()
|
|
766
937
|
#host_window.refresh()
|
|
767
|
-
global
|
|
938
|
+
global __keyPressesIn
|
|
768
939
|
stdscr.nodelay(True)
|
|
769
940
|
# we generate a stats window at the top of the screen
|
|
770
941
|
stat_window = curses.newwin(1, max_x, 0, 0)
|
|
@@ -803,19 +974,19 @@ def generate_display(stdscr, hosts, threads,lineToDisplay = -1,curserPosition =
|
|
|
803
974
|
elif key in [259, 258, 260, 261, 339, 338, 262, 360]:
|
|
804
975
|
# if the key is up arrow, we will move the line to display up
|
|
805
976
|
if key == 259: # 259 is the key code for up arrow
|
|
806
|
-
lineToDisplay = max(lineToDisplay - 1, -len(
|
|
977
|
+
lineToDisplay = max(lineToDisplay - 1, -len(__keyPressesIn))
|
|
807
978
|
# if the key is down arrow, we will move the line to display down
|
|
808
979
|
elif key == 258: # 258 is the key code for down arrow
|
|
809
980
|
lineToDisplay = min(lineToDisplay + 1, -1)
|
|
810
981
|
# if the key is left arrow, we will move the cursor left
|
|
811
982
|
elif key == 260: # 260 is the key code for left arrow
|
|
812
|
-
curserPosition = min(max(curserPosition - 1, 0), len(
|
|
983
|
+
curserPosition = min(max(curserPosition - 1, 0), len(__keyPressesIn[lineToDisplay]) -1)
|
|
813
984
|
# if the key is right arrow, we will move the cursor right
|
|
814
985
|
elif key == 261: # 261 is the key code for right arrow
|
|
815
|
-
curserPosition = max(min(curserPosition + 1, len(
|
|
986
|
+
curserPosition = max(min(curserPosition + 1, len(__keyPressesIn[lineToDisplay])), 0)
|
|
816
987
|
# if the key is page up, we will move the line to display up by 5 lines
|
|
817
988
|
elif key == 339: # 339 is the key code for page up
|
|
818
|
-
lineToDisplay = max(lineToDisplay - 5, -len(
|
|
989
|
+
lineToDisplay = max(lineToDisplay - 5, -len(__keyPressesIn))
|
|
819
990
|
# if the key is page down, we will move the line to display down by 5 lines
|
|
820
991
|
elif key == 338: # 338 is the key code for page down
|
|
821
992
|
lineToDisplay = min(lineToDisplay + 5, -1)
|
|
@@ -824,48 +995,48 @@ def generate_display(stdscr, hosts, threads,lineToDisplay = -1,curserPosition =
|
|
|
824
995
|
curserPosition = 0
|
|
825
996
|
# if the key is end, we will move the cursor to the end of the line
|
|
826
997
|
elif key == 360: # 360 is the key code for end
|
|
827
|
-
curserPosition = len(
|
|
998
|
+
curserPosition = len(__keyPressesIn[lineToDisplay])
|
|
828
999
|
# We are left with these are keys that mofidy the current line.
|
|
829
1000
|
else:
|
|
830
1001
|
# This means the user have done scrolling and is committing to modify the current line.
|
|
831
1002
|
if lineToDisplay < -1:
|
|
832
1003
|
# We overwrite the last line (current working line) with the line to display, removing the newline at the end
|
|
833
|
-
|
|
1004
|
+
__keyPressesIn[-1] = __keyPressesIn[lineToDisplay][:-1]
|
|
834
1005
|
lineToDisplay = -1
|
|
835
|
-
curserPosition = max(0, min(curserPosition, len(
|
|
1006
|
+
curserPosition = max(0, min(curserPosition, len(__keyPressesIn[lineToDisplay])))
|
|
836
1007
|
if key == 10: # 10 is the key code for newline
|
|
837
|
-
|
|
838
|
-
|
|
1008
|
+
__keyPressesIn[-1].append(chr(key))
|
|
1009
|
+
__keyPressesIn.append([])
|
|
839
1010
|
lineToDisplay = -1
|
|
840
1011
|
curserPosition = 0
|
|
841
1012
|
# if the key is backspace, we will remove the last character from the last list
|
|
842
1013
|
elif key in [8,263]: # 8 is the key code for backspace
|
|
843
1014
|
if curserPosition > 0:
|
|
844
|
-
|
|
1015
|
+
__keyPressesIn[lineToDisplay].pop(curserPosition - 1)
|
|
845
1016
|
curserPosition -= 1
|
|
846
1017
|
# if the key is ESC, we will clear the last list
|
|
847
1018
|
elif key == 27: # 27 is the key code for ESC
|
|
848
|
-
|
|
1019
|
+
__keyPressesIn[-1] = []
|
|
849
1020
|
curserPosition = 0
|
|
850
1021
|
# ignore delete key
|
|
851
1022
|
elif key in [127, 330]: # 330 is the key code for delete key
|
|
852
1023
|
# delete the character at the cursor position
|
|
853
|
-
if curserPosition < len(
|
|
854
|
-
|
|
1024
|
+
if curserPosition < len(__keyPressesIn[lineToDisplay]):
|
|
1025
|
+
__keyPressesIn[lineToDisplay].pop(curserPosition)
|
|
855
1026
|
else:
|
|
856
1027
|
# if the key is not a special key, we will add it
|
|
857
|
-
|
|
1028
|
+
__keyPressesIn[lineToDisplay].insert(curserPosition, chr(key))
|
|
858
1029
|
curserPosition += 1
|
|
859
1030
|
# reconfigure when the terminal size changes
|
|
860
1031
|
# raise Exception when max_y or max_x is changed, let parent handle reconfigure
|
|
861
1032
|
if org_dim != stdscr.getmaxyx():
|
|
862
1033
|
raise Exception('Terminal size changed. Please reconfigure window.')
|
|
863
1034
|
# We generate the aggregated stats if user did not input anything
|
|
864
|
-
if not
|
|
1035
|
+
if not __keyPressesIn[lineToDisplay]:
|
|
865
1036
|
stats = '┍'+ f"Total: {len(hosts)} Running: {host_stats['running']} Failed: {host_stats['failed']} Finished: {host_stats['finished']} Waiting: {host_stats['waiting']}"[:max_x - 2].center(max_x - 2, "━")
|
|
866
1037
|
else:
|
|
867
1038
|
# we use the stat bar to display the key presses
|
|
868
|
-
encodedLine = ''.join(
|
|
1039
|
+
encodedLine = ''.join(__keyPressesIn[lineToDisplay]).encode().decode().strip('\n') + ' '
|
|
869
1040
|
# # add the flashing indicator at the curse position
|
|
870
1041
|
# if time.perf_counter() % 1 > 0.5:
|
|
871
1042
|
# encodedLine = encodedLine[:curserPosition] + '█' + encodedLine[curserPosition:]
|
|
@@ -985,8 +1156,8 @@ def print_output(hosts,usejson = False,quiet = False,greppable = False):
|
|
|
985
1156
|
Returns:
|
|
986
1157
|
str: The pretty output generated
|
|
987
1158
|
'''
|
|
988
|
-
global
|
|
989
|
-
global
|
|
1159
|
+
global __keyPressesIn
|
|
1160
|
+
global __global_suppress_printout
|
|
990
1161
|
hosts = [dict(host) for host in hosts]
|
|
991
1162
|
if usejson:
|
|
992
1163
|
# [print(dict(host)) for host in hosts]
|
|
@@ -1009,14 +1180,14 @@ def print_output(hosts,usejson = False,quiet = False,greppable = False):
|
|
|
1009
1180
|
rtnStr = ''
|
|
1010
1181
|
for output, hosts in outputs.items():
|
|
1011
1182
|
rtnStr += f"{','.join(hosts)}{output}\n"
|
|
1012
|
-
if
|
|
1013
|
-
CMDsOut = [''.join(cmd).encode('unicode_escape').decode().replace('\\n', '↵') for cmd in
|
|
1183
|
+
if __keyPressesIn[-1]:
|
|
1184
|
+
CMDsOut = [''.join(cmd).encode('unicode_escape').decode().replace('\\n', '↵') for cmd in __keyPressesIn if cmd]
|
|
1014
1185
|
rtnStr += 'User Inputs: '+ '\nUser Inputs: '.join(CMDsOut)
|
|
1015
1186
|
#rtnStr += '\n'
|
|
1016
1187
|
else:
|
|
1017
1188
|
outputs = {}
|
|
1018
1189
|
for host in hosts:
|
|
1019
|
-
if
|
|
1190
|
+
if __global_suppress_printout:
|
|
1020
1191
|
if host['returncode'] == 0:
|
|
1021
1192
|
continue
|
|
1022
1193
|
hostPrintOut = f" Command:\n {host['command']}\n"
|
|
@@ -1032,24 +1203,24 @@ def print_output(hosts,usejson = False,quiet = False,greppable = False):
|
|
|
1032
1203
|
outputs[hostPrintOut].append(host['name'])
|
|
1033
1204
|
rtnStr = ''
|
|
1034
1205
|
for output, hosts in outputs.items():
|
|
1035
|
-
if
|
|
1206
|
+
if __global_suppress_printout:
|
|
1036
1207
|
rtnStr += f'Error returncode produced by {hosts}:\n'
|
|
1037
1208
|
rtnStr += output+'\n'
|
|
1038
1209
|
else:
|
|
1039
1210
|
rtnStr += '*'*80+'\n'
|
|
1040
1211
|
rtnStr += f"These hosts: {hosts} have a response of:\n"
|
|
1041
1212
|
rtnStr += output+'\n'
|
|
1042
|
-
if not
|
|
1213
|
+
if not __global_suppress_printout or outputs:
|
|
1043
1214
|
rtnStr += '*'*80+'\n'
|
|
1044
|
-
if
|
|
1045
|
-
CMDsOut = [''.join(cmd).encode('unicode_escape').decode().replace('\\n', '↵') for cmd in
|
|
1046
|
-
#rtnStr += f"Key presses: {''.join(
|
|
1047
|
-
#rtnStr += f"Key presses: {
|
|
1215
|
+
if __keyPressesIn[-1]:
|
|
1216
|
+
CMDsOut = [''.join(cmd).encode('unicode_escape').decode().replace('\\n', '↵') for cmd in __keyPressesIn if cmd]
|
|
1217
|
+
#rtnStr += f"Key presses: {''.join(__keyPressesIn).encode('unicode_escape').decode()}\n"
|
|
1218
|
+
#rtnStr += f"Key presses: {__keyPressesIn}\n"
|
|
1048
1219
|
rtnStr += "User Inputs: \n "
|
|
1049
1220
|
rtnStr += '\n '.join(CMDsOut)
|
|
1050
1221
|
rtnStr += '\n'
|
|
1051
|
-
|
|
1052
|
-
if
|
|
1222
|
+
__keyPressesIn = [[]]
|
|
1223
|
+
if __global_suppress_printout and not outputs:
|
|
1053
1224
|
rtnStr += 'Success'
|
|
1054
1225
|
if not quiet:
|
|
1055
1226
|
print(rtnStr)
|
|
@@ -1095,10 +1266,10 @@ def signal_handler(sig, frame):
|
|
|
1095
1266
|
Returns:
|
|
1096
1267
|
None
|
|
1097
1268
|
'''
|
|
1098
|
-
global
|
|
1099
|
-
if not
|
|
1269
|
+
global _emo
|
|
1270
|
+
if not _emo:
|
|
1100
1271
|
print('Ctrl C caught, exiting...')
|
|
1101
|
-
|
|
1272
|
+
_emo = True
|
|
1102
1273
|
else:
|
|
1103
1274
|
print('Ctrl C caught again, exiting immediately!')
|
|
1104
1275
|
# wait for 0.1 seconds to allow the threads to exit
|
|
@@ -1107,10 +1278,10 @@ def signal_handler(sig, frame):
|
|
|
1107
1278
|
sys.exit(0)
|
|
1108
1279
|
|
|
1109
1280
|
|
|
1110
|
-
def processRunOnHosts(timeout, password, max_connections, hosts, returnUnfinished,
|
|
1111
|
-
global
|
|
1281
|
+
def processRunOnHosts(timeout, password, max_connections, hosts, returnUnfinished, nowatch, json, called, greppable,unavailableHosts,willUpdateUnreachableHosts,curses_min_char_len = DEFAULT_CURSES_MINIMUM_CHAR_LEN, curses_min_line_len = DEFAULT_CURSES_MINIMUM_LINE_LEN,single_window = DEFAULT_SINGLE_WINDOW):
|
|
1282
|
+
global __gloablUnavailableHosts
|
|
1112
1283
|
threads = start_run_on_hosts(hosts, timeout=timeout,password=password,max_connections=max_connections)
|
|
1113
|
-
if not
|
|
1284
|
+
if not nowatch and threads and not returnUnfinished and any([thread.is_alive() for thread in threads]) and sys.stdout.isatty() and os.get_terminal_size() and os.get_terminal_size().columns > 10:
|
|
1114
1285
|
curses.wrapper(curses_print, hosts, threads, min_char_len = curses_min_char_len, min_line_len = curses_min_line_len, single_window = single_window)
|
|
1115
1286
|
if not returnUnfinished:
|
|
1116
1287
|
# wait until all hosts have a return code
|
|
@@ -1121,7 +1292,7 @@ def processRunOnHosts(timeout, password, max_connections, hosts, returnUnfinishe
|
|
|
1121
1292
|
# update the unavailable hosts and global unavailable hosts
|
|
1122
1293
|
if willUpdateUnreachableHosts:
|
|
1123
1294
|
unavailableHosts.update([host.name for host in hosts if host.stderr and ('No route to host' in host.stderr[0].strip() or host.stderr[0].strip().startswith('Timeout!'))])
|
|
1124
|
-
|
|
1295
|
+
__gloablUnavailableHosts.update(unavailableHosts)
|
|
1125
1296
|
# print the output, if the output of multiple hosts are the same, we aggragate them
|
|
1126
1297
|
if not called:
|
|
1127
1298
|
print_output(hosts,json,greppable=greppable)
|
|
@@ -1152,9 +1323,9 @@ def formHostStr(host) -> str:
|
|
|
1152
1323
|
|
|
1153
1324
|
@cache_decorator
|
|
1154
1325
|
def __formCommandArgStr(oneonone = DEFAULT_ONE_ON_ONE, timeout = DEFAULT_TIMEOUT,password = DEFAULT_PASSWORD,
|
|
1155
|
-
|
|
1326
|
+
nowatch = DEFAULT_NO_WATCH,json = DEFAULT_JSON_MODE,max_connections=DEFAULT_MAX_CONNECTIONS,
|
|
1156
1327
|
files = None,ipmi = DEFAULT_IPMI,interface_ip_prefix = DEFAULT_INTERFACE_IP_PREFIX,
|
|
1157
|
-
scp=DEFAULT_SCP,username=DEFAULT_USERNAME,extraargs=DEFAULT_EXTRA_ARGS,skipUnreachable=DEFAULT_SKIP_UNREACHABLE,
|
|
1328
|
+
scp=DEFAULT_SCP,gather_mode = False,username=DEFAULT_USERNAME,extraargs=DEFAULT_EXTRA_ARGS,skipUnreachable=DEFAULT_SKIP_UNREACHABLE,
|
|
1158
1329
|
no_env=DEFAULT_NO_ENV,greppable=DEFAULT_GREPPABLE_MODE,skip_hosts = DEFAULT_SKIP_HOSTS,
|
|
1159
1330
|
file_sync = False, error_only = DEFAULT_ERROR_ONLY,
|
|
1160
1331
|
shortend = False) -> str:
|
|
@@ -1162,13 +1333,14 @@ def __formCommandArgStr(oneonone = DEFAULT_ONE_ON_ONE, timeout = DEFAULT_TIMEOUT
|
|
|
1162
1333
|
if oneonone: argsList.append('--oneonone' if not shortend else '-11')
|
|
1163
1334
|
if timeout and timeout != DEFAULT_TIMEOUT: argsList.append(f'--timeout={timeout}' if not shortend else f'-t={timeout}')
|
|
1164
1335
|
if password and password != DEFAULT_PASSWORD: argsList.append(f'--password="{password}"' if not shortend else f'-p="{password}"')
|
|
1165
|
-
if
|
|
1336
|
+
if nowatch: argsList.append('--nowatch' if not shortend else '-q')
|
|
1166
1337
|
if json: argsList.append('--json' if not shortend else '-j')
|
|
1167
|
-
if max_connections and max_connections != DEFAULT_MAX_CONNECTIONS: argsList.append(f'--
|
|
1338
|
+
if max_connections and max_connections != DEFAULT_MAX_CONNECTIONS: argsList.append(f'--max_connections={max_connections}' if not shortend else f'-m={max_connections}')
|
|
1168
1339
|
if files: argsList.extend([f'--file="{file}"' for file in files] if not shortend else [f'-f="{file}"' for file in files])
|
|
1169
1340
|
if ipmi: argsList.append('--ipmi')
|
|
1170
1341
|
if interface_ip_prefix and interface_ip_prefix != DEFAULT_INTERFACE_IP_PREFIX: argsList.append(f'--interface_ip_prefix="{interface_ip_prefix}"' if not shortend else f'-pre="{interface_ip_prefix}"')
|
|
1171
1342
|
if scp: argsList.append('--scp')
|
|
1343
|
+
if gather_mode: argsList.append('--gather_mode' if not shortend else '-gm')
|
|
1172
1344
|
if username and username != DEFAULT_USERNAME: argsList.append(f'--username="{username}"' if not shortend else f'-u="{username}"')
|
|
1173
1345
|
if extraargs and extraargs != DEFAULT_EXTRA_ARGS: argsList.append(f'--extraargs="{extraargs}"' if not shortend else f'-ea="{extraargs}"')
|
|
1174
1346
|
if skipUnreachable: argsList.append('--skipUnreachable' if not shortend else '-su')
|
|
@@ -1179,56 +1351,56 @@ def __formCommandArgStr(oneonone = DEFAULT_ONE_ON_ONE, timeout = DEFAULT_TIMEOUT
|
|
|
1179
1351
|
if file_sync: argsList.append('--file_sync' if not shortend else '-fs')
|
|
1180
1352
|
return ' '.join(argsList)
|
|
1181
1353
|
|
|
1182
|
-
def getStrCommand(hosts,commands,oneonone = DEFAULT_ONE_ON_ONE, timeout = DEFAULT_TIMEOUT,password = DEFAULT_PASSWORD,
|
|
1183
|
-
|
|
1184
|
-
files = None,ipmi = DEFAULT_IPMI,interface_ip_prefix = DEFAULT_INTERFACE_IP_PREFIX,returnUnfinished =
|
|
1185
|
-
scp=DEFAULT_SCP,username=DEFAULT_USERNAME,extraargs=DEFAULT_EXTRA_ARGS,skipUnreachable=DEFAULT_SKIP_UNREACHABLE,
|
|
1186
|
-
no_env=DEFAULT_NO_ENV,greppable=DEFAULT_GREPPABLE_MODE,willUpdateUnreachableHosts=
|
|
1354
|
+
def getStrCommand(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAULT_ONE_ON_ONE, timeout = DEFAULT_TIMEOUT,password = DEFAULT_PASSWORD,
|
|
1355
|
+
nowatch = DEFAULT_NO_WATCH,json = DEFAULT_JSON_MODE,called = _DEFAULT_CALLED,max_connections=DEFAULT_MAX_CONNECTIONS,
|
|
1356
|
+
files = None,ipmi = DEFAULT_IPMI,interface_ip_prefix = DEFAULT_INTERFACE_IP_PREFIX,returnUnfinished = _DEFAULT_RETURN_UNFINISHED,
|
|
1357
|
+
scp=DEFAULT_SCP,gather_mode = False,username=DEFAULT_USERNAME,extraargs=DEFAULT_EXTRA_ARGS,skipUnreachable=DEFAULT_SKIP_UNREACHABLE,
|
|
1358
|
+
no_env=DEFAULT_NO_ENV,greppable=DEFAULT_GREPPABLE_MODE,willUpdateUnreachableHosts=_DEFAULT_UPDATE_UNREACHABLE_HOSTS,no_start=_DEFAULT_NO_START,
|
|
1187
1359
|
skip_hosts = DEFAULT_SKIP_HOSTS, curses_min_char_len = DEFAULT_CURSES_MINIMUM_CHAR_LEN, curses_min_line_len = DEFAULT_CURSES_MINIMUM_LINE_LEN,
|
|
1188
1360
|
single_window = DEFAULT_SINGLE_WINDOW,file_sync = False,error_only = DEFAULT_ERROR_ONLY, shortend = False):
|
|
1189
1361
|
hosts = hosts if type(hosts) == str else frozenset(hosts)
|
|
1190
1362
|
hostStr = formHostStr(hosts)
|
|
1191
1363
|
files = frozenset(files) if files else None
|
|
1192
1364
|
argsStr = __formCommandArgStr(oneonone = oneonone, timeout = timeout,password = password,
|
|
1193
|
-
|
|
1194
|
-
files = files,ipmi = ipmi,interface_ip_prefix = interface_ip_prefix,scp=scp,
|
|
1365
|
+
nowatch = nowatch,json = json,max_connections=max_connections,
|
|
1366
|
+
files = files,ipmi = ipmi,interface_ip_prefix = interface_ip_prefix,scp=scp,gather_mode = gather_mode,
|
|
1195
1367
|
username=username,extraargs=extraargs,skipUnreachable=skipUnreachable,no_env=no_env,
|
|
1196
1368
|
greppable=greppable,skip_hosts = skip_hosts, file_sync = file_sync,error_only = error_only, shortend = shortend)
|
|
1197
1369
|
commandStr = '"' + '" "'.join(commands) + '"' if commands else ''
|
|
1198
1370
|
return f'multissh {argsStr} {hostStr} {commandStr}'
|
|
1199
1371
|
|
|
1200
|
-
def run_command_on_hosts(hosts,commands,oneonone = DEFAULT_ONE_ON_ONE, timeout = DEFAULT_TIMEOUT,password = DEFAULT_PASSWORD,
|
|
1201
|
-
|
|
1202
|
-
files = None,ipmi = DEFAULT_IPMI,interface_ip_prefix = DEFAULT_INTERFACE_IP_PREFIX,returnUnfinished =
|
|
1203
|
-
scp=DEFAULT_SCP,username=DEFAULT_USERNAME,extraargs=DEFAULT_EXTRA_ARGS,skipUnreachable=DEFAULT_SKIP_UNREACHABLE,
|
|
1204
|
-
no_env=DEFAULT_NO_ENV,greppable=DEFAULT_GREPPABLE_MODE,willUpdateUnreachableHosts=
|
|
1372
|
+
def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAULT_ONE_ON_ONE, timeout = DEFAULT_TIMEOUT,password = DEFAULT_PASSWORD,
|
|
1373
|
+
nowatch = DEFAULT_NO_WATCH,json = DEFAULT_JSON_MODE,called = _DEFAULT_CALLED,max_connections=DEFAULT_MAX_CONNECTIONS,
|
|
1374
|
+
files = None,ipmi = DEFAULT_IPMI,interface_ip_prefix = DEFAULT_INTERFACE_IP_PREFIX,returnUnfinished = _DEFAULT_RETURN_UNFINISHED,
|
|
1375
|
+
scp=DEFAULT_SCP,gather_mode = False,username=DEFAULT_USERNAME,extraargs=DEFAULT_EXTRA_ARGS,skipUnreachable=DEFAULT_SKIP_UNREACHABLE,
|
|
1376
|
+
no_env=DEFAULT_NO_ENV,greppable=DEFAULT_GREPPABLE_MODE,willUpdateUnreachableHosts=_DEFAULT_UPDATE_UNREACHABLE_HOSTS,no_start=_DEFAULT_NO_START,
|
|
1205
1377
|
skip_hosts = DEFAULT_SKIP_HOSTS, curses_min_char_len = DEFAULT_CURSES_MINIMUM_CHAR_LEN, curses_min_line_len = DEFAULT_CURSES_MINIMUM_LINE_LEN,
|
|
1206
1378
|
single_window = DEFAULT_SINGLE_WINDOW,file_sync = False,error_only = DEFAULT_ERROR_ONLY):
|
|
1207
1379
|
f'''
|
|
1208
1380
|
Run the command on the hosts, aka multissh. main function
|
|
1209
1381
|
|
|
1210
1382
|
Args:
|
|
1211
|
-
hosts (str/iterable): A string of hosts seperated by space or comma / iterable of hosts.
|
|
1212
|
-
commands (list): A list of commands to run on the hosts. When using files, defines the destination of the files.
|
|
1383
|
+
hosts (str/iterable): A string of hosts seperated by space or comma / iterable of hosts. Default to {DEFAULT_HOSTS}.
|
|
1384
|
+
commands (list): A list of commands to run on the hosts. When using files, defines the destination of the files. Defaults to None.
|
|
1213
1385
|
oneonone (bool, optional): Whether to run the commands one on one. Defaults to {DEFAULT_ONE_ON_ONE}.
|
|
1214
1386
|
timeout (int, optional): The timeout for the command. Defaults to {DEFAULT_TIMEOUT}.
|
|
1215
1387
|
password (str, optional): The password for the hosts. Defaults to {DEFAULT_PASSWORD}.
|
|
1216
|
-
|
|
1388
|
+
nowatch (bool, optional): Whether to print the output. Defaults to {DEFAULT_NO_WATCH}.
|
|
1217
1389
|
json (bool, optional): Whether to print the output in JSON format. Defaults to {DEFAULT_JSON_MODE}.
|
|
1218
|
-
called (bool, optional): Whether the function is called by another function. Defaults to {
|
|
1390
|
+
called (bool, optional): Whether the function is called by another function. Defaults to {_DEFAULT_CALLED}.
|
|
1219
1391
|
max_connections (int, optional): The maximum number of concurrent SSH sessions. Defaults to 4 * os.cpu_count().
|
|
1220
1392
|
files (list, optional): A list of files to be copied to the hosts. Defaults to None.
|
|
1221
1393
|
ipmi (bool, optional): Whether to use IPMI to connect to the hosts. Defaults to {DEFAULT_IPMI}.
|
|
1222
1394
|
interface_ip_prefix (str, optional): The prefix of the IPMI interface. Defaults to {DEFAULT_INTERFACE_IP_PREFIX}.
|
|
1223
|
-
returnUnfinished (bool, optional): Whether to return the unfinished hosts. Defaults to {
|
|
1395
|
+
returnUnfinished (bool, optional): Whether to return the unfinished hosts. Defaults to {_DEFAULT_RETURN_UNFINISHED}.
|
|
1224
1396
|
scp (bool, optional): Whether to use scp instead of rsync. Defaults to {DEFAULT_SCP}.
|
|
1225
1397
|
username (str, optional): The username to use to connect to the hosts. Defaults to {DEFAULT_USERNAME}.
|
|
1226
1398
|
extraargs (str, optional): Extra arguments to pass to the ssh / rsync / scp command. Defaults to {DEFAULT_EXTRA_ARGS}.
|
|
1227
1399
|
skipUnreachable (bool, optional): Whether to skip unreachable hosts. Defaults to {DEFAULT_SKIP_UNREACHABLE}.
|
|
1228
1400
|
no_env (bool, optional): Whether to not read the current sat system environment variables. (Will still read from files) Defaults to {DEFAULT_NO_ENV}.
|
|
1229
1401
|
greppable (bool, optional): Whether to print the output in greppable format. Defaults to {DEFAULT_GREPPABLE_MODE}.
|
|
1230
|
-
willUpdateUnreachableHosts (bool, optional): Whether to update the global unavailable hosts. Defaults to {
|
|
1231
|
-
no_start (bool, optional): Whether to return the hosts without starting the command. Defaults to {
|
|
1402
|
+
willUpdateUnreachableHosts (bool, optional): Whether to update the global unavailable hosts. Defaults to {_DEFAULT_UPDATE_UNREACHABLE_HOSTS}.
|
|
1403
|
+
no_start (bool, optional): Whether to return the hosts without starting the command. Defaults to {_DEFAULT_NO_START}.
|
|
1232
1404
|
skip_hosts (str, optional): The hosts to skip. Defaults to {DEFAULT_SKIP_HOSTS}.
|
|
1233
1405
|
min_char_len (int, optional): The minimum character per line of the curses output. Defaults to {DEFAULT_CURSES_MINIMUM_CHAR_LEN}.
|
|
1234
1406
|
min_line_len (int, optional): The minimum line number for each window of the curses output. Defaults to {DEFAULT_CURSES_MINIMUM_LINE_LEN}.
|
|
@@ -1238,8 +1410,8 @@ def run_command_on_hosts(hosts,commands,oneonone = DEFAULT_ONE_ON_ONE, timeout =
|
|
|
1238
1410
|
Returns:
|
|
1239
1411
|
list: A list of Host objects
|
|
1240
1412
|
'''
|
|
1241
|
-
global
|
|
1242
|
-
global
|
|
1413
|
+
global __gloablUnavailableHosts
|
|
1414
|
+
global __global_suppress_printout
|
|
1243
1415
|
if not max_connections:
|
|
1244
1416
|
max_connections = 4 * os.cpu_count()
|
|
1245
1417
|
elif max_connections == 0:
|
|
@@ -1253,22 +1425,22 @@ def run_command_on_hosts(hosts,commands,oneonone = DEFAULT_ONE_ON_ONE, timeout =
|
|
|
1253
1425
|
if called:
|
|
1254
1426
|
# if called,
|
|
1255
1427
|
# if skipUnreachable is not set, we default to skip unreachable hosts within one command call
|
|
1256
|
-
|
|
1428
|
+
__global_suppress_printout = True
|
|
1257
1429
|
if skipUnreachable is None:
|
|
1258
1430
|
skipUnreachable = True
|
|
1259
1431
|
if skipUnreachable:
|
|
1260
|
-
unavailableHosts =
|
|
1432
|
+
unavailableHosts = __gloablUnavailableHosts
|
|
1261
1433
|
else:
|
|
1262
1434
|
unavailableHosts = set()
|
|
1263
1435
|
else:
|
|
1264
1436
|
# if run in command line ( or emulating running in command line, we default to skip unreachable hosts within one command call )
|
|
1265
1437
|
if skipUnreachable:
|
|
1266
|
-
unavailableHosts =
|
|
1438
|
+
unavailableHosts = __gloablUnavailableHosts
|
|
1267
1439
|
else:
|
|
1268
1440
|
unavailableHosts = set()
|
|
1269
1441
|
skipUnreachable = True
|
|
1270
|
-
global
|
|
1271
|
-
|
|
1442
|
+
global _emo
|
|
1443
|
+
_emo = False
|
|
1272
1444
|
# We create the hosts
|
|
1273
1445
|
hostStr = formHostStr(hosts)
|
|
1274
1446
|
skipHostStr = formHostStr(skip_hosts) if skip_hosts else ''
|
|
@@ -1290,7 +1462,7 @@ def run_command_on_hosts(hosts,commands,oneonone = DEFAULT_ONE_ON_ONE, timeout =
|
|
|
1290
1462
|
targetHostsList = expand_hostnames(frozenset(hostStr.split(',')),no_env=no_env)
|
|
1291
1463
|
skipHostsList = expand_hostnames(frozenset(skipHostStr.split(',')),no_env=no_env)
|
|
1292
1464
|
if skipHostsList:
|
|
1293
|
-
if not
|
|
1465
|
+
if not __global_suppress_printout: print(f"Skipping hosts: {skipHostsList}")
|
|
1294
1466
|
if files and not commands:
|
|
1295
1467
|
# if files are specified but not target dir, we default to file sync mode
|
|
1296
1468
|
file_sync = True
|
|
@@ -1321,22 +1493,22 @@ def run_command_on_hosts(hosts,commands,oneonone = DEFAULT_ONE_ON_ONE, timeout =
|
|
|
1321
1493
|
print(f"Number of commands: {len(commands)}")
|
|
1322
1494
|
print(f"Number of hosts: {len(targetHostsList - skipHostsList)}")
|
|
1323
1495
|
sys.exit(255)
|
|
1324
|
-
if not
|
|
1496
|
+
if not __global_suppress_printout:
|
|
1325
1497
|
print('-'*80)
|
|
1326
1498
|
print("Running in one on one mode")
|
|
1327
1499
|
for host, command in zip(targetHostsList, commands):
|
|
1328
1500
|
if not ipmi and skipUnreachable and host.strip() in unavailableHosts:
|
|
1329
|
-
if not
|
|
1501
|
+
if not __global_suppress_printout: print(f"Skipping unavailable host: {host}")
|
|
1330
1502
|
continue
|
|
1331
1503
|
if host.strip() in skipHostsList: continue
|
|
1332
1504
|
if file_sync:
|
|
1333
|
-
hosts.append(Host(host.strip(), os.path.dirname(command)+os.path.sep, files = [command],ipmi=ipmi,interface_ip_prefix=interface_ip_prefix,scp=scp,extraargs=extraargs))
|
|
1505
|
+
hosts.append(Host(host.strip(), os.path.dirname(command)+os.path.sep, files = [command],ipmi=ipmi,interface_ip_prefix=interface_ip_prefix,scp=scp,extraargs=extraargs,gatherMode=gather_mode))
|
|
1334
1506
|
else:
|
|
1335
|
-
hosts.append(Host(host.strip(), command, files = files,ipmi=ipmi,interface_ip_prefix=interface_ip_prefix,scp=scp,extraargs=extraargs))
|
|
1336
|
-
if not
|
|
1507
|
+
hosts.append(Host(host.strip(), command, files = files,ipmi=ipmi,interface_ip_prefix=interface_ip_prefix,scp=scp,extraargs=extraargs,gatherMode=gather_mode))
|
|
1508
|
+
if not __global_suppress_printout:
|
|
1337
1509
|
print(f"Running command: {command} on host: {host}")
|
|
1338
|
-
if not
|
|
1339
|
-
if not no_start: processRunOnHosts(timeout, password, max_connections, hosts, returnUnfinished,
|
|
1510
|
+
if not __global_suppress_printout: print('-'*80)
|
|
1511
|
+
if not no_start: processRunOnHosts(timeout, password, max_connections, hosts, returnUnfinished, nowatch, json, called, greppable,unavailableHosts,willUpdateUnreachableHosts,curses_min_char_len = curses_min_char_len, curses_min_line_len = curses_min_line_len,single_window=single_window)
|
|
1340
1512
|
return hosts
|
|
1341
1513
|
else:
|
|
1342
1514
|
allHosts = []
|
|
@@ -1345,7 +1517,7 @@ def run_command_on_hosts(hosts,commands,oneonone = DEFAULT_ONE_ON_ONE, timeout =
|
|
|
1345
1517
|
hosts = []
|
|
1346
1518
|
for host in targetHostsList:
|
|
1347
1519
|
if not ipmi and skipUnreachable and host.strip() in unavailableHosts:
|
|
1348
|
-
if not
|
|
1520
|
+
if not __global_suppress_printout: print(f"Skipping unavailable host: {host}")
|
|
1349
1521
|
continue
|
|
1350
1522
|
if host.strip() in skipHostsList: continue
|
|
1351
1523
|
if file_sync:
|
|
@@ -1357,93 +1529,165 @@ def run_command_on_hosts(hosts,commands,oneonone = DEFAULT_ONE_ON_ONE, timeout =
|
|
|
1357
1529
|
print(f"Error: ipmi mode is not supported in interactive mode")
|
|
1358
1530
|
else:
|
|
1359
1531
|
hosts.append(Host(host.strip(), '', files = files,ipmi=ipmi,interface_ip_prefix=interface_ip_prefix,scp=scp,extraargs=extraargs))
|
|
1360
|
-
if not
|
|
1532
|
+
if not __global_suppress_printout:
|
|
1361
1533
|
print('-'*80)
|
|
1362
1534
|
print(f"Running in interactive mode on hosts: {hostStr}" + (f"; skipping: {skipHostStr}" if skipHostStr else ''))
|
|
1363
1535
|
print('-'*80)
|
|
1364
1536
|
if no_start:
|
|
1365
1537
|
print(f"Warning: no_start is set, the command will not be started. As we are in interactive mode, no action will be done.")
|
|
1366
1538
|
else:
|
|
1367
|
-
processRunOnHosts(timeout, password, max_connections, hosts, returnUnfinished,
|
|
1539
|
+
processRunOnHosts(timeout, password, max_connections, hosts, returnUnfinished, nowatch, json, called, greppable,unavailableHosts,willUpdateUnreachableHosts,curses_min_char_len = curses_min_char_len, curses_min_line_len = curses_min_line_len,single_window=single_window)
|
|
1368
1540
|
return hosts
|
|
1369
1541
|
for command in commands:
|
|
1370
1542
|
hosts = []
|
|
1371
1543
|
for host in targetHostsList:
|
|
1372
1544
|
if not ipmi and skipUnreachable and host.strip() in unavailableHosts:
|
|
1373
|
-
if not
|
|
1545
|
+
if not __global_suppress_printout: print(f"Skipping unavailable host: {host}")
|
|
1374
1546
|
continue
|
|
1375
1547
|
if host.strip() in skipHostsList: continue
|
|
1376
1548
|
if file_sync:
|
|
1377
|
-
hosts.append(Host(host.strip(), os.path.dirname(command)+os.path.sep, files = [command],ipmi=ipmi,interface_ip_prefix=interface_ip_prefix,scp=scp,extraargs=extraargs))
|
|
1549
|
+
hosts.append(Host(host.strip(), os.path.dirname(command)+os.path.sep, files = [command],ipmi=ipmi,interface_ip_prefix=interface_ip_prefix,scp=scp,extraargs=extraargs,gatherMode=gather_mode))
|
|
1378
1550
|
else:
|
|
1379
|
-
hosts.append(Host(host.strip(), command, files = files,ipmi=ipmi,interface_ip_prefix=interface_ip_prefix,scp=scp,extraargs=extraargs))
|
|
1380
|
-
if not
|
|
1551
|
+
hosts.append(Host(host.strip(), command, files = files,ipmi=ipmi,interface_ip_prefix=interface_ip_prefix,scp=scp,extraargs=extraargs,gatherMode=gather_mode))
|
|
1552
|
+
if not __global_suppress_printout and len(commands) > 1:
|
|
1381
1553
|
print('-'*80)
|
|
1382
1554
|
print(f"Running command: {command} on hosts: {hostStr}" + (f"; skipping: {skipHostStr}" if skipHostStr else ''))
|
|
1383
1555
|
print('-'*80)
|
|
1384
|
-
if not no_start: processRunOnHosts(timeout, password, max_connections, hosts, returnUnfinished,
|
|
1556
|
+
if not no_start: processRunOnHosts(timeout, password, max_connections, hosts, returnUnfinished, nowatch, json, called, greppable,unavailableHosts,willUpdateUnreachableHosts,curses_min_char_len = curses_min_char_len, curses_min_line_len = curses_min_line_len,single_window=single_window)
|
|
1385
1557
|
allHosts += hosts
|
|
1386
1558
|
return allHosts
|
|
1387
1559
|
|
|
1388
|
-
def
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1560
|
+
def get_default_config(args):
|
|
1561
|
+
'''
|
|
1562
|
+
Get the default config
|
|
1563
|
+
|
|
1564
|
+
Args:
|
|
1565
|
+
args (argparse.Namespace): The arguments
|
|
1566
|
+
|
|
1567
|
+
Returns:
|
|
1568
|
+
dict: The default config
|
|
1569
|
+
'''
|
|
1570
|
+
return {
|
|
1571
|
+
'AUTHOR': AUTHOR,
|
|
1572
|
+
'AUTHOR_EMAIL': AUTHOR_EMAIL,
|
|
1573
|
+
'DEFAULT_HOSTS': args.hosts,
|
|
1574
|
+
'DEFAULT_USERNAME': args.username,
|
|
1575
|
+
'DEFAULT_PASSWORD': args.password,
|
|
1576
|
+
'DEFAULT_EXTRA_ARGS': args.extraargs,
|
|
1577
|
+
'DEFAULT_ONE_ON_ONE': args.oneonone,
|
|
1578
|
+
'DEFAULT_SCP': args.scp,
|
|
1579
|
+
'DEFAULT_FILE_SYNC': args.file_sync,
|
|
1580
|
+
'DEFAULT_TIMEOUT': DEFAULT_TIMEOUT,
|
|
1581
|
+
'DEFAULT_CLI_TIMEOUT': args.timeout,
|
|
1582
|
+
'DEFAULT_REPEAT': args.repeat,
|
|
1583
|
+
'DEFAULT_INTERVAL': args.interval,
|
|
1584
|
+
'DEFAULT_IPMI': args.ipmi,
|
|
1585
|
+
'DEFAULT_IPMI_INTERFACE_IP_PREFIX': args.ipmi_interface_ip_prefix,
|
|
1586
|
+
'DEFAULT_INTERFACE_IP_PREFIX': args.interface_ip_prefix,
|
|
1587
|
+
'DEFAULT_NO_WATCH': args.nowatch,
|
|
1588
|
+
'DEFAULT_CURSES_MINIMUM_CHAR_LEN': args.window_width,
|
|
1589
|
+
'DEFAULT_CURSES_MINIMUM_LINE_LEN': args.window_height,
|
|
1590
|
+
'DEFAULT_SINGLE_WINDOW': args.single_window,
|
|
1591
|
+
'DEFAULT_ERROR_ONLY': args.error_only,
|
|
1592
|
+
'DEFAULT_NO_OUTPUT': args.no_output,
|
|
1593
|
+
'DEFAULT_NO_ENV': args.no_env,
|
|
1594
|
+
'DEFAULT_ENV_FILE': args.env_file,
|
|
1595
|
+
'DEFAULT_MAX_CONNECTIONS': args.max_connections if args.max_connections != 4 * os.cpu_count() else None,
|
|
1596
|
+
'DEFAULT_JSON_MODE': args.json,
|
|
1597
|
+
'DEFAULT_PRINT_SUCCESS_HOSTS': args.success_hosts,
|
|
1598
|
+
'DEFAULT_GREPPABLE_MODE': args.greppable,
|
|
1599
|
+
'DEFAULT_SKIP_UNREACHABLE': args.skip_unreachable,
|
|
1600
|
+
'DEFAULT_SKIP_HOSTS': args.skip_hosts,
|
|
1601
|
+
'ERROR_MESSAGES_TO_IGNORE': ERROR_MESSAGES_TO_IGNORE,
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
def write_default_config(args,CONFIG_FILE,backup = True):
|
|
1605
|
+
if backup and os.path.exists(CONFIG_FILE):
|
|
1606
|
+
os.rename(CONFIG_FILE,CONFIG_FILE+'.bak')
|
|
1607
|
+
default_config = get_default_config(args)
|
|
1608
|
+
with open(CONFIG_FILE,'w') as f:
|
|
1609
|
+
json.dump(default_config,f,indent=4)
|
|
1399
1610
|
|
|
1400
1611
|
|
|
1612
|
+
def main():
|
|
1613
|
+
global _emo
|
|
1614
|
+
global __global_suppress_printout
|
|
1615
|
+
global __gloablUnavailableHosts
|
|
1616
|
+
global __mainReturnCode
|
|
1617
|
+
global __failedHosts
|
|
1618
|
+
global __ipmiiInterfaceIPPrefix
|
|
1619
|
+
global _binPaths
|
|
1620
|
+
global _env_file
|
|
1621
|
+
_emo = False
|
|
1401
1622
|
# We handle the signal
|
|
1402
1623
|
signal.signal(signal.SIGINT, signal_handler)
|
|
1403
1624
|
# We parse the arguments
|
|
1404
|
-
parser = argparse.ArgumentParser(description='Run a command on multiple hosts, Use #HOST# or #HOSTNAME# to replace the host name in the command')
|
|
1405
|
-
parser.add_argument('hosts', metavar='hosts', type=str, help='Hosts to run the command on, use "," to seperate hosts')
|
|
1406
|
-
parser.add_argument('commands', metavar='commands', type=str, nargs='
|
|
1625
|
+
parser = argparse.ArgumentParser(description=f'Run a command on multiple hosts, Use #HOST# or #HOSTNAME# to replace the host name in the command. Config file: {CONFIG_FILE}')
|
|
1626
|
+
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)
|
|
1627
|
+
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.')
|
|
1407
1628
|
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)
|
|
1408
|
-
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)
|
|
1409
1629
|
parser.add_argument('-p', '--password', type=str,help=f'The password to use to connect to the hosts, (default: {DEFAULT_PASSWORD})',default=DEFAULT_PASSWORD)
|
|
1630
|
+
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)
|
|
1410
1631
|
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)
|
|
1411
1632
|
parser.add_argument("-f","--file", action='append', help="The file to be copied to the hosts. Use -f multiple times to copy multiple files")
|
|
1412
1633
|
parser.add_argument('--file_sync', action='store_true', help=f'Operate in file sync mode, sync path in <COMMANDS> from this machine to <HOSTS>. Treat --file <FILE> and <COMMANDS> both as source as source and destination will be the same in this mode. (default: {DEFAULT_FILE_SYNC})', default=DEFAULT_FILE_SYNC)
|
|
1413
1634
|
parser.add_argument('--scp', action='store_true', help=f'Use scp for copying files instead of rsync. Need to use this on windows. (default: {DEFAULT_SCP})', default=DEFAULT_SCP)
|
|
1635
|
+
parser.add_argument('-gm','--gather_mode', action='store_true', help=f'Gather files from the hosts instead of sending files to the hosts. Will send remote files specified in <FILE> to local path specified in <COMMANDS> (default: False)', default=False)
|
|
1414
1636
|
#parser.add_argument("-d",'-c',"--destination", type=str, help="The destination of the files. Same as specify with commands. Added for compatibility. Use #HOST# or #HOSTNAME# to replace the host name in the destination")
|
|
1415
|
-
parser.add_argument("-t","--timeout", type=int, help=f"Timeout for each command in seconds (default:
|
|
1637
|
+
parser.add_argument("-t","--timeout", type=int, help=f"Timeout for each command in seconds (default: {DEFAULT_CLI_TIMEOUT} (disabled))", default=DEFAULT_CLI_TIMEOUT)
|
|
1416
1638
|
parser.add_argument("-r","--repeat", type=int, help=f"Repeat the command for a number of times (default: {DEFAULT_REPEAT})", default=DEFAULT_REPEAT)
|
|
1417
1639
|
parser.add_argument("-i","--interval", type=int, help=f"Interval between repeats in seconds (default: {DEFAULT_INTERVAL})", default=DEFAULT_INTERVAL)
|
|
1418
1640
|
parser.add_argument("--ipmi", action='store_true', help=f"Use ipmitool to run the command. (default: {DEFAULT_IPMI})", default=DEFAULT_IPMI)
|
|
1419
1641
|
parser.add_argument("-mpre","--ipmi_interface_ip_prefix", type=str, help=f"The prefix of the IPMI interfaces (default: {DEFAULT_IPMI_INTERFACE_IP_PREFIX})", default=DEFAULT_IPMI_INTERFACE_IP_PREFIX)
|
|
1420
1642
|
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)
|
|
1421
|
-
parser.add_argument("-q","--quiet", action='store_true', help=f"Quiet mode, no curses, only print the output. (default: {
|
|
1643
|
+
parser.add_argument("-q","-nw","--nowatch","--quiet", action='store_true', help=f"Quiet mode, no curses watch, only print the output. (default: {DEFAULT_NO_WATCH})", default=DEFAULT_NO_WATCH)
|
|
1422
1644
|
parser.add_argument("-ww",'--window_width', type=int, help=f"The minimum character length of the curses window. (default: {DEFAULT_CURSES_MINIMUM_CHAR_LEN})", default=DEFAULT_CURSES_MINIMUM_CHAR_LEN)
|
|
1423
1645
|
parser.add_argument("-wh",'--window_height', type=int, help=f"The minimum line height of the curses window. (default: {DEFAULT_CURSES_MINIMUM_LINE_LEN})", default=DEFAULT_CURSES_MINIMUM_LINE_LEN)
|
|
1424
1646
|
parser.add_argument('-sw','--single_window', action='store_true', help=f'Use a single window for all hosts. (default: {DEFAULT_SINGLE_WINDOW})', default=DEFAULT_SINGLE_WINDOW)
|
|
1425
1647
|
parser.add_argument('-eo','--error_only', action='store_true', help=f'Only print the error output. (default: {DEFAULT_ERROR_ONLY})', default=DEFAULT_ERROR_ONLY)
|
|
1426
|
-
parser.add_argument("-no","--
|
|
1648
|
+
parser.add_argument("-no","--no_output", action='store_true', help=f"Do not print the output. (default: {DEFAULT_NO_OUTPUT})", default=DEFAULT_NO_OUTPUT)
|
|
1427
1649
|
parser.add_argument('--no_env', action='store_true', help=f'Do not load the environment variables. (default: {DEFAULT_NO_ENV})', default=DEFAULT_NO_ENV)
|
|
1428
1650
|
parser.add_argument("--env_file", type=str, help=f"The file to load the environment variables from. (default: {DEFAULT_ENV_FILE})", default=DEFAULT_ENV_FILE)
|
|
1429
|
-
parser.add_argument("-m","--
|
|
1651
|
+
parser.add_argument("-m","--max_connections", type=int, help=f"Max number of connections to use (default: 4 * cpu_count)", default=DEFAULT_MAX_CONNECTIONS)
|
|
1430
1652
|
parser.add_argument("-j","--json", action='store_true', help=F"Output in json format. (default: {DEFAULT_JSON_MODE})", default=DEFAULT_JSON_MODE)
|
|
1431
1653
|
parser.add_argument("--success_hosts", action='store_true', help=f"Output the hosts that succeeded in summary as wells. (default: {DEFAULT_PRINT_SUCCESS_HOSTS})", default=DEFAULT_PRINT_SUCCESS_HOSTS)
|
|
1432
1654
|
parser.add_argument("-g","--greppable", action='store_true', help=f"Output in greppable format. (default: {DEFAULT_GREPPABLE_MODE})", default=DEFAULT_GREPPABLE_MODE)
|
|
1433
|
-
parser.add_argument("-
|
|
1434
|
-
parser.add_argument("-
|
|
1435
|
-
parser.add_argument(
|
|
1436
|
-
parser.add_argument("-V","--version", action='version', version=f'%(prog)s {version}
|
|
1655
|
+
parser.add_argument("-su","--skip_unreachable", action='store_true', help=f"Skip unreachable hosts while using --repeat. Note: Timedout Hosts are considered unreachable. Note: multiple command sequence will still auto skip unreachable hosts. (default: {DEFAULT_SKIP_UNREACHABLE})", default=DEFAULT_SKIP_UNREACHABLE)
|
|
1656
|
+
parser.add_argument("-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)
|
|
1657
|
+
parser.add_argument('--generate_default_config_file', action='store_true', help=f'Generate / store the default config file from command line argument and current config at {CONFIG_FILE}')
|
|
1658
|
+
parser.add_argument("-V","--version", action='version', version=f'%(prog)s {version} with [ {", ".join(_binPaths.keys())} ] by {AUTHOR} ({AUTHOR_EMAIL})')
|
|
1437
1659
|
|
|
1438
1660
|
# parser.add_argument('-u', '--user', metavar='user', type=str, nargs=1,
|
|
1439
1661
|
# help='the user to use to connect to the hosts')
|
|
1440
1662
|
args = parser.parse_args()
|
|
1441
1663
|
|
|
1442
|
-
|
|
1664
|
+
if args.generate_default_config_file:
|
|
1665
|
+
try:
|
|
1666
|
+
if os.path.exists(CONFIG_FILE):
|
|
1667
|
+
print(f"Warning: {CONFIG_FILE} already exists, what to do? (o/b/n)")
|
|
1668
|
+
print(f"o: Overwrite the file")
|
|
1669
|
+
print(f"b: Rename the current config file at {CONFIG_FILE}.bak forcefully and write the new config file (default)")
|
|
1670
|
+
print(f"n: Do nothing")
|
|
1671
|
+
inStr = input_with_timeout_and_countdown(10)
|
|
1672
|
+
if (not inStr) or inStr.lower().strip().startswith('b'):
|
|
1673
|
+
write_default_config(args,CONFIG_FILE,backup = True)
|
|
1674
|
+
print(f"Config file written to {CONFIG_FILE}")
|
|
1675
|
+
elif inStr.lower().strip().startswith('o'):
|
|
1676
|
+
write_default_config(args,CONFIG_FILE,backup = False)
|
|
1677
|
+
print(f"Config file written to {CONFIG_FILE}")
|
|
1678
|
+
else:
|
|
1679
|
+
write_default_config(args,CONFIG_FILE,backup = True)
|
|
1680
|
+
print(f"Config file written to {CONFIG_FILE}")
|
|
1681
|
+
except Exception as e:
|
|
1682
|
+
print(f"Error while writing config file: {e}")
|
|
1683
|
+
if not args.commands:
|
|
1684
|
+
sys.exit(0)
|
|
1685
|
+
|
|
1686
|
+
_env_file = args.env_file
|
|
1443
1687
|
# if there are more than 1 commands, and every command only consists of one word,
|
|
1444
1688
|
# we will ask the user to confirm if they want to run multiple commands or just one command.
|
|
1445
1689
|
if not args.file and len(args.commands) > 1 and all([len(command.split()) == 1 for command in args.commands]):
|
|
1446
|
-
print(f"Multiple one word command detected, what to do? (
|
|
1690
|
+
print(f"Multiple one word command detected, what to do? (1/m/n)")
|
|
1447
1691
|
print(f"1: Run 1 command [{' '.join(args.commands)}] on all hosts ( default )")
|
|
1448
1692
|
print(f"m: Run multiple commands [{', '.join(args.commands)}] on all hosts")
|
|
1449
1693
|
print(f"n: Exit")
|
|
@@ -1456,66 +1700,64 @@ def main():
|
|
|
1456
1700
|
else:
|
|
1457
1701
|
sys.exit(0)
|
|
1458
1702
|
|
|
1459
|
-
|
|
1703
|
+
__ipmiiInterfaceIPPrefix = args.ipmi_interface_ip_prefix
|
|
1460
1704
|
|
|
1461
|
-
if not args.greppable and not args.json and not args.
|
|
1462
|
-
|
|
1705
|
+
if not args.greppable and not args.json and not args.no_output:
|
|
1706
|
+
__global_suppress_printout = False
|
|
1463
1707
|
|
|
1464
|
-
if not
|
|
1708
|
+
if not __global_suppress_printout:
|
|
1465
1709
|
print('> ' + getStrCommand(args.hosts,args.commands,oneonone=args.oneonone,timeout=args.timeout,password=args.password,
|
|
1466
|
-
|
|
1467
|
-
files=args.file,file_sync=args.file_sync,ipmi=args.ipmi,interface_ip_prefix=args.interface_ip_prefix,scp=args.scp,username=args.username,
|
|
1468
|
-
extraargs=args.extraargs,skipUnreachable=args.
|
|
1710
|
+
nowatch=args.nowatch,json=args.json,called=args.no_output,max_connections=args.max_connections,
|
|
1711
|
+
files=args.file,file_sync=args.file_sync,ipmi=args.ipmi,interface_ip_prefix=args.interface_ip_prefix,scp=args.scp,gather_mode = args.gather_mode,username=args.username,
|
|
1712
|
+
extraargs=args.extraargs,skipUnreachable=args.skip_unreachable,no_env=args.no_env,greppable=args.greppable,skip_hosts = args.skip_hosts,
|
|
1469
1713
|
curses_min_char_len = args.window_width, curses_min_line_len = args.window_height,single_window=args.single_window,error_only=args.error_only))
|
|
1470
1714
|
if args.error_only:
|
|
1471
|
-
|
|
1715
|
+
__global_suppress_printout = True
|
|
1472
1716
|
|
|
1473
1717
|
for i in range(args.repeat):
|
|
1474
1718
|
if args.interval > 0 and i < args.repeat - 1:
|
|
1475
1719
|
print(f"Sleeping for {args.interval} seconds")
|
|
1476
1720
|
time.sleep(args.interval)
|
|
1477
1721
|
|
|
1478
|
-
if not
|
|
1722
|
+
if not __global_suppress_printout: print(f"Running the {i+1}/{args.repeat} time") if args.repeat > 1 else None
|
|
1479
1723
|
hosts = run_command_on_hosts(args.hosts,args.commands,
|
|
1480
1724
|
oneonone=args.oneonone,timeout=args.timeout,password=args.password,
|
|
1481
|
-
|
|
1482
|
-
files=args.file,file_sync=args.file_sync,ipmi=args.ipmi,interface_ip_prefix=args.interface_ip_prefix,scp=args.scp,username=args.username,
|
|
1483
|
-
extraargs=args.extraargs,skipUnreachable=args.
|
|
1725
|
+
nowatch=args.nowatch,json=args.json,called=args.no_output,max_connections=args.max_connections,
|
|
1726
|
+
files=args.file,file_sync=args.file_sync,ipmi=args.ipmi,interface_ip_prefix=args.interface_ip_prefix,scp=args.scp,gather_mode = args.gather_mode,username=args.username,
|
|
1727
|
+
extraargs=args.extraargs,skipUnreachable=args.skip_unreachable,no_env=args.no_env,greppable=args.greppable,skip_hosts = args.skip_hosts,
|
|
1484
1728
|
curses_min_char_len = args.window_width, curses_min_line_len = args.window_height,single_window=args.single_window,error_only=args.error_only)
|
|
1485
1729
|
#print('*'*80)
|
|
1486
1730
|
|
|
1487
|
-
if not
|
|
1731
|
+
if not __global_suppress_printout: print('-'*80)
|
|
1488
1732
|
|
|
1489
1733
|
succeededHosts = set()
|
|
1490
1734
|
for host in hosts:
|
|
1491
1735
|
if host.returncode and host.returncode != 0:
|
|
1492
|
-
|
|
1493
|
-
|
|
1736
|
+
__mainReturnCode += 1
|
|
1737
|
+
__failedHosts.add(host.name)
|
|
1494
1738
|
else:
|
|
1495
1739
|
succeededHosts.add(host.name)
|
|
1496
|
-
succeededHosts -=
|
|
1740
|
+
succeededHosts -= __failedHosts
|
|
1497
1741
|
# sort the failed hosts and succeeded hosts
|
|
1498
|
-
|
|
1742
|
+
__failedHosts = sorted(__failedHosts)
|
|
1499
1743
|
succeededHosts = sorted(succeededHosts)
|
|
1500
|
-
if
|
|
1501
|
-
if not
|
|
1744
|
+
if __mainReturnCode > 0:
|
|
1745
|
+
if not __global_suppress_printout: print(f'Complete. Failed hosts (Return Code not 0) count: {__mainReturnCode}')
|
|
1502
1746
|
# with open('/tmp/bashcmd.stdin','w') as f:
|
|
1503
|
-
# f.write(f"export failed_hosts={
|
|
1504
|
-
if not
|
|
1747
|
+
# f.write(f"export failed_hosts={__failedHosts}\n")
|
|
1748
|
+
if not __global_suppress_printout: print(f'failed_hosts: {",".join(__failedHosts)}')
|
|
1505
1749
|
else:
|
|
1506
|
-
if not
|
|
1750
|
+
if not __global_suppress_printout: print('Complete. All hosts returned 0.')
|
|
1507
1751
|
|
|
1508
|
-
if args.success_hosts and not
|
|
1752
|
+
if args.success_hosts and not __global_suppress_printout:
|
|
1509
1753
|
print(f'succeeded_hosts: {",".join(succeededHosts)}')
|
|
1510
1754
|
|
|
1511
1755
|
if threading.active_count() > 1:
|
|
1512
|
-
if not
|
|
1756
|
+
if not __global_suppress_printout: print(f'Remaining active thread: {threading.active_count()}')
|
|
1513
1757
|
# os.system(f'pkill -ef {os.path.basename(__file__)}')
|
|
1514
1758
|
# os._exit(mainReturnCode)
|
|
1515
1759
|
|
|
1516
|
-
sys.exit(
|
|
1517
|
-
|
|
1518
|
-
|
|
1760
|
+
sys.exit(__mainReturnCode)
|
|
1519
1761
|
|
|
1520
1762
|
if __name__ == "__main__":
|
|
1521
1763
|
main()
|