multiSSH3 5.43__tar.gz → 5.45__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of multiSSH3 might be problematic. Click here for more details.
- {multissh3-5.43 → multissh3-5.45}/PKG-INFO +1 -1
- {multissh3-5.43 → multissh3-5.45}/multiSSH3.egg-info/PKG-INFO +1 -1
- {multissh3-5.43 → multissh3-5.45}/multiSSH3.py +132 -150
- {multissh3-5.43 → multissh3-5.45}/README.md +0 -0
- {multissh3-5.43 → multissh3-5.45}/multiSSH3.egg-info/SOURCES.txt +0 -0
- {multissh3-5.43 → multissh3-5.45}/multiSSH3.egg-info/dependency_links.txt +0 -0
- {multissh3-5.43 → multissh3-5.45}/multiSSH3.egg-info/entry_points.txt +0 -0
- {multissh3-5.43 → multissh3-5.45}/multiSSH3.egg-info/requires.txt +0 -0
- {multissh3-5.43 → multissh3-5.45}/multiSSH3.egg-info/top_level.txt +0 -0
- {multissh3-5.43 → multissh3-5.45}/setup.cfg +0 -0
- {multissh3-5.43 → multissh3-5.45}/setup.py +0 -0
|
@@ -33,6 +33,7 @@ import getpass
|
|
|
33
33
|
import uuid
|
|
34
34
|
import tempfile
|
|
35
35
|
import math
|
|
36
|
+
from itertools import count
|
|
36
37
|
|
|
37
38
|
try:
|
|
38
39
|
# Check if functiools.cache is available
|
|
@@ -45,10 +46,18 @@ except AttributeError:
|
|
|
45
46
|
# If neither is available, use a dummy decorator
|
|
46
47
|
def cache_decorator(func):
|
|
47
48
|
return func
|
|
48
|
-
version = '5.
|
|
49
|
+
version = '5.45'
|
|
49
50
|
VERSION = version
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
CONFIG_FILE_CHAIN = ['./multiSSH3.config.json',
|
|
53
|
+
'~/multiSSH3.config.json',
|
|
54
|
+
'~/.multiSSH3.config.json',
|
|
55
|
+
'~/.config/multiSSH3/multiSSH3.config.json',
|
|
56
|
+
'/etc/multiSSH3.d/multiSSH3.config.json',
|
|
57
|
+
'/etc/multiSSH3.config.json'] # The first one has the highest priority
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# TODO: Add terminal TUI with history support
|
|
52
61
|
|
|
53
62
|
# ------------ Pre Helper Functions ----------------
|
|
54
63
|
def eprint(*args, **kwargs):
|
|
@@ -149,8 +158,8 @@ def getIP(hostname: str,local=False):
|
|
|
149
158
|
except:
|
|
150
159
|
return None
|
|
151
160
|
|
|
152
|
-
|
|
153
|
-
|
|
161
|
+
|
|
162
|
+
_i_counter = count()
|
|
154
163
|
def _get_i():
|
|
155
164
|
'''
|
|
156
165
|
Get the global counter for the host objects
|
|
@@ -158,15 +167,11 @@ def _get_i():
|
|
|
158
167
|
Returns:
|
|
159
168
|
int: The global counter for the host objects
|
|
160
169
|
'''
|
|
161
|
-
|
|
162
|
-
global __host_i_lock
|
|
163
|
-
with __host_i_lock:
|
|
164
|
-
__host_i_counter += 1
|
|
165
|
-
return __host_i_counter
|
|
170
|
+
return next(_i_counter)
|
|
166
171
|
|
|
167
172
|
# ------------ Host Object ----------------
|
|
168
173
|
class Host:
|
|
169
|
-
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 =
|
|
174
|
+
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):
|
|
170
175
|
self.name = name # the name of the host (hostname or IP address)
|
|
171
176
|
self.command = command # the command to run on the host
|
|
172
177
|
self.returncode = None # the return code of the command
|
|
@@ -185,7 +190,7 @@ class Host:
|
|
|
185
190
|
self.extraargs = extraargs # extra arguments to be passed to ssh
|
|
186
191
|
self.resolvedName = None # the resolved IP address of the host
|
|
187
192
|
# also store a globally unique integer i from 0
|
|
188
|
-
self.i = i
|
|
193
|
+
self.i = i if i != -1 else _get_i()
|
|
189
194
|
self.uuid = uuid
|
|
190
195
|
self.identity_file = identity_file
|
|
191
196
|
self.ip = ip if ip else getIP(name)
|
|
@@ -224,44 +229,42 @@ def load_config_file(config_file):
|
|
|
224
229
|
return {}
|
|
225
230
|
return config
|
|
226
231
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
'
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
'
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
'
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
'SSH_STRICT_HOST_KEY_CHECKING': False,
|
|
264
|
-
'ERROR_MESSAGES_TO_IGNORE': [
|
|
232
|
+
if True:
|
|
233
|
+
AUTHOR = 'Yufei Pan'
|
|
234
|
+
AUTHOR_EMAIL = 'pan@zopyr.us'
|
|
235
|
+
DEFAULT_HOSTS = 'all'
|
|
236
|
+
DEFAULT_USERNAME = None
|
|
237
|
+
DEFAULT_PASSWORD = ''
|
|
238
|
+
DEFAULT_IDENTITY_FILE = None
|
|
239
|
+
DEDAULT_SSH_KEY_SEARCH_PATH = '~/.ssh/'
|
|
240
|
+
DEFAULT_USE_KEY = False
|
|
241
|
+
DEFAULT_EXTRA_ARGS = None
|
|
242
|
+
DEFAULT_ONE_ON_ONE = False
|
|
243
|
+
DEFAULT_SCP = False
|
|
244
|
+
DEFAULT_FILE_SYNC = False
|
|
245
|
+
DEFAULT_TIMEOUT = 50
|
|
246
|
+
DEFAULT_CLI_TIMEOUT = 0
|
|
247
|
+
DEFAULT_REPEAT = 1
|
|
248
|
+
DEFAULT_INTERVAL = 0
|
|
249
|
+
DEFAULT_IPMI = False
|
|
250
|
+
DEFAULT_IPMI_INTERFACE_IP_PREFIX = ''
|
|
251
|
+
DEFAULT_INTERFACE_IP_PREFIX = None
|
|
252
|
+
DEFAULT_NO_WATCH = False
|
|
253
|
+
DEFAULT_CURSES_MINIMUM_CHAR_LEN = 40
|
|
254
|
+
DEFAULT_CURSES_MINIMUM_LINE_LEN = 1
|
|
255
|
+
DEFAULT_SINGLE_WINDOW = False
|
|
256
|
+
DEFAULT_ERROR_ONLY = False
|
|
257
|
+
DEFAULT_NO_OUTPUT = False
|
|
258
|
+
DEFAULT_NO_ENV = False
|
|
259
|
+
DEFAULT_ENV_FILE = '/etc/profile.d/hosts.sh'
|
|
260
|
+
DEFAULT_MAX_CONNECTIONS = 4 * os.cpu_count()
|
|
261
|
+
DEFAULT_JSON_MODE = False
|
|
262
|
+
DEFAULT_PRINT_SUCCESS_HOSTS = False
|
|
263
|
+
DEFAULT_GREPPABLE_MODE = False
|
|
264
|
+
DEFAULT_SKIP_UNREACHABLE = True
|
|
265
|
+
DEFAULT_SKIP_HOSTS = ''
|
|
266
|
+
SSH_STRICT_HOST_KEY_CHECKING = False
|
|
267
|
+
ERROR_MESSAGES_TO_IGNORE = [
|
|
265
268
|
'Pseudo-terminal will not be allocated because stdin is not a terminal',
|
|
266
269
|
'Connection to .* closed',
|
|
267
270
|
'Warning: Permanently added',
|
|
@@ -269,80 +272,34 @@ __build_in_default_config = {
|
|
|
269
272
|
'disabling multiplexing',
|
|
270
273
|
'Killed by signal',
|
|
271
274
|
'Connection reset by peer',
|
|
272
|
-
]
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
275
|
+
]
|
|
276
|
+
_DEFAULT_CALLED = True
|
|
277
|
+
_DEFAULT_RETURN_UNFINISHED = False
|
|
278
|
+
_DEFAULT_UPDATE_UNREACHABLE_HOSTS = True
|
|
279
|
+
_DEFAULT_NO_START = False
|
|
280
|
+
_etc_hosts = {}
|
|
281
|
+
_sshpassPath = None
|
|
282
|
+
_sshPath = None
|
|
283
|
+
_scpPath = None
|
|
284
|
+
_ipmitoolPath = None
|
|
285
|
+
_rsyncPath = None
|
|
286
|
+
_shellPath = None
|
|
287
|
+
__ERROR_MESSAGES_TO_IGNORE_REGEX =None
|
|
288
|
+
__DEBUG_MODE = False
|
|
287
289
|
|
|
288
290
|
# Load Config Based Default Global variables
|
|
289
291
|
if True:
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
DEFAULT_ENV_FILE = __configs_from_file.get('DEFAULT_ENV_FILE', __build_in_default_config['DEFAULT_ENV_FILE'])
|
|
295
|
-
DEFAULT_USERNAME = __configs_from_file.get('DEFAULT_USERNAME', __build_in_default_config['DEFAULT_USERNAME'])
|
|
296
|
-
DEFAULT_PASSWORD = __configs_from_file.get('DEFAULT_PASSWORD', __build_in_default_config['DEFAULT_PASSWORD'])
|
|
297
|
-
DEFAULT_IDENTITY_FILE = __configs_from_file.get('DEFAULT_IDENTITY_FILE', __build_in_default_config['DEFAULT_IDENTITY_FILE'])
|
|
298
|
-
DEDAULT_SSH_KEY_SEARCH_PATH = __configs_from_file.get('DEDAULT_SSH_KEY_SEARCH_PATH', __build_in_default_config['DEDAULT_SSH_KEY_SEARCH_PATH'])
|
|
299
|
-
DEFAULT_USE_KEY = __configs_from_file.get('DEFAULT_USE_KEY', __build_in_default_config['DEFAULT_USE_KEY'])
|
|
300
|
-
DEFAULT_EXTRA_ARGS = __configs_from_file.get('DEFAULT_EXTRA_ARGS', __build_in_default_config['DEFAULT_EXTRA_ARGS'])
|
|
301
|
-
DEFAULT_ONE_ON_ONE = __configs_from_file.get('DEFAULT_ONE_ON_ONE', __build_in_default_config['DEFAULT_ONE_ON_ONE'])
|
|
302
|
-
DEFAULT_SCP = __configs_from_file.get('DEFAULT_SCP', __build_in_default_config['DEFAULT_SCP'])
|
|
303
|
-
DEFAULT_FILE_SYNC = __configs_from_file.get('DEFAULT_FILE_SYNC', __build_in_default_config['DEFAULT_FILE_SYNC'])
|
|
304
|
-
DEFAULT_TIMEOUT = __configs_from_file.get('DEFAULT_TIMEOUT', __build_in_default_config['DEFAULT_TIMEOUT'])
|
|
305
|
-
DEFAULT_CLI_TIMEOUT = __configs_from_file.get('DEFAULT_CLI_TIMEOUT', __build_in_default_config['DEFAULT_CLI_TIMEOUT'])
|
|
306
|
-
DEFAULT_REPEAT = __configs_from_file.get('DEFAULT_REPEAT', __build_in_default_config['DEFAULT_REPEAT'])
|
|
307
|
-
DEFAULT_INTERVAL = __configs_from_file.get('DEFAULT_INTERVAL', __build_in_default_config['DEFAULT_INTERVAL'])
|
|
308
|
-
DEFAULT_IPMI = __configs_from_file.get('DEFAULT_IPMI', __build_in_default_config['DEFAULT_IPMI'])
|
|
309
|
-
DEFAULT_IPMI_INTERFACE_IP_PREFIX = __configs_from_file.get('DEFAULT_IPMI_INTERFACE_IP_PREFIX', __build_in_default_config['DEFAULT_IPMI_INTERFACE_IP_PREFIX'])
|
|
310
|
-
DEFAULT_INTERFACE_IP_PREFIX = __configs_from_file.get('DEFAULT_INTERFACE_IP_PREFIX', __build_in_default_config['DEFAULT_INTERFACE_IP_PREFIX'])
|
|
311
|
-
DEFAULT_NO_WATCH = __configs_from_file.get('DEFAULT_NO_WATCH', __build_in_default_config['DEFAULT_NO_WATCH'])
|
|
312
|
-
DEFAULT_CURSES_MINIMUM_CHAR_LEN = __configs_from_file.get('DEFAULT_CURSES_MINIMUM_CHAR_LEN', __build_in_default_config['DEFAULT_CURSES_MINIMUM_CHAR_LEN'])
|
|
313
|
-
DEFAULT_CURSES_MINIMUM_LINE_LEN = __configs_from_file.get('DEFAULT_CURSES_MINIMUM_LINE_LEN', __build_in_default_config['DEFAULT_CURSES_MINIMUM_LINE_LEN'])
|
|
314
|
-
DEFAULT_SINGLE_WINDOW = __configs_from_file.get('DEFAULT_SINGLE_WINDOW', __build_in_default_config['DEFAULT_SINGLE_WINDOW'])
|
|
315
|
-
DEFAULT_ERROR_ONLY = __configs_from_file.get('DEFAULT_ERROR_ONLY', __build_in_default_config['DEFAULT_ERROR_ONLY'])
|
|
316
|
-
DEFAULT_NO_OUTPUT = __configs_from_file.get('DEFAULT_NO_OUTPUT', __build_in_default_config['DEFAULT_NO_OUTPUT'])
|
|
317
|
-
DEFAULT_NO_ENV = __configs_from_file.get('DEFAULT_NO_ENV', __build_in_default_config['DEFAULT_NO_ENV'])
|
|
318
|
-
DEFAULT_MAX_CONNECTIONS = __configs_from_file.get('DEFAULT_MAX_CONNECTIONS', __build_in_default_config['DEFAULT_MAX_CONNECTIONS'])
|
|
319
|
-
if not DEFAULT_MAX_CONNECTIONS:
|
|
320
|
-
DEFAULT_MAX_CONNECTIONS = 4 * os.cpu_count()
|
|
321
|
-
DEFAULT_JSON_MODE = __configs_from_file.get('DEFAULT_JSON_MODE', __build_in_default_config['DEFAULT_JSON_MODE'])
|
|
322
|
-
DEFAULT_PRINT_SUCCESS_HOSTS = __configs_from_file.get('DEFAULT_PRINT_SUCCESS_HOSTS', __build_in_default_config['DEFAULT_PRINT_SUCCESS_HOSTS'])
|
|
323
|
-
DEFAULT_GREPPABLE_MODE = __configs_from_file.get('DEFAULT_GREPPABLE_MODE', __build_in_default_config['DEFAULT_GREPPABLE_MODE'])
|
|
324
|
-
DEFAULT_SKIP_UNREACHABLE = __configs_from_file.get('DEFAULT_SKIP_UNREACHABLE', __build_in_default_config['DEFAULT_SKIP_UNREACHABLE'])
|
|
325
|
-
DEFAULT_SKIP_HOSTS = __configs_from_file.get('DEFAULT_SKIP_HOSTS', __build_in_default_config['DEFAULT_SKIP_HOSTS'])
|
|
326
|
-
|
|
327
|
-
SSH_STRICT_HOST_KEY_CHECKING = __configs_from_file.get('SSH_STRICT_HOST_KEY_CHECKING', __build_in_default_config['SSH_STRICT_HOST_KEY_CHECKING'])
|
|
328
|
-
|
|
329
|
-
ERROR_MESSAGES_TO_IGNORE = __configs_from_file.get('ERROR_MESSAGES_TO_IGNORE', __build_in_default_config['ERROR_MESSAGES_TO_IGNORE'])
|
|
330
|
-
|
|
331
|
-
_DEFAULT_CALLED = __configs_from_file.get('_DEFAULT_CALLED', __build_in_default_config['_DEFAULT_CALLED'])
|
|
332
|
-
_DEFAULT_RETURN_UNFINISHED = __configs_from_file.get('_DEFAULT_RETURN_UNFINISHED', __build_in_default_config['_DEFAULT_RETURN_UNFINISHED'])
|
|
333
|
-
_DEFAULT_UPDATE_UNREACHABLE_HOSTS = __configs_from_file.get('_DEFAULT_UPDATE_UNREACHABLE_HOSTS', __build_in_default_config['_DEFAULT_UPDATE_UNREACHABLE_HOSTS'])
|
|
334
|
-
_DEFAULT_NO_START = __configs_from_file.get('_DEFAULT_NO_START', __build_in_default_config['_DEFAULT_NO_START'])
|
|
335
|
-
|
|
292
|
+
__configs_from_file = {}
|
|
293
|
+
for config_file in reversed(CONFIG_FILE_CHAIN.copy()):
|
|
294
|
+
__configs_from_file.update(load_config_file(os.path.expanduser(config_file)))
|
|
295
|
+
globals().update(__configs_from_file)
|
|
336
296
|
# form the regex from the list
|
|
337
|
-
__ERROR_MESSAGES_TO_IGNORE_REGEX = __configs_from_file.get('__ERROR_MESSAGES_TO_IGNORE_REGEX', __build_in_default_config['__ERROR_MESSAGES_TO_IGNORE_REGEX'])
|
|
338
297
|
if __ERROR_MESSAGES_TO_IGNORE_REGEX:
|
|
339
|
-
eprint('Using __ERROR_MESSAGES_TO_IGNORE_REGEX
|
|
340
|
-
__ERROR_MESSAGES_TO_IGNORE_REGEX = re.compile(
|
|
298
|
+
eprint('Using __ERROR_MESSAGES_TO_IGNORE_REGEX, ignoring ERROR_MESSAGES_TO_IGNORE')
|
|
299
|
+
__ERROR_MESSAGES_TO_IGNORE_REGEX = re.compile(__ERROR_MESSAGES_TO_IGNORE_REGEX)
|
|
341
300
|
else:
|
|
342
301
|
__ERROR_MESSAGES_TO_IGNORE_REGEX = re.compile('|'.join(ERROR_MESSAGES_TO_IGNORE))
|
|
343
302
|
|
|
344
|
-
__DEBUG_MODE = __configs_from_file.get('__DEBUG_MODE', __build_in_default_config['__DEBUG_MODE'])
|
|
345
|
-
|
|
346
303
|
# Load mssh Functional Global Variables
|
|
347
304
|
if True:
|
|
348
305
|
__global_suppress_printout = False
|
|
@@ -355,7 +312,6 @@ if True:
|
|
|
355
312
|
__ipmiiInterfaceIPPrefix = DEFAULT_IPMI_INTERFACE_IP_PREFIX
|
|
356
313
|
__keyPressesIn = [[]]
|
|
357
314
|
_emo = False
|
|
358
|
-
_etc_hosts = __configs_from_file.get('_etc_hosts', __build_in_default_config['_etc_hosts'])
|
|
359
315
|
__curses_global_color_pairs = {(-1,-1):1}
|
|
360
316
|
__curses_current_color_pair_index = 2 # Start from 1, as 0 is the default color pair
|
|
361
317
|
__curses_color_table = {}
|
|
@@ -394,12 +350,11 @@ if __curses_available:
|
|
|
394
350
|
_binPaths = {}
|
|
395
351
|
def check_path(program_name):
|
|
396
352
|
global __configs_from_file
|
|
397
|
-
global __build_in_default_config
|
|
398
353
|
global _binPaths
|
|
399
354
|
config_key = f'_{program_name}Path'
|
|
400
355
|
program_path = (
|
|
401
356
|
__configs_from_file.get(config_key) or
|
|
402
|
-
|
|
357
|
+
globals().get(config_key) or
|
|
403
358
|
shutil.which(program_name)
|
|
404
359
|
)
|
|
405
360
|
if program_path:
|
|
@@ -2715,14 +2670,45 @@ def generate_default_config(args):
|
|
|
2715
2670
|
'ERROR_MESSAGES_TO_IGNORE': ERROR_MESSAGES_TO_IGNORE,
|
|
2716
2671
|
}
|
|
2717
2672
|
|
|
2718
|
-
def write_default_config(args,CONFIG_FILE
|
|
2719
|
-
if backup and os.path.exists(CONFIG_FILE):
|
|
2720
|
-
os.rename(CONFIG_FILE,CONFIG_FILE+'.bak')
|
|
2673
|
+
def write_default_config(args,CONFIG_FILE = None):
|
|
2721
2674
|
default_config = generate_default_config(args)
|
|
2722
2675
|
# apply the updated defualt_config to __configs_from_file and write that to file
|
|
2723
2676
|
__configs_from_file.update(default_config)
|
|
2724
|
-
|
|
2725
|
-
json.
|
|
2677
|
+
if not CONFIG_FILE:
|
|
2678
|
+
print(json.dumps(__configs_from_file, indent=4))
|
|
2679
|
+
return
|
|
2680
|
+
backup = True
|
|
2681
|
+
if os.path.exists(CONFIG_FILE):
|
|
2682
|
+
eprint(f"Warning: {CONFIG_FILE!r} already exists, what to do? (o/b/n)")
|
|
2683
|
+
eprint(f"o: Overwrite the file")
|
|
2684
|
+
eprint(f"b: Rename the current config file at {CONFIG_FILE!r}.bak forcefully and write the new config file (default)")
|
|
2685
|
+
eprint(f"n: Do nothing")
|
|
2686
|
+
inStr = input_with_timeout_and_countdown(10)
|
|
2687
|
+
if (not inStr) or inStr.lower().strip().startswith('b'):
|
|
2688
|
+
backup = True
|
|
2689
|
+
elif inStr.lower().strip().startswith('o'):
|
|
2690
|
+
backup = False
|
|
2691
|
+
else:
|
|
2692
|
+
eprint("Aborted")
|
|
2693
|
+
sys.exit(1)
|
|
2694
|
+
try:
|
|
2695
|
+
if backup and os.path.exists(CONFIG_FILE):
|
|
2696
|
+
os.rename(CONFIG_FILE,CONFIG_FILE+'.bak')
|
|
2697
|
+
except Exception as e:
|
|
2698
|
+
eprint(f"Error: Unable to backup the config file: {e!r}")
|
|
2699
|
+
eprint(f"Do you want to continue writing the new config file to {CONFIG_FILE!r}? (y/n)")
|
|
2700
|
+
inStr = input_with_timeout_and_countdown(10)
|
|
2701
|
+
if not inStr or not inStr.lower().strip().startswith('y'):
|
|
2702
|
+
eprint("Aborted")
|
|
2703
|
+
sys.exit(1)
|
|
2704
|
+
try:
|
|
2705
|
+
with open(CONFIG_FILE,'w') as f:
|
|
2706
|
+
json.dump(__configs_from_file,f,indent=4)
|
|
2707
|
+
eprint(f"Config file written to {CONFIG_FILE!r}")
|
|
2708
|
+
except Exception as e:
|
|
2709
|
+
eprint(f"Error: Unable to write to the config file: {e!r}")
|
|
2710
|
+
eprint(f'Printing the config file to stdout:')
|
|
2711
|
+
print(json.dumps(__configs_from_file, indent=4))
|
|
2726
2712
|
|
|
2727
2713
|
# ------------ Wrapper Block ----------------
|
|
2728
2714
|
def main():
|
|
@@ -2734,11 +2720,12 @@ def main():
|
|
|
2734
2720
|
global _binPaths
|
|
2735
2721
|
global _env_file
|
|
2736
2722
|
global __DEBUG_MODE
|
|
2723
|
+
global __configs_from_file
|
|
2737
2724
|
_emo = False
|
|
2738
2725
|
# We handle the signal
|
|
2739
2726
|
signal.signal(signal.SIGINT, signal_handler)
|
|
2740
2727
|
# We parse the arguments
|
|
2741
|
-
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: {
|
|
2728
|
+
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 chain: {CONFIG_FILE_CHAIN!r}')
|
|
2742
2729
|
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)
|
|
2743
2730
|
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.')
|
|
2744
2731
|
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)
|
|
@@ -2775,7 +2762,9 @@ def main():
|
|
|
2775
2762
|
group.add_argument("-nsu","--no_skip_unreachable",dest = 'skip_unreachable', action='store_false', help=f"Do not skip unreachable hosts. Note: Timedout Hosts are considered unreachable. Note: multiple command sequence will still auto skip unreachable hosts. (default: {not DEFAULT_SKIP_UNREACHABLE})", default=not DEFAULT_SKIP_UNREACHABLE)
|
|
2776
2763
|
|
|
2777
2764
|
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)
|
|
2778
|
-
parser.add_argument('--
|
|
2765
|
+
parser.add_argument('--generate_config_file', action='store_true', help=f'Store / generate the default config file from command line argument and current config at --config_file / stdout')
|
|
2766
|
+
parser.add_argument('--config_file', type=str,nargs='?', help=f'Additional config file to use, will pioritize over config chains. When using with store_config_file, will store the resulting config file at this location. Use without a path will use multiSSH3.config.json',const='multiSSH3.config.json',default=None)
|
|
2767
|
+
parser.add_argument('--store_config_file',type = str,nargs='?',help=f'Store the default config file from command line argument and current config. Same as --store_config_file --config_file=<path>',const='multiSSH3.config.json')
|
|
2779
2768
|
parser.add_argument('--debug', action='store_true', help='Print debug information')
|
|
2780
2769
|
parser.add_argument('-ci','--copy_id', action='store_true', help='Copy the ssh id to the hosts')
|
|
2781
2770
|
parser.add_argument("-V","--version", action='version', version=f'%(prog)s {version} with [ {", ".join(_binPaths.keys())} ] by {AUTHOR} ({AUTHOR_EMAIL})')
|
|
@@ -2796,33 +2785,26 @@ def main():
|
|
|
2796
2785
|
eprint(f"Warning: Unknown arguments, treating all as commands: {unknown!r}")
|
|
2797
2786
|
args.commands += unknown
|
|
2798
2787
|
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
elif inStr.lower().strip().startswith('o'):
|
|
2813
|
-
write_default_config(args,CONFIG_FILE,backup = False)
|
|
2814
|
-
eprint(f"Config file written to {CONFIG_FILE!r}")
|
|
2815
|
-
else:
|
|
2816
|
-
write_default_config(args,CONFIG_FILE,backup = True)
|
|
2817
|
-
eprint(f"Config file written to {CONFIG_FILE!r}")
|
|
2818
|
-
except Exception as e:
|
|
2819
|
-
eprint(f"Error while writing config file: {e!r}")
|
|
2820
|
-
import traceback
|
|
2821
|
-
eprint(traceback.format_exc())
|
|
2822
|
-
if not args.commands:
|
|
2823
|
-
with open(CONFIG_FILE,'r') as f:
|
|
2788
|
+
if args.generate_config_file or args.store_config_file:
|
|
2789
|
+
if args.store_config_file:
|
|
2790
|
+
configFileToWriteTo = args.store_config_file
|
|
2791
|
+
if args.config_file:
|
|
2792
|
+
if os.path.exists(args.config_file):
|
|
2793
|
+
__configs_from_file.update(load_config_file(os.path.expanduser(args.config_file)))
|
|
2794
|
+
else:
|
|
2795
|
+
eprint(f"Warning: Pre store config file {args.config_file!r} not found.")
|
|
2796
|
+
else:
|
|
2797
|
+
configFileToWriteTo = args.config_file
|
|
2798
|
+
write_default_config(args,configFileToWriteTo)
|
|
2799
|
+
if not args.commands and configFileToWriteTo:
|
|
2800
|
+
with open(configFileToWriteTo,'r') as f:
|
|
2824
2801
|
eprint(f"Config file content: \n{f.read()}")
|
|
2825
2802
|
sys.exit(0)
|
|
2803
|
+
if args.config_file:
|
|
2804
|
+
if os.path.exists(args.config_file):
|
|
2805
|
+
__configs_from_file.update(load_config_file(os.path.expanduser(args.config_file)))
|
|
2806
|
+
else:
|
|
2807
|
+
eprint(f"Warning: Config file {args.config_file!r} not found, ignoring it.")
|
|
2826
2808
|
|
|
2827
2809
|
_env_file = args.env_file
|
|
2828
2810
|
__DEBUG_MODE = args.debug
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|